Add wuffs_base__magic_number_guess_fourcc
diff --git a/example/imageviewer/imageviewer.c b/example/imageviewer/imageviewer.c
index 87c1779..d98b6b5 100644
--- a/example/imageviewer/imageviewer.c
+++ b/example/imageviewer/imageviewer.c
@@ -123,15 +123,23 @@
 
 bool  //
 load_image_type() {
-  while (g_src.meta.wi == 0) {
-    if (!read_more_src()) {
+  int32_t fourcc = 0;
+  while (true) {
+    fourcc = wuffs_base__magic_number_guess_fourcc(
+        wuffs_base__io_buffer__reader_slice(&g_src));
+    if (fourcc >= 0) {
+      break;
+    } else if (wuffs_base__io_buffer__writer_length(&g_src) == 0) {
+      printf("%s: could not determine file format\n", g_filename);
+      return false;
+    } else if (!read_more_src()) {
       return false;
     }
   }
 
   wuffs_base__status status;
-  switch (g_src_buffer_array[0]) {
-    case 0x00:
+  switch (fourcc) {
+    case WUFFS_BASE__FOURCC__WBMP:
       status = wuffs_wbmp__decoder__initialize(
           &g_potential_decoders.wbmp, sizeof g_potential_decoders.wbmp,
           WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
@@ -144,7 +152,7 @@
               &g_potential_decoders.wbmp);
       break;
 
-    case 'B':
+    case WUFFS_BASE__FOURCC__BMP:
       status = wuffs_bmp__decoder__initialize(
           &g_potential_decoders.bmp, sizeof g_potential_decoders.bmp,
           WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
@@ -157,7 +165,7 @@
               &g_potential_decoders.bmp);
       break;
 
-    case 'G':
+    case WUFFS_BASE__FOURCC__GIF:
       status = wuffs_gif__decoder__initialize(
           &g_potential_decoders.gif, sizeof g_potential_decoders.gif,
           WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
@@ -170,7 +178,7 @@
               &g_potential_decoders.gif);
       break;
 
-    case 'n':
+    case WUFFS_BASE__FOURCC__NIE:
       status = wuffs_nie__decoder__initialize(
           &g_potential_decoders.nie, sizeof g_potential_decoders.nie,
           WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
@@ -183,7 +191,7 @@
               &g_potential_decoders.nie);
       break;
 
-    case 0x89:
+    case WUFFS_BASE__FOURCC__PNG:
       status = wuffs_png__decoder__initialize(
           &g_potential_decoders.png, sizeof g_potential_decoders.png,
           WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
diff --git a/internal/cgen/base/all-impl.c b/internal/cgen/base/all-impl.c
index bfd21e6..feb3605 100644
--- a/internal/cgen/base/all-impl.c
+++ b/internal/cgen/base/all-impl.c
@@ -152,6 +152,15 @@
         // defined(WUFFS_CONFIG__MODULE__BASE__INTCONV)
 
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \
+    defined(WUFFS_CONFIG__MODULE__BASE__MAGIC)
+
+// !! INSERT base/magic-submodule.c.
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) ||
+        // defined(WUFFS_CONFIG__MODULE__BASE) ||
+        // defined(WUFFS_CONFIG__MODULE__BASE__MAGIC)
+
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \
     defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV)
 
 // !! INSERT base/pixconv-submodule.c.
diff --git a/internal/cgen/base/fundamental-public.h b/internal/cgen/base/fundamental-public.h
index 5280c48..cfcc753 100644
--- a/internal/cgen/base/fundamental-public.h
+++ b/internal/cgen/base/fundamental-public.h
@@ -1099,3 +1099,29 @@
   }
   return wuffs_base__make_slice_u8(NULL, 0);
 }
+
+// ---------------- Magic Numbers
+
+// wuffs_base__magic_number_guess_fourcc guesses the file format of some data,
+// given its opening bytes. It returns a positive FourCC value on success.
+//
+// It returns zero if nothing matches its hard-coded list of 'magic numbers'.
+//
+// It returns a negative value if a longer prefix is required for a conclusive
+// result. For example, seeing a single 'B' byte is not enough to discriminate
+// the BMP and BPG image file formats.
+//
+// It does not do a full validity check. Like any guess made from a short
+// prefix of the data, it may return false positives. Data that starts with 99
+// bytes of valid JPEG followed by corruption or truncation is an invalid JPEG
+// image overall, but this function will still return WUFFS_BASE__FOURCC__JPEG.
+//
+// Another source of false positives is that some 'magic numbers' are valid
+// ASCII data. A file starting with "GIF87a and GIF89a are the two versions of
+// GIF" will match GIF's 'magic number' even if it's plain text, not an image.
+//
+// For modular builds that divide the base module into sub-modules, using this
+// function requires the WUFFS_CONFIG__MODULE__BASE__MAGIC sub-module, not just
+// WUFFS_CONFIG__MODULE__BASE__CORE.
+WUFFS_BASE__MAYBE_STATIC int32_t  //
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix);
diff --git a/internal/cgen/base/magic-submodule.c b/internal/cgen/base/magic-submodule.c
new file mode 100644
index 0000000..05a59c4
--- /dev/null
+++ b/internal/cgen/base/magic-submodule.c
@@ -0,0 +1,96 @@
+// After editing this file, run "go generate" in the ../data directory.
+
+// Copyright 2021 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ---------------- Magic Numbers
+
+WUFFS_BASE__MAYBE_STATIC int32_t  //
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix) {
+  // table holds the 'magic numbers' (which are actually variable length
+  // strings). The strings may contain NUL bytes, so the "const char* magic"
+  // value starts with the length-minus-1 of the 'magic number'.
+  //
+  // Keep it sorted by magic[1], then magic[0] descending and finally by
+  // magic[2:]. When multiple entries match, the longest one wins.
+  static struct {
+    int32_t fourcc;
+    const char* magic;
+  } table[] = {
+      {0x57424D50, "\x01\x00\x00"},          // WBMP
+      {0x424D5020, "\x01\x42\x4D"},          // BMP
+      {0x47494620, "\x03\x47\x49\x46\x38"},  // GIF
+      {0x54494646, "\x03\x49\x49\x2A\x00"},  // TIFF (little-endian)
+      {0x54494646, "\x03\x4D\x4D\x00\x2A"},  // TIFF (big-endian)
+      {0x52494646, "\x03\x52\x49\x46\x46"},  // RIFF (see § below)
+      {0x4E494520, "\x02\x6E\xC3\xAF"},      // NIE
+      {0x504E4720, "\x03\x89\x50\x4E\x47"},  // PNG
+      {0x4A504547, "\x01\xFF\xD8"},          // JPEG
+  };
+  static const size_t table_len = sizeof(table) / sizeof(table[0]);
+
+  if (prefix.len == 0) {
+    return -1;
+  }
+  uint8_t pre_first_byte = prefix.ptr[0];
+
+  int32_t fourcc = 0;
+  size_t i;
+  for (i = 0; i < table_len; i++) {
+    uint8_t mag_first_byte = table[i].magic[1];
+    if (pre_first_byte < mag_first_byte) {
+      break;
+    } else if (pre_first_byte > mag_first_byte) {
+      continue;
+    }
+    fourcc = table[i].fourcc;
+
+    uint8_t mag_remaining_len = table[i].magic[0];
+    if (mag_remaining_len == 0) {
+      goto match;
+    }
+
+    const char* mag_remaining_ptr = table[i].magic + 2;
+    uint8_t* pre_remaining_ptr = prefix.ptr + 1;
+    size_t pre_remaining_len = prefix.len - 1;
+    if (pre_remaining_len < mag_remaining_len) {
+      if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, pre_remaining_len)) {
+        return -1;
+      }
+    } else {
+      if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, mag_remaining_len)) {
+        goto match;
+      }
+    }
+  }
+  return 0;
+
+match:
+  // Some FourCC values (see § above) are further specialized.
+  if (fourcc == 0x52494646) {  // 'RIFF'be
+    if (prefix.len < 16) {
+      return -1;
+    }
+    uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 8);
+    if (x == 0x57454250) {  // 'WEBP'be
+      uint32_t y = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 12);
+      if (y == 0x56503820) {         // 'VP8 'be
+        return 0x57503820;           // 'WP8 'be
+      } else if (y == 0x5650384C) {  // 'VP8L'be
+        return 0x5750384C;           // 'WP8L'be
+      }
+    }
+  }
+  return fourcc;
+}
diff --git a/internal/cgen/cgen.go b/internal/cgen/cgen.go
index 0eca973..50bb81f 100644
--- a/internal/cgen/cgen.go
+++ b/internal/cgen/cgen.go
@@ -100,6 +100,7 @@
 	"floatconv",
 	"intconv",
 	"interfaces",
