Prepare to decode BMP embedding a JPEG or PNG
diff --git a/example/imageviewer/imageviewer.c b/example/imageviewer/imageviewer.c
index d117cdc..41625b4 100644
--- a/example/imageviewer/imageviewer.c
+++ b/example/imageviewer/imageviewer.c
@@ -179,6 +179,7 @@
     if (status.repr == NULL) {
       break;
     } else if (status.repr != wuffs_base__suspension__short_read) {
+      // TODO: handle wuffs_base__note__i_o_redirect.
       printf("%s: %s\n", g_filename, wuffs_base__status__message(&status));
       return false;
     }
diff --git a/internal/cgen/base/strconv-impl.c b/internal/cgen/base/strconv-impl.c
index a6013ba..640fbc0 100644
--- a/internal/cgen/base/strconv-impl.c
+++ b/internal/cgen/base/strconv-impl.c
@@ -221,7 +221,7 @@
     v &= 0x0F;
 
     // UINT64_MAX is 18446744073709551615, which is ((10 * max10) + max1).
-    const uint64_t max10 = 1844674407370955161;
+    const uint64_t max10 = 1844674407370955161u;
     const uint8_t max1 = 5;
 
     for (; p < q; p++) {
diff --git a/internal/cgen/cgen.go b/internal/cgen/cgen.go
index bf9fb32..d2f867b 100644
--- a/internal/cgen/cgen.go
+++ b/internal/cgen/cgen.go
@@ -44,6 +44,8 @@
 
 	mibi = big.NewInt(1 << 20)
 
+	maxInt64 = big.NewInt((1 << 63) - 1)
+
 	typeExprUtility = a.NewTypeExpr(0, t.IDBase, t.IDUtility, nil, nil, nil)
 )
 
diff --git a/internal/cgen/data.go b/internal/cgen/data.go
index 6be82bf..abdef5f 100644
--- a/internal/cgen/data.go
+++ b/internal/cgen/data.go
@@ -37,9 +37,9 @@
 	"" +
 	"// --------\n\nWUFFS_BASE__MAYBE_STATIC wuffs_base__result_i64  //\nwuffs_base__parse_number_i64(wuffs_base__slice_u8 s) {\n  uint8_t* p = s.ptr;\n  uint8_t* q = s.ptr + s.len;\n\n  for (; (p < q) && (*p == '_'); p++) {\n  }\n\n  bool negative = false;\n  if (p >= q) {\n    goto fail_bad_argument;\n  } else if (*p == '-') {\n    p++;\n    negative = true;\n  } else if (*p == '+') {\n    p++;\n  }\n\n  do {\n    wuffs_base__result_u64 r = wuffs_base__parse_number_u64(\n        wuffs_base__make_slice_u8(p, (size_t)(q - p)));\n    if (r.status.repr != NULL) {\n      wuffs_base__result_i64 ret;\n      ret.status.repr = r.status.repr;\n      ret.value = 0;\n      return ret;\n    } else if (negative) {\n      if (r.value > 0x8000000000000000) {\n        goto fail_out_of_bounds;\n      }\n      wuffs_base__result_i64 ret;\n      ret.status.repr = NULL;\n      ret.value = -(int64_t)(r.value);\n      return ret;\n    } else if (r.value > 0x7FFFFFFFFFFFFFFF) {\n      goto fail_out_of_bounds;\n    } else {\n      wuffs_base__result_i64 ret;\n      ret.status" +
 	".repr = NULL;\n      ret.value = +(int64_t)(r.value);\n      return ret;\n    }\n  } while (0);\n\nfail_bad_argument:\n  do {\n    wuffs_base__result_i64 ret;\n    ret.status.repr = wuffs_base__error__bad_argument;\n    ret.value = 0;\n    return ret;\n  } while (0);\n\nfail_out_of_bounds:\n  do {\n    wuffs_base__result_i64 ret;\n    ret.status.repr = wuffs_base__error__out_of_bounds;\n    ret.value = 0;\n    return ret;\n  } while (0);\n}\n\nWUFFS_BASE__MAYBE_STATIC wuffs_base__result_u64  //\nwuffs_base__parse_number_u64(wuffs_base__slice_u8 s) {\n  uint8_t* p = s.ptr;\n  uint8_t* q = s.ptr + s.len;\n\n  for (; (p < q) && (*p == '_'); p++) {\n  }\n\n  if (p >= q) {\n    goto fail_bad_argument;\n\n  } else if (*p == '0') {\n    p++;\n    if (p >= q) {\n      goto ok_zero;\n    }\n    if (*p == '_') {\n      p++;\n      for (; p < q; p++) {\n        if (*p != '_') {\n          goto fail_bad_argument;\n        }\n      }\n      goto ok_zero;\n    }\n\n    if ((*p == 'x') || (*p == 'X')) {\n      p++;\n      for (; (p < q) && (*p == '_'); p++) {\n      }\n      " +