+	"magic",
 	"pixconv",
 	"utf8",
 }
@@ -129,6 +130,7 @@
 				"// !! INSERT base/copyright\n":              insertBaseCopyright,
 				"// !! INSERT base/floatconv-submodule.c.\n": insertBaseFloatConvSubmoduleC,
 				"// !! INSERT base/intconv-submodule.c.\n":   insertBaseIntConvSubmoduleC,
+				"// !! INSERT base/magic-submodule.c.\n":     insertBaseMagicSubmoduleC,
 				"// !! INSERT base/pixconv-submodule.c.\n":   insertBasePixConvSubmoduleC,
 				"// !! INSERT base/utf8-submodule.c.\n":      insertBaseUTF8SubmoduleC,
 				"// !! INSERT vtable names.\n": func(b *buffer) error {
@@ -369,6 +371,11 @@
 	return nil
 }
 
+func insertBaseMagicSubmoduleC(buf *buffer) error {
+	buf.writes(data.BaseMagicSubmoduleC)
+	return nil
+}
+
 func insertBasePixConvSubmoduleC(buf *buffer) error {
 	buf.writes(data.BasePixConvSubmoduleC)
 	return nil
diff --git a/internal/cgen/data/data.go b/internal/cgen/data/data.go
index 119fd4b..e903aeb 100644
--- a/internal/cgen/data/data.go
+++ b/internal/cgen/data/data.go
@@ -24,8 +24,8 @@
 	"// ----------------\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__CORE)\n\nconst uint8_t wuffs_base__low_bits_mask__u8[9] = {\n    0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF,\n};\n\nconst uint16_t wuffs_base__low_bits_mask__u16[17] = {\n    0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,\n    0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,\n};\n\nconst uint32_t wuffs_base__low_bits_mask__u32[33] = {\n    0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F,\n    0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF,\n    0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF,\n    0x0003FFFF, 0x0007FFFF, 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF,\n    0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, 0x1FFFFFFF,\n    0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,\n};\n\nconst uint64_t wuffs_base__low_bits_mask__u64[65] = {\n    0x0000000000000000, 0x000" +
 	"0000000000001, 0x0000000000000003,\n    0x0000000000000007, 0x000000000000000F, 0x000000000000001F,\n    0x000000000000003F, 0x000000000000007F, 0x00000000000000FF,\n    0x00000000000001FF, 0x00000000000003FF, 0x00000000000007FF,\n    0x0000000000000FFF, 0x0000000000001FFF, 0x0000000000003FFF,\n    0x0000000000007FFF, 0x000000000000FFFF, 0x000000000001FFFF,\n    0x000000000003FFFF, 0x000000000007FFFF, 0x00000000000FFFFF,\n    0x00000000001FFFFF, 0x00000000003FFFFF, 0x00000000007FFFFF,\n    0x0000000000FFFFFF, 0x0000000001FFFFFF, 0x0000000003FFFFFF,\n    0x0000000007FFFFFF, 0x000000000FFFFFFF, 0x000000001FFFFFFF,\n    0x000000003FFFFFFF, 0x000000007FFFFFFF, 0x00000000FFFFFFFF,\n    0x00000001FFFFFFFF, 0x00000003FFFFFFFF, 0x00000007FFFFFFFF,\n    0x0000000FFFFFFFFF, 0x0000001FFFFFFFFF, 0x0000003FFFFFFFFF,\n    0x0000007FFFFFFFFF, 0x000000FFFFFFFFFF, 0x000001FFFFFFFFFF,\n    0x000003FFFFFFFFFF, 0x000007FFFFFFFFFF, 0x00000FFFFFFFFFFF,\n    0x00001FFFFFFFFFFF, 0x00003FFFFFFFFFFF, 0x00007FFFFFFFFFFF,\n    0x0000FFFFFFFFFFFF, 0x000" +
 	"1FFFFFFFFFFFF, 0x0003FFFFFFFFFFFF,\n    0x0007FFFFFFFFFFFF, 0x000FFFFFFFFFFFFF, 0x001FFFFFFFFFFFFF,\n    0x003FFFFFFFFFFFFF, 0x007FFFFFFFFFFFFF, 0x00FFFFFFFFFFFFFF,\n    0x01FFFFFFFFFFFFFF, 0x03FFFFFFFFFFFFFF, 0x07FFFFFFFFFFFFFF,\n    0x0FFFFFFFFFFFFFFF, 0x1FFFFFFFFFFFFFFF, 0x3FFFFFFFFFFFFFFF,\n    0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF,\n};\n\nconst uint32_t wuffs_base__pixel_format__bits_per_channel[16] = {\n    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,\n    0x08, 0x0A, 0x0C, 0x10, 0x18, 0x20, 0x30, 0x40,\n};\n\n// !! INSERT wuffs_base__status strings.\n\n// !! INSERT vtable names.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE)  ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__CORE)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__INTERFACES)\n\n// !! INSERT InterfaceDefinitions.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_" +
-	"CONFIG__MODULE__BASE__INTERFACES)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__FLOATCONV)\n\n// !! INSERT base/floatconv-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__FLOATCONV)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__INTCONV)\n\n// !! INSERT base/intconv-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__INTCONV)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV)\n\n// !! INSERT base/pixconv-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV)\n\n#if !defined(W" +
-	"UFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__UTF8)\n\n// !! INSERT base/utf8-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__UTF8)\n\n#ifdef __cplusplus\n}  // extern \"C\"\n#endif\n\n#endif  // WUFFS_IMPLEMENTATION\n\n// !! WUFFS MONOLITHIC RELEASE DISCARDS EVERYTHING BELOW.\n\n#endif  // WUFFS_INCLUDE_GUARD__BASE\n" +
+	"CONFIG__MODULE__BASE__INTERFACES)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__FLOATCONV)\n\n// !! INSERT base/floatconv-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__FLOATCONV)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__INTCONV)\n\n// !! INSERT base/intconv-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__INTCONV)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__MAGIC)\n\n// !! INSERT base/magic-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__MAGIC)\n\n#if !defined(WUFFS_C" +
+	"ONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV)\n\n// !! INSERT base/pixconv-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV)\n\n#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \\\n    defined(WUFFS_CONFIG__MODULE__BASE__UTF8)\n\n// !! INSERT base/utf8-submodule.c.\n\n#endif  // !defined(WUFFS_CONFIG__MODULES) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE) ||\n        // defined(WUFFS_CONFIG__MODULE__BASE__UTF8)\n\n#ifdef __cplusplus\n}  // extern \"C\"\n#endif\n\n#endif  // WUFFS_IMPLEMENTATION\n\n// !! WUFFS MONOLITHIC RELEASE DISCARDS EVERYTHING BELOW.\n\n#endif  // WUFFS_INCLUDE_GUARD__BASE\n" +
 	""
 
 const BaseFundamentalPrivateH = "" +
@@ -112,7 +112,10 @@
 	"int8_t) wuffs_base__table_u8;\ntypedef WUFFS_BASE__TABLE(uint16_t) wuffs_base__table_u16;\ntypedef WUFFS_BASE__TABLE(uint32_t) wuffs_base__table_u32;\ntypedef WUFFS_BASE__TABLE(uint64_t) wuffs_base__table_u64;\n\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__make_slice_u8(uint8_t* ptr, size_t len) {\n  wuffs_base__slice_u8 ret;\n  ret.ptr = ptr;\n  ret.len = len;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u16  //\nwuffs_base__make_slice_u16(uint16_t* ptr, size_t len) {\n  wuffs_base__slice_u16 ret;\n  ret.ptr = ptr;\n  ret.len = len;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u32  //\nwuffs_base__make_slice_u32(uint32_t* ptr, size_t len) {\n  wuffs_base__slice_u32 ret;\n  ret.ptr = ptr;\n  ret.len = len;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u64  //\nwuffs_base__make_slice_u64(uint64_t* ptr, size_t len) {\n  wuffs_base__slice_u64 ret;\n  ret.ptr = ptr;\n  ret.len = len;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__empty_slice_u8() {\n  wuffs_base__slice_u8 ret;\n  ret.ptr = NULL;\n" +
 	"  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u16  //\nwuffs_base__empty_slice_u16() {\n  wuffs_base__slice_u16 ret;\n  ret.ptr = NULL;\n  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u32  //\nwuffs_base__empty_slice_u32() {\n  wuffs_base__slice_u32 ret;\n  ret.ptr = NULL;\n  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u64  //\nwuffs_base__empty_slice_u64() {\n  wuffs_base__slice_u64 ret;\n  ret.ptr = NULL;\n  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u8  //\nwuffs_base__empty_table_u8() {\n  wuffs_base__table_u8 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u16  //\nwuffs_base__empty_table_u16() {\n  wuffs_base__table_u16 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u32  //\nwuffs_base__empty_table_u32() {\n  wuffs_base__table_u32 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stri" +
 	"de = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u64  //\nwuffs_base__empty_table_u64() {\n  wuffs_base__table_u64 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\n// wuffs_base__slice_u8__subslice_i returns s[i:].\n//\n// It returns an empty slice if i is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_i(wuffs_base__slice_u8 s, uint64_t i) {\n  if ((i <= SIZE_MAX) && (i <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(s.len - i)));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n// wuffs_base__slice_u8__subslice_j returns s[:j].\n//\n// It returns an empty slice if j is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_j(wuffs_base__slice_u8 s, uint64_t j) {\n  if ((j <= SIZE_MAX) && (j <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr, ((size_t)j));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n// wuffs_base__slice_u8__subslice_ij returns s[i:j].\n//\n// It " +
-	"returns an empty slice if i or j is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_ij(wuffs_base__slice_u8 s,\n                                  uint64_t i,\n                                  uint64_t j) {\n  if ((i <= j) && (j <= SIZE_MAX) && (j <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(j - i)));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n" +
+	"returns an empty slice if i or j is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_ij(wuffs_base__slice_u8 s,\n                                  uint64_t i,\n                                  uint64_t j) {\n  if ((i <= j) && (j <= SIZE_MAX) && (j <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(j - i)));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n" +
+	"" +
+	"// ---------------- Magic Numbers\n\n// wuffs_base__magic_number_guess_fourcc guesses the file format of some data,\n// given its opening bytes. It returns a positive FourCC value on success.\n//\n// It returns zero if nothing matches its hard-coded list of 'magic numbers'.\n//\n// It returns a negative value if a longer prefix is required for a conclusive\n// result. For example, seeing a single 'B' byte is not enough to discriminate\n// the BMP and BPG image file formats.\n//\n// It does not do a full validity check. Like any guess made from a short\n// prefix of the data, it may return false positives. Data that starts with 99\n// bytes of valid JPEG followed by corruption or truncation is an invalid JPEG\n// image overall, but this function will still return WUFFS_BASE__FOURCC__JPEG.\n//\n// Another source of false positives is that some 'magic numbers' are valid\n// ASCII data. A file starting with \"GIF87a and GIF89a are the two versions of\n// GIF\" will match GIF's 'magic number' even if it's plain text, not an image.\n//" +
+	"\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__MAGIC sub-module, not just\n// WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC int32_t  //\nwuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix);\n" +
 	""
 
 const BaseMemoryPrivateH = "" +
@@ -534,6 +537,12 @@
 	" goto done;\n\n    } else if (s_len == 1) {\n      if (d_len <\n          ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 2)) {\n        o.status.repr = wuffs_base__suspension__short_write;\n        goto done;\n      }\n      uint32_t s = ((uint32_t)(wuffs_base__peek_u8__no_bounds_check(s_ptr)))\n                   << 16;\n      s_ptr += 1;\n      *d_ptr++ = alphabet[0x3F & (s >> 18)];\n      *d_ptr++ = alphabet[0x3F & (s >> 12)];\n      if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) {\n        *d_ptr++ = '=';\n        *d_ptr++ = '=';\n      }\n      o.status.repr = NULL;\n      goto done;\n\n    } else {\n      o.status.repr = NULL;\n      goto done;\n    }\n  } while (0);\n\ndone:\n  o.num_dst = (size_t)(d_ptr - dst.ptr);\n  o.num_src = (size_t)(s_ptr - src.ptr);\n  return o;\n}\n" +
 	""
 
+const BaseMagicSubmoduleC = "" +
+	"// ---------------- Magic Numbers\n\nWUFFS_BASE__MAYBE_STATIC int32_t  //\nwuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix) {\n  // table holds the 'magic numbers' (which are actually variable length\n  // strings). The strings may contain NUL bytes, so the \"const char* magic\"\n  // value starts with the length-minus-1 of the 'magic number'.\n  //\n  // Keep it sorted by magic[1], then magic[0] descending and finally by\n  // magic[2:]. When multiple entries match, the longest one wins.\n  static struct {\n    int32_t fourcc;\n    const char* magic;\n  } table[] = {\n      {0x57424D50, \"\\x01\\x00\\x00\"},          // WBMP\n      {0x424D5020, \"\\x01\\x42\\x4D\"},          // BMP\n      {0x47494620, \"\\x03\\x47\\x49\\x46\\x38\"},  // GIF\n      {0x54494646, \"\\x03\\x49\\x49\\x2A\\x00\"},  // TIFF (little-endian)\n      {0x54494646, \"\\x03\\x4D\\x4D\\x00\\x2A\"},  // TIFF (big-endian)\n      {0x52494646, \"\\x03\\x52\\x49\\x46\\x46\"},  // RIFF (see § below)\n      {0x4E494520, \"\\x02\\x6E\\xC3\\xAF\"},      // NIE\n      {0x504E4720, \"\\x03\\x89\\x50\\x" +
+	"4E\\x47\"},  // PNG\n      {0x4A504547, \"\\x01\\xFF\\xD8\"},          // JPEG\n  };\n  static const size_t table_len = sizeof(table) / sizeof(table[0]);\n\n  if (prefix.len == 0) {\n    return -1;\n  }\n  uint8_t pre_first_byte = prefix.ptr[0];\n\n  int32_t fourcc = 0;\n  size_t i;\n  for (i = 0; i < table_len; i++) {\n    uint8_t mag_first_byte = table[i].magic[1];\n    if (pre_first_byte < mag_first_byte) {\n      break;\n    } else if (pre_first_byte > mag_first_byte) {\n      continue;\n    }\n    fourcc = table[i].fourcc;\n\n    uint8_t mag_remaining_len = table[i].magic[0];\n    if (mag_remaining_len == 0) {\n      goto match;\n    }\n\n    const char* mag_remaining_ptr = table[i].magic + 2;\n    uint8_t* pre_remaining_ptr = prefix.ptr + 1;\n    size_t pre_remaining_len = prefix.len - 1;\n    if (pre_remaining_len < mag_remaining_len) {\n      if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, pre_remaining_len)) {\n        return -1;\n      }\n    } else {\n      if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, mag_remaining_len)) {\n        " +
+	"goto match;\n      }\n    }\n  }\n  return 0;\n\nmatch:\n  // Some FourCC values (see § above) are further specialized.\n  if (fourcc == 0x52494646) {  // 'RIFF'be\n    if (prefix.len < 16) {\n      return -1;\n    }\n    uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 8);\n    if (x == 0x57454250) {  // 'WEBP'be\n      uint32_t y = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 12);\n      if (y == 0x56503820) {         // 'VP8 'be\n        return 0x57503820;           // 'WP8 'be\n      } else if (y == 0x5650384C) {  // 'VP8L'be\n        return 0x5750384C;           // 'WP8L'be\n      }\n    }\n  }\n  return fourcc;\n}\n" +
+	""
+
 const BasePixConvSubmoduleC = "" +
 	"// ---------------- Pixel Swizzler\n\nstatic inline uint32_t  //\nwuffs_base__swap_u32_argb_abgr(uint32_t u) {\n  uint32_t o = u & 0xFF00FF00;\n  uint32_t r = u & 0x00FF0000;\n  uint32_t b = u & 0x000000FF;\n  return o | (r >> 16) | (b << 16);\n}\n\n" +
 	"" +
diff --git a/internal/cgen/data/gen.go b/internal/cgen/data/gen.go
index 65a9c85..b84c85e 100644
--- a/internal/cgen/data/gen.go
+++ b/internal/cgen/data/gen.go
@@ -94,6 +94,7 @@
 		{"../base/floatconv-submodule-code.c", "BaseFloatConvSubmoduleCodeC"},
 		{"../base/floatconv-submodule-data.c", "BaseFloatConvSubmoduleDataC"},
 		{"../base/intconv-submodule.c", "BaseIntConvSubmoduleC"},
+		{"../base/magic-submodule.c", "BaseMagicSubmoduleC"},
 		{"../base/pixconv-submodule.c", "BasePixConvSubmoduleC"},
 		{"../base/utf8-submodule.c", "BaseUTF8SubmoduleC"},
 
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 69f6672..8df4bbb 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -1306,6 +1306,32 @@
   return wuffs_base__make_slice_u8(NULL, 0);
 }
 
+// ---------------- Magic Numbers
+
+// wuffs_base__magic_number_guess_fourcc guesses the file format of some data,
+// given its opening bytes. It returns a positive FourCC value on success.
+//
+// It returns zero if nothing matches its hard-coded list of 'magic numbers'.
+//
+// It returns a negative value if a longer prefix is required for a conclusive
+// result. For example, seeing a single 'B' byte is not enough to discriminate
+// the BMP and BPG image file formats.
+//
+// It does not do a full validity check. Like any guess made from a short
+// prefix of the data, it may return false positives. Data that starts with 99
+// bytes of valid JPEG followed by corruption or truncation is an invalid JPEG
+// image overall, but this function will still return WUFFS_BASE__FOURCC__JPEG.
+//
+// Another source of false positives is that some 'magic numbers' are valid
+// ASCII data. A file starting with "GIF87a and GIF89a are the two versions of
+// GIF" will match GIF's 'magic number' even if it's plain text, not an image.
+//
+// For modular builds that divide the base module into sub-modules, using this
+// function requires the WUFFS_CONFIG__MODULE__BASE__MAGIC sub-module, not just
+// WUFFS_CONFIG__MODULE__BASE__CORE.
+WUFFS_BASE__MAYBE_STATIC int32_t  //
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix);
+
 // ---------------- Ranges and Rects
 
 // See https://github.com/google/wuffs/blob/main/doc/note/ranges-and-rects.md