-	"if (p < q) {\n        goto hexadecimal;\n      }\n\n    } else if ((*p == 'd') || (*p == 'D')) {\n      p++;\n      for (; (p < q) && (*p == '_'); p++) {\n      }\n      if (p < q) {\n        goto decimal;\n      }\n    }\n\n    goto fail_bad_argument;\n  }\n\ndecimal:\n  do {\n    uint64_t v = wuffs_base__parse_number__decimal_digits[*p++];\n    if (v == 0) {\n      goto fail_bad_argument;\n    }\n    v &= 0x0F;\n\n    // UINT64_MAX is 18446744073709551615, which is ((10 * max10) + max1).\n    const uint64_t max10 = 1844674407370955161;\n    const uint8_t max1 = 5;\n\n    for (; p < q; p++) {\n      if (*p == '_') {\n        continue;\n      }\n      uint8_t digit = wuffs_base__parse_number__decimal_digits[*p];\n      if (digit == 0) {\n        goto fail_bad_argument;\n      }\n      digit &= 0x0F;\n      if ((v > max10) || ((v == max10) && (digit > max1))) {\n        goto fail_out_of_bounds;\n      }\n      v = (10 * v) + ((uint64_t)(digit));\n    }\n\n    wuffs_base__result_u64 ret;\n    ret.status.repr = NULL;\n    ret.value = v;\n    return ret;\n  }" +
-	" while (0);\n\nhexadecimal:\n  do {\n    uint64_t v = wuffs_base__parse_number__hexadecimal_digits[*p++];\n    if (v == 0) {\n      goto fail_bad_argument;\n    }\n    v &= 0x0F;\n\n    for (; p < q; p++) {\n      if (*p == '_') {\n        continue;\n      }\n      uint8_t digit = wuffs_base__parse_number__hexadecimal_digits[*p];\n      if (digit == 0) {\n        goto fail_bad_argument;\n      }\n      digit &= 0x0F;\n      if ((v >> 60) != 0) {\n        goto fail_out_of_bounds;\n      }\n      v = (v << 4) | ((uint64_t)(digit));\n    }\n\n    wuffs_base__result_u64 ret;\n    ret.status.repr = NULL;\n    ret.value = v;\n    return ret;\n  } while (0);\n\nok_zero:\n  do {\n    wuffs_base__result_u64 ret;\n    ret.status.repr = NULL;\n    ret.value = 0;\n    return ret;\n  } while (0);\n\nfail_bad_argument:\n  do {\n    wuffs_base__result_u64 ret;\n    ret.status.repr = wuffs_base__error__bad_argument;\n    ret.value = 0;\n    return ret;\n  } while (0);\n\nfail_out_of_bounds:\n  do {\n    wuffs_base__result_u64 ret;\n    ret.status.repr = wuffs_base__error__o" +
-	"ut_of_bounds;\n    ret.value = 0;\n    return ret;\n  } while (0);\n}\n\n" +
+	"if (p < q) {\n        goto hexadecimal;\n      }\n\n    } else if ((*p == 'd') || (*p == 'D')) {\n      p++;\n      for (; (p < q) && (*p == '_'); p++) {\n      }\n      if (p < q) {\n        goto decimal;\n      }\n    }\n\n    goto fail_bad_argument;\n  }\n\ndecimal:\n  do {\n    uint64_t v = wuffs_base__parse_number__decimal_digits[*p++];\n    if (v == 0) {\n      goto fail_bad_argument;\n    }\n    v &= 0x0F;\n\n    // UINT64_MAX is 18446744073709551615, which is ((10 * max10) + max1).\n    const uint64_t max10 = 1844674407370955161u;\n    const uint8_t max1 = 5;\n\n    for (; p < q; p++) {\n      if (*p == '_') {\n        continue;\n      }\n      uint8_t digit = wuffs_base__parse_number__decimal_digits[*p];\n      if (digit == 0) {\n        goto fail_bad_argument;\n      }\n      digit &= 0x0F;\n      if ((v > max10) || ((v == max10) && (digit > max1))) {\n        goto fail_out_of_bounds;\n      }\n      v = (10 * v) + ((uint64_t)(digit));\n    }\n\n    wuffs_base__result_u64 ret;\n    ret.status.repr = NULL;\n    ret.value = v;\n    return ret;\n  " +
+	"} while (0);\n\nhexadecimal:\n  do {\n    uint64_t v = wuffs_base__parse_number__hexadecimal_digits[*p++];\n    if (v == 0) {\n      goto fail_bad_argument;\n    }\n    v &= 0x0F;\n\n    for (; p < q; p++) {\n      if (*p == '_') {\n        continue;\n      }\n      uint8_t digit = wuffs_base__parse_number__hexadecimal_digits[*p];\n      if (digit == 0) {\n        goto fail_bad_argument;\n      }\n      digit &= 0x0F;\n      if ((v >> 60) != 0) {\n        goto fail_out_of_bounds;\n      }\n      v = (v << 4) | ((uint64_t)(digit));\n    }\n\n    wuffs_base__result_u64 ret;\n    ret.status.repr = NULL;\n    ret.value = v;\n    return ret;\n  } while (0);\n\nok_zero:\n  do {\n    wuffs_base__result_u64 ret;\n    ret.status.repr = NULL;\n    ret.value = 0;\n    return ret;\n  } while (0);\n\nfail_bad_argument:\n  do {\n    wuffs_base__result_u64 ret;\n    ret.status.repr = wuffs_base__error__bad_argument;\n    ret.value = 0;\n    return ret;\n  } while (0);\n\nfail_out_of_bounds:\n  do {\n    wuffs_base__result_u64 ret;\n    ret.status.repr = wuffs_base__error__" +
+	"out_of_bounds;\n    ret.value = 0;\n    return ret;\n  } while (0);\n}\n\n" +
 	"" +
 	"// ---------------- Hexadecimal\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len2 = src.len / 2;\n  size_t len = dst.len < src_len2 ? dst.len : src_len2;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[0]] << 4) |\n                   (wuffs_base__parse_number__hexadecimal_digits[s[1]] & 0x0F));\n    d += 1;\n    s += 2;\n  }\n\n  return len;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len4 = src.len / 4;\n  size_t len = dst.len < src_len4 ? dst.len : src_len4;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[2]] << 4) |\n                   (wuffs_base__parse_number__hexa" +
 	"decimal_digits[s[3]] & 0x0F));\n    d += 1;\n    s += 4;\n  }\n\n  return len;\n}\n\n" +
diff --git a/internal/cgen/expr.go b/internal/cgen/expr.go
index 7204fc2..300e726 100644
--- a/internal/cgen/expr.go
+++ b/internal/cgen/expr.go
@@ -30,6 +30,9 @@
 	if cv := n.ConstValue(); cv != nil {
 		if typ := n.MType(); typ.IsNumTypeOrIdeal() {
 			b.writes(cv.String())
+			if cv.Cmp(maxInt64) > 0 {
+				b.writeb('u')
+			}
 		} else if typ.IsNullptr() {
 			b.writes("NULL")
 		} else if typ.IsStatus() {
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index cab8fd8..b8edef9 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -27,11 +27,14 @@
 
 var FourCCs = [...][2]string{
 	{"ICCP", "International Color Consortium Profile"},
+	{"JPEG", "Joint Photographic Experts Group"},
+	{"PNG ", "Portable Network Graphics"},
 	{"XMP ", "Extensible Metadata Platform"},
 }
 
 var Statuses = [...]string{
 	// Notes.
+	`"@I/O redirect"`,
 	`"@end of data"`,
 	`"@metadata reported"`,
 
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index fecdc96..8ec9186 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -189,6 +189,7 @@
 
 } wuffs_base__status;
 
+extern const char* wuffs_base__note__i_o_redirect;
 extern const char* wuffs_base__note__end_of_data;
 extern const char* wuffs_base__note__metadata_reported;
 extern const char* wuffs_base__suspension__even_more_information;
@@ -318,6 +319,12 @@
 // International Color Consortium Profile.
 #define WUFFS_BASE__FOURCC__ICCP 0x49434350
 
+// Joint Photographic Experts Group.
+#define WUFFS_BASE__FOURCC__JPEG 0x4A504547
+
+// Portable Network Graphics.
+#define WUFFS_BASE__FOURCC__PNG 0x504E4720
+
 // Extensible Metadata Platform.
 #define WUFFS_BASE__FOURCC__XMP 0x584D5020
 
@@ -4832,6 +4839,8 @@
     uint64_t f_bytes_per_row;
     uint64_t f_bytes_total;
     wuffs_base__pixel_format f_pixfmt;
+    uint32_t f_io_redirect_fourcc;
+    uint64_t f_io_redirect_pos;
     uint32_t f_padding;
     uint32_t f_mask_r;
     uint32_t f_mask_g;
@@ -4856,6 +4865,7 @@
   struct {
     struct {
       uint32_t v_bitmap_info_len;
+      uint32_t v_bits_per_pixel;
       uint32_t v_compression;
       uint64_t scratch;
     } s_decode_image_config[1];
@@ -7922,6 +7932,7 @@
     0x08, 0x0A, 0x0C, 0x10, 0x18, 0x20, 0x30, 0x40,
 };
 
+const char* wuffs_base__note__i_o_redirect = "@base: I/O redirect";
 const char* wuffs_base__note__end_of_data = "@base: end of data";
 const char* wuffs_base__note__metadata_reported = "@base: metadata reported";
 const char* wuffs_base__suspension__even_more_information =
@@ -8183,7 +8194,7 @@
     v &= 0x0F;
 
     // UINT64_MAX is 18446744073709551615, which is ((10 * max10) + max1).
-    const uint64_t max10 = 1844674407370955161;
+    const uint64_t max10 = 1844674407370955161u;
     const uint8_t max1 = 5;
 
     for (; p < q; p++) {
@@ -12127,14 +12138,20 @@
   if (coro_susp_point) {
     v_bitmap_info_len =
         self->private_data.s_decode_image_config[0].v_bitmap_info_len;
+    v_bits_per_pixel =
+        self->private_data.s_decode_image_config[0].v_bits_per_pixel;
     v_compression = self->private_data.s_decode_image_config[0].v_compression;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
-    if (self->private_impl.f_call_sequence != 0) {
+    if ((self->private_impl.f_call_sequence != 0) ||
+        (self->private_impl.f_io_redirect_fourcc == 1)) {
       status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
       goto exit;
+    } else if (self->private_impl.f_io_redirect_fourcc != 0) {
+      status = wuffs_base__make_status(wuffs_base__note__i_o_redirect);
+      goto ok;
     }
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
@@ -12218,6 +12235,10 @@
       goto exit;
     }
     self->private_impl.f_padding -= 14;
+    self->private_impl.f_io_redirect_pos = wuffs_base__u64__sat_add(
+        ((uint64_t)(self->private_impl.f_padding)),
+        wuffs_base__u64__sat_add(a_src->meta.pos,
+                                 ((uint64_t)(iop_a_src - io0_a_src))));
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
       uint32_t t_2;
@@ -12401,27 +12422,6 @@
       }
       v_bits_per_pixel = t_6;
     }
-    if (v_bits_per_pixel == 24) {
-      self->private_impl.f_bits_per_pixel = 24;
-      self->private_impl.f_bytes_per_row =
-          ((((((uint64_t)(self->private_impl.f_width)) * 3) + 3) >> 2) << 2);
-      self->private_impl.f_pad_per_row = (self->private_impl.f_width & 3);
-      self->private_impl.f_pixfmt =
-          wuffs_base__utility__make_pixel_format(2147485832);
-    } else if (v_bits_per_pixel == 32) {
-      self->private_impl.f_bits_per_pixel = 32;
-      self->private_impl.f_bytes_per_row =
-          (((uint64_t)(self->private_impl.f_width)) * 4);
-      self->private_impl.f_pad_per_row = 0;
-      self->private_impl.f_pixfmt =
-          wuffs_base__utility__make_pixel_format(2164295816);
-    } else {
-      status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file);
-      goto exit;
-    }
-    self->private_impl.f_bytes_total =
-        (self->private_impl.f_bytes_per_row *
-         ((uint64_t)(self->private_impl.f_height)));
     {
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16);
       uint32_t t_7;
@@ -12453,6 +12453,39 @@
       }
       v_compression = t_7;
     }