@@ -14282,6 +14308,94 @@
         // defined(WUFFS_CONFIG__MODULE__BASE__INTCONV)
 
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \
+    defined(WUFFS_CONFIG__MODULE__BASE__MAGIC)
+
+// ---------------- Magic Numbers
+
+WUFFS_BASE__MAYBE_STATIC int32_t  //
+wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix) {
+  // table holds the 'magic numbers' (which are actually variable length
+  // strings). The strings may contain NUL bytes, so the "const char* magic"
+  // value starts with the length-minus-1 of the 'magic number'.
+  //
+  // Keep it sorted by magic[1], then magic[0] descending and finally by
+  // magic[2:]. When multiple entries match, the longest one wins.
+  static struct {
+    int32_t fourcc;
+    const char* magic;
+  } table[] = {
+      {0x57424D50, "\x01\x00\x00"},          // WBMP
+      {0x424D5020, "\x01\x42\x4D"},          // BMP
+      {0x47494620, "\x03\x47\x49\x46\x38"},  // GIF
+      {0x54494646, "\x03\x49\x49\x2A\x00"},  // TIFF (little-endian)
+      {0x54494646, "\x03\x4D\x4D\x00\x2A"},  // TIFF (big-endian)
+      {0x52494646, "\x03\x52\x49\x46\x46"},  // RIFF (see § below)
+      {0x4E494520, "\x02\x6E\xC3\xAF"},      // NIE
+      {0x504E4720, "\x03\x89\x50\x4E\x47"},  // PNG
+      {0x4A504547, "\x01\xFF\xD8"},          // JPEG
+  };
+  static const size_t table_len = sizeof(table) / sizeof(table[0]);
+
+  if (prefix.len == 0) {
+    return -1;
+  }
+  uint8_t pre_first_byte = prefix.ptr[0];
+
+  int32_t fourcc = 0;
+  size_t i;
+  for (i = 0; i < table_len; i++) {
+    uint8_t mag_first_byte = table[i].magic[1];
+    if (pre_first_byte < mag_first_byte) {
+      break;
+    } else if (pre_first_byte > mag_first_byte) {
+      continue;
+    }
+    fourcc = table[i].fourcc;
+
+    uint8_t mag_remaining_len = table[i].magic[0];
+    if (mag_remaining_len == 0) {
+      goto match;
+    }
+
+    const char* mag_remaining_ptr = table[i].magic + 2;
+    uint8_t* pre_remaining_ptr = prefix.ptr + 1;
+    size_t pre_remaining_len = prefix.len - 1;
+    if (pre_remaining_len < mag_remaining_len) {
+      if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, pre_remaining_len)) {
+        return -1;
+      }
+    } else {
+      if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, mag_remaining_len)) {
+        goto match;
+      }
+    }
+  }
+  return 0;
+
+match:
+  // Some FourCC values (see § above) are further specialized.
+  if (fourcc == 0x52494646) {  // 'RIFF'be
+    if (prefix.len < 16) {
+      return -1;
+    }
+    uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 8);
+    if (x == 0x57454250) {  // 'WEBP'be
+      uint32_t y = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 12);
+      if (y == 0x56503820) {         // 'VP8 'be
+        return 0x57503820;           // 'WP8 'be
+      } else if (y == 0x5650384C) {  // 'VP8L'be
+        return 0x5750384C;           // 'WP8L'be
+      }
+    }
+  }
+  return fourcc;
+}
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) ||
+        // defined(WUFFS_CONFIG__MODULE__BASE) ||
+        // defined(WUFFS_CONFIG__MODULE__BASE__MAGIC)
+
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \
     defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV)
 
 // ---------------- Pixel Swizzler