+    if (v_bits_per_pixel == 0) {
+      if (v_compression == 4) {
+        self->private_impl.f_io_redirect_fourcc = 1246774599;
+        status = wuffs_base__make_status(wuffs_base__note__i_o_redirect);
+        goto ok;
+      } else if (v_compression == 5) {
+        self->private_impl.f_io_redirect_fourcc = 1347307296;
+        status = wuffs_base__make_status(wuffs_base__note__i_o_redirect);
+        goto ok;
+      }
+      status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file);
+      goto exit;
+    } else if (v_bits_per_pixel == 24) {
+      self->private_impl.f_bits_per_pixel = 24;
+      self->private_impl.f_bytes_per_row =
+          ((((((uint64_t)(self->private_impl.f_width)) * 3) + 3) >> 2) << 2);
+      self->private_impl.f_pad_per_row = (self->private_impl.f_width & 3);
+      self->private_impl.f_pixfmt =
+          wuffs_base__utility__make_pixel_format(2147485832);
+    } else if (v_bits_per_pixel == 32) {
+      self->private_impl.f_bits_per_pixel = 32;
+      self->private_impl.f_bytes_per_row =
+          (((uint64_t)(self->private_impl.f_width)) * 4);
+      self->private_impl.f_pad_per_row = 0;
+      self->private_impl.f_pixfmt =
+          wuffs_base__utility__make_pixel_format(2164295816);
+    } else {
+      status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file);
+      goto exit;
+    }
+    self->private_impl.f_bytes_total =
+        (self->private_impl.f_bytes_per_row *
+         ((uint64_t)(self->private_impl.f_height)));
     self->private_data.s_decode_image_config[0].scratch = 20;
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(18);
     if (self->private_data.s_decode_image_config[0].scratch >
@@ -12636,6 +12669,8 @@
       wuffs_base__status__is_suspension(&status) ? 1 : 0;
   self->private_data.s_decode_image_config[0].v_bitmap_info_len =
       v_bitmap_info_len;
+  self->private_data.s_decode_image_config[0].v_bits_per_pixel =
+      v_bits_per_pixel;
   self->private_data.s_decode_image_config[0].v_compression = v_compression;
 
   goto exit;
@@ -13268,8 +13303,17 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
-  status = wuffs_base__make_status(wuffs_base__error__no_more_information);
-  goto exit;
+  if (self->private_impl.f_io_redirect_fourcc <= 1) {
+    status = wuffs_base__make_status(wuffs_base__error__no_more_information);
+    goto exit;
+  }
+  if (a_minfo != NULL) {
+    wuffs_base__more_information__set(
+        a_minfo, 1, self->private_impl.f_io_redirect_fourcc, 0,
+        self->private_impl.f_io_redirect_pos, 18446744073709551615u);
+  }
+  self->private_impl.f_io_redirect_fourcc = 1;
+
   goto ok;
 ok:
   goto exit;
diff --git a/std/bmp/decode_bmp.wuffs b/std/bmp/decode_bmp.wuffs
index 5af01c6..61b5852 100644
--- a/std/bmp/decode_bmp.wuffs
+++ b/std/bmp/decode_bmp.wuffs
@@ -32,6 +32,9 @@
 	bytes_total    : base.u64[..= 0xFFFF_FFFC_0000_0004],  // 4 * 0x7FFF_FFFF * 0x7FFF_FFFF
 	pixfmt         : base.pixel_format,
 
+	io_redirect_fourcc : base.u32,
+	io_redirect_pos    : base.u64,
+
 	padding : base.u32,
 
 	mask_r : base.u32,
@@ -66,8 +69,10 @@
 	var bits_per_pixel  : base.u32
 	var compression     : base.u32
 
-	if this.call_sequence <> 0 {
+	if (this.call_sequence <> 0) or (this.io_redirect_fourcc == 1) {
 		return base."#bad call sequence"
+	} else if this.io_redirect_fourcc <> 0 {
+		return base."@I/O redirect"
 	}
 
 	// Read the BITMAPFILEHEADER (14 bytes).
@@ -84,6 +89,7 @@
 		return "#bad header"
 	}
 	this.padding -= 14
+	this.io_redirect_pos = (this.padding as base.u64) ~sat+ args.src.position()
 
 	// Read the BITMAPINFOHEADER (version 3 / 4 / 5 is 40 / 108 / 124 bytes).
 
@@ -120,7 +126,17 @@
 	}
 
 	bits_per_pixel = args.src.read_u16le_as_u32?()
-	if bits_per_pixel == 24 {
+	compression = args.src.read_u32le?()
+	if bits_per_pixel == 0 {
+		if compression == 4 {
+			this.io_redirect_fourcc = 'JPEG'be
+			return base."@I/O redirect"
+		} else if compression == 5 {
+			this.io_redirect_fourcc = 'PNG 'be
+			return base."@I/O redirect"
+		}
+		return "#unsupported BMP file"
+	} else if bits_per_pixel == 24 {
 		this.bits_per_pixel = 24
 		// 3 bytes per pixel, but row lengths are rounded up to multiples of 4.
 		// The "((x + 3) >> 2) << 2" dance rounds x up.
@@ -142,8 +158,6 @@
 	}
 	this.bytes_total = this.bytes_per_row * (this.height as base.u64)
 
-	compression = args.src.read_u32le?()
-
 	// We've already read 20 bytes from the BITMAPINFOHEADER: size (4), width
 	// (4), height (4), planes (2), bpp (2), compression (4). Skip the rest of
 	// the version 3 BITMAPINFOHEADER (whose total size is 40).
@@ -454,7 +468,20 @@
 }
 
 pub func decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) {
-	return base."#no more information"
+	if this.io_redirect_fourcc <= 1 {
+		return base."#no more information"
+	}
+	if args.minfo <> nullptr {
+		args.minfo.set!(
+			flavor: 1,  // WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT
+			w: this.io_redirect_fourcc,
+			x: 0,
+			y: this.io_redirect_pos,
+			z: 0xFFFF_FFFF_FFFF_FFFF)
+	}
+	// Setting io_redirect_fourcc to a dummy value of 1 will cause future calls
+	// to return an error.
+	this.io_redirect_fourcc = 1
 }
 
 pub func decoder.workbuf_len() base.range_ii_u64 {
diff --git a/test/c/std/bmp.c b/test/c/std/bmp.c
index bb6a6e9..1e20e2f 100644
--- a/test/c/std/bmp.c
+++ b/test/c/std/bmp.c
@@ -112,6 +112,58 @@
   return NULL;
 }
 
+const char*  //
+test_wuffs_bmp_decode_io_redirect() {
+  CHECK_FOCUS(__func__);
+  wuffs_bmp__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_bmp__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = g_src_slice_u8,
+  });
+  CHECK_STRING(read_file(&src, "test/data/rgb24png.bmp"));
+  if (src.meta.wi != 1210) {
+    RETURN_FAIL("file size: have %zu, want 1210", src.meta.wi);
+  }
+
+  wuffs_base__status status =
+      wuffs_bmp__decoder__decode_image_config(&dec, NULL, &src);
+  if (status.repr != wuffs_base__note__i_o_redirect) {
+    RETURN_FAIL("decode_image_config: have \"%s\", want \"%s\"", status.repr,
+                wuffs_base__note__i_o_redirect);
+  }
+
+  wuffs_base__io_buffer empty = wuffs_base__empty_io_buffer();
+  wuffs_base__more_information minfo = wuffs_base__empty_more_information();
+  CHECK_STATUS("tell_me_more",
+               wuffs_bmp__decoder__tell_me_more(&dec, &empty, &minfo, &src));
+  if (minfo.flavor != WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT) {
+    RETURN_FAIL("flavor: have %" PRIu32 ", want %" PRIu32, minfo.flavor,
+                WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT);
+  }
+
+  uint32_t have_fourcc =
+      wuffs_base__more_information__io_redirect__fourcc(&minfo);
+  if (have_fourcc != WUFFS_BASE__FOURCC__PNG) {
+    RETURN_FAIL("fourcc: have 0x%08" PRIX32 ", want 0x%08" PRIX32, have_fourcc,
+                WUFFS_BASE__FOURCC__PNG);
+  }
+
+  wuffs_base__range_ie_u64 have_range =
+      wuffs_base__more_information__io_redirect__range(&minfo);
+  if (have_range.min_incl != 138) {
+    RETURN_FAIL("range.min_incl: have %" PRIu64 ", want 138",
+                have_range.min_incl);
+  } else if (have_range.max_excl < 1210) {
+    RETURN_FAIL("range.max_excl: have %" PRIu64 ", want >= 1210",
+                have_range.max_excl);
+  }
+  return NULL;
+}
+
   // ---------------- Mimic Tests
 
 #ifdef WUFFS_MIMIC
@@ -138,6 +190,7 @@
 
     test_wuffs_bmp_decode_frame_config,
     test_wuffs_bmp_decode_interface,
+    test_wuffs_bmp_decode_io_redirect,
 
 #ifdef WUFFS_MIMIC
 
diff --git a/test/data/README.md b/test/data/README.md
index 156c9d7..94dc070 100644
--- a/test/data/README.md
+++ b/test/data/README.md
@@ -101,6 +101,9 @@
 6901 "JavaScript Object Notation (JSON)
 Pointer"](https://tools.ietf.org/rfc/rfc6901.txt) specification.
 
+`rgb24png.bmp` comes from [BMP Suite](https://github.com/jsummers/bmpsuite),
+which states that its generated images "are in the public domain".
+
 `romeo.txt` is an excerpt of Shakespeare's "Romeo and Juliet", copied from
 [shakespeare.mit.edu](http://shakespeare.mit.edu/romeo_juliet/romeo_juliet.2.2.html).
 
diff --git a/test/data/rgb24png.bmp b/test/data/rgb24png.bmp
new file mode 100644
index 0000000..e87ec7a
--- /dev/null
+++ b/test/data/rgb24png.bmp
Binary files differ