From e688624f3f44919e9c171a83f6fb9e794ce4cc3d Mon Sep 17 00:00:00 2001
From: Claude <noreply@anthropic.com>
Date: Sat, 30 May 2026 09:40:44 +0000
Subject: [PATCH] pak readers: bounds-checked decode via a node_body_t cursor

Replace the raw char* pointer arithmetic over pak descriptor node
bodies with a self-contained node_body_t cursor that owns the buffer
and bounds-checks every read, so a malformed or hostile pak fatals
loudly instead of reading out of bounds.  decode_*() overloads
dispatch to the cursor's checked readers at the existing call sites.
---
 .../descriptor/reader/bridge_reader.cc        |  8 +-
 .../descriptor/reader/building_reader.cc      | 15 +---
 .../descriptor/reader/citycar_reader.cc       |  8 +-
 .../descriptor/reader/crossing_reader.cc      |  8 +-
 .../descriptor/reader/factory_reader.cc       | 45 +++-------
 .../descriptor/reader/good_reader.cc          |  8 +-
 .../descriptor/reader/groundobj_reader.cc     |  8 +-
 .../descriptor/reader/image_reader.cc         | 13 ++-
 .../descriptor/reader/imagelist2d_reader.cc   |  8 +-
 .../descriptor/reader/imagelist_reader.cc     |  8 +-
 src/simutrans/descriptor/reader/obj_reader.h  | 90 +++++++++++++++++++
 .../descriptor/reader/pedestrian_reader.cc    |  8 +-
 .../descriptor/reader/roadsign_reader.cc      |  8 +-
 .../descriptor/reader/sound_reader.cc         | 10 +--
 .../descriptor/reader/tree_reader.cc          |  8 +-
 .../descriptor/reader/tunnel_reader.cc        |  6 +-
 .../descriptor/reader/vehicle_reader.cc       |  8 +-
 .../descriptor/reader/way_obj_reader.cc       |  8 +-
 src/simutrans/descriptor/reader/way_reader.cc |  8 +-
 .../descriptor/reader/xref_reader.cc          | 15 ++--
 20 files changed, 148 insertions(+), 150 deletions(-)

diff --git a/src/simutrans/descriptor/reader/bridge_reader.cc b/src/simutrans/descriptor/reader/bridge_reader.cc
index b5135be35e..69d5a9343c 100644
--- a/src/simutrans/descriptor/reader/bridge_reader.cc
+++ b/src/simutrans/descriptor/reader/bridge_reader.cc
@@ -12,7 +12,6 @@
 #include "bridge_reader.h"
 #include "../obj_node_info.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 #include <inttypes.h>
 #include <stdio.h>
@@ -32,11 +31,8 @@ void bridge_reader_t::register_obj(obj_desc_t *&data)
 
 obj_desc_t *bridge_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/building_reader.cc b/src/simutrans/descriptor/reader/building_reader.cc
index dc62422e41..d1f52a1f53 100644
--- a/src/simutrans/descriptor/reader/building_reader.cc
+++ b/src/simutrans/descriptor/reader/building_reader.cc
@@ -12,7 +12,6 @@
 #include "../obj_node_info.h"
 #include "building_reader.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 /**
@@ -33,11 +32,8 @@ struct old_btyp
 
 obj_desc_t * tile_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the highest bit was always cleared.
@@ -218,11 +214,8 @@ bool building_reader_t::successfully_loaded() const
 
 obj_desc_t *building_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char * p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the highest bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/citycar_reader.cc b/src/simutrans/descriptor/reader/citycar_reader.cc
index 68821a789e..ceec5ccbd5 100644
--- a/src/simutrans/descriptor/reader/citycar_reader.cc
+++ b/src/simutrans/descriptor/reader/citycar_reader.cc
@@ -16,7 +16,6 @@
 
 #include "../../simdebug.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 void citycar_reader_t::register_obj(obj_desc_t *&data)
@@ -39,11 +38,8 @@ bool citycar_reader_t::successfully_loaded() const
 
 obj_desc_t * citycar_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/crossing_reader.cc b/src/simutrans/descriptor/reader/crossing_reader.cc
index 032e32ab8f..4fc7a29e2e 100644
--- a/src/simutrans/descriptor/reader/crossing_reader.cc
+++ b/src/simutrans/descriptor/reader/crossing_reader.cc
@@ -15,7 +15,6 @@
 
 #include "../../simdebug.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 void crossing_reader_t::register_obj(obj_desc_t *&data)
@@ -33,11 +32,8 @@ void crossing_reader_t::register_obj(obj_desc_t *&data)
 
 obj_desc_t * crossing_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/factory_reader.cc b/src/simutrans/descriptor/reader/factory_reader.cc
index 4a4d89a608..7ab8b6f575 100644
--- a/src/simutrans/descriptor/reader/factory_reader.cc
+++ b/src/simutrans/descriptor/reader/factory_reader.cc
@@ -12,7 +12,6 @@
 #include "../factory_desc.h"
 #include "../xref_desc.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 #include "factory_reader.h"
 
@@ -41,11 +40,8 @@ uint16 rescale_probability(const uint16 p)
 
 obj_desc_t *factory_field_class_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	uint16 v = decode_uint16(p);
 	field_class_desc_t *desc = new field_class_desc_t();
@@ -74,11 +70,8 @@ obj_desc_t *factory_field_class_reader_t::read_node(FILE *fp, obj_node_info_t &n
 
 obj_desc_t *factory_field_group_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	uint16 v = decode_uint16(p);
 	field_group_desc_t *desc = new field_group_desc_t();
@@ -168,11 +161,8 @@ void factory_field_group_reader_t::register_obj(obj_desc_t *&data)
 
 obj_desc_t *factory_smoke_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	sint16 x = decode_sint16(p);
 	sint16 y = decode_sint16(p);
@@ -194,16 +184,11 @@ obj_desc_t *factory_smoke_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 
 obj_desc_t *factory_supplier_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
-
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
-
 	const uint16 v = decode_uint16(p);
 	const int version = v & 0x8000 ? v & 0x7FFF : 0;
 
@@ -232,11 +217,8 @@ obj_desc_t *factory_supplier_reader_t::read_node(FILE *fp, obj_node_info_t &node
 
 obj_desc_t *factory_product_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
@@ -270,11 +252,8 @@ obj_desc_t *factory_product_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 
 obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/good_reader.cc b/src/simutrans/descriptor/reader/good_reader.cc
index 1a23f29e3a..7f77ade4c0 100644
--- a/src/simutrans/descriptor/reader/good_reader.cc
+++ b/src/simutrans/descriptor/reader/good_reader.cc
@@ -11,7 +11,6 @@
 #include "../obj_node_info.h"
 #include "../goods_desc.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 void goods_reader_t::register_obj(obj_desc_t *&data)
@@ -37,11 +36,8 @@ bool goods_reader_t::successfully_loaded() const
 
 obj_desc_t * goods_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/groundobj_reader.cc b/src/simutrans/descriptor/reader/groundobj_reader.cc
index f535169142..5fc41eda08 100644
--- a/src/simutrans/descriptor/reader/groundobj_reader.cc
+++ b/src/simutrans/descriptor/reader/groundobj_reader.cc
@@ -15,7 +15,6 @@
 #include "../obj_node_info.h"
 #include "groundobj_reader.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 #include <cinttypes>
 
@@ -45,11 +44,8 @@ bool groundobj_reader_t::successfully_loaded() const
 
 obj_desc_t *groundobj_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the highest bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/image_reader.cc b/src/simutrans/descriptor/reader/image_reader.cc
index 6df9ade7eb..bfd22da6f8 100644
--- a/src/simutrans/descriptor/reader/image_reader.cc
+++ b/src/simutrans/descriptor/reader/image_reader.cc
@@ -16,7 +16,6 @@
 
 #include <zlib.h>
 #include "../../tpl/inthashtable_tpl.h"
-#include "../../tpl/array_tpl.h"
 
 
 // if without graphics backend, do not copy any pixel
@@ -28,16 +27,14 @@
 
 obj_desc_t *image_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin()+6;
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
+	p.seek(6);
 	// always zero in old version, since length was always less than 65535
 	// because a node could not hold more data
 	uint8 version = decode_uint8(p);
-	p = desc_buf.begin();
+	p.seek(0);
 
 #if COLOUR_DEPTH != 0
 	image_t *desc = new image_t();
@@ -60,7 +57,7 @@ obj_desc_t *image_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		//PAKSET_INFO("image_t::read_node()","x,y=%d,%d  w,h=%d,%d, len=%i",desc->x,desc->y,desc->w,desc->h, desc->len);
 
 		uint16* dest = desc->data;
-		p = desc_buf.begin()+12;
+		p.seek(12);
 
 		if (desc->h > 0) {
 			for (uint i = 0; i < desc->len; i++) {
diff --git a/src/simutrans/descriptor/reader/imagelist2d_reader.cc b/src/simutrans/descriptor/reader/imagelist2d_reader.cc
index 8487bd6726..91cb8e360f 100644
--- a/src/simutrans/descriptor/reader/imagelist2d_reader.cc
+++ b/src/simutrans/descriptor/reader/imagelist2d_reader.cc
@@ -10,16 +10,12 @@
 
 #include "imagelist2d_reader.h"
 #include "../obj_node_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 obj_desc_t * imagelist2d_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	image_array_t *desc = new image_array_t();
 	desc->count = decode_uint16(p);
diff --git a/src/simutrans/descriptor/reader/imagelist_reader.cc b/src/simutrans/descriptor/reader/imagelist_reader.cc
index a5ca577627..b72ca3c44d 100644
--- a/src/simutrans/descriptor/reader/imagelist_reader.cc
+++ b/src/simutrans/descriptor/reader/imagelist_reader.cc
@@ -10,16 +10,12 @@
 
 #include "imagelist_reader.h"
 #include "../obj_node_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 obj_desc_t * imagelist_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	image_list_t *desc = new image_list_t();
 	desc->count = decode_uint16(p);
diff --git a/src/simutrans/descriptor/reader/obj_reader.h b/src/simutrans/descriptor/reader/obj_reader.h
index 8893b42a1e..891e838509 100644
--- a/src/simutrans/descriptor/reader/obj_reader.h
+++ b/src/simutrans/descriptor/reader/obj_reader.h
@@ -13,6 +13,7 @@
 #include "../objversion.h"
 #include "../../simdebug.h"
 #include "../../simtypes.h"
+#include "../../tpl/array_tpl.h"
 #include "../../dataobj/pakset_manager.h"
 
 
@@ -96,6 +97,95 @@ inline uint64 decode_uint64(char *&data)
 		static classname the_instance
 
 
+/// Owns one pak node's bytes and a bounds-checked cursor over them.
+/// decode_*(node_body_t&) overloads read through it.
+class node_body_t
+{
+public:
+	/// fread @p size bytes from @p fp; short read leaves a bool-false cursor.
+	node_body_t(FILE* fp, size_t size, const char* type_name)
+		: buf_(size),
+		  pos_(NULL),
+		  type_name_(type_name)
+	{
+		if (size > 0 && fread(buf_.begin(), size, 1, fp) != 1) {
+			buf_.clear();
+			return;
+		}
+		pos_ = buf_.begin();
+	}
+
+	/// false on short fread.
+	explicit operator bool() const { return pos_ != NULL; }
+
+	/// Bounds-checked absolute seek from the buffer start.
+	void seek(size_t off)
+	{
+		if (buf_.begin() + off > buf_.end()) {
+			dbg->fatal(type_name_,
+				"seek to offset %zu past buffer end %zu",
+				off, (size_t)buf_.get_count());
+		}
+		pos_ = buf_.begin() + off;
+	}
+
+	/// Bounds-checked `p += n`.
+	node_body_t& operator+=(size_t n) { require(n); pos_ += n; return *this; }
+
+	/// Bounds-checked single-byte skip (`p++`); return value unused.
+	node_body_t& operator++(int) { require(1); ++pos_; return *this; }
+
+	/// Bounds-checked: returns the cursor, then advances @p n bytes (tail reads).
+	char* read_bytes(size_t n) { require(n); char* p = pos_; pos_ += n; return p; }
+
+	uint8 read_uint8()   { require(1); return (uint8)*pos_++; }
+	uint16 read_uint16() { require(2); uint16 v = (uint16)(uint8)pos_[0] | (uint16)(uint8)pos_[1] << 8; pos_ += 2; return v; }
+	uint32 read_uint32()
+	{
+		require(4);
+		uint32 v =
+			(uint32)(uint8)pos_[0]       |
+			(uint32)(uint8)pos_[1] <<  8 |
+			(uint32)(uint8)pos_[2] << 16 |
+			(uint32)(uint8)pos_[3] << 24;
+		pos_ += 4;
+		return v;
+	}
+	uint64 read_uint64()
+	{
+		require(8);
+		uint64 v = 0;
+		for (int i = 0; i < 8; ++i) {
+			v |= (uint64)(uint8)pos_[i] << (i * 8);
+		}
+		pos_ += 8;
+		return v;
+	}
+
+private:
+	void require(size_t n) const
+	{
+		if (pos_ + n > buf_.end()) {
+			dbg->fatal(type_name_,
+				"short read at offset %zu of %zu: need %zu, have %zu",
+				(size_t)(pos_ - buf_.begin()), (size_t)buf_.get_count(),
+				n, (size_t)(buf_.end() - pos_));
+		}
+	}
+
+	array_tpl<char> buf_;
+	char* pos_;
+	const char* type_name_;
+};
+
+/// decode_*(node_body_t&) overloads, chosen over the char*& ones by
+/// argument type so reader call sites stay `decode_uint16(p)`.
+inline uint8  decode_uint8(node_body_t& b)  { return b.read_uint8(); }
+inline uint16 decode_uint16(node_body_t& b) { return b.read_uint16(); }
+inline uint32 decode_uint32(node_body_t& b) { return b.read_uint32(); }
+inline uint64 decode_uint64(node_body_t& b) { return b.read_uint64(); }
+
+
 class obj_reader_t
 {
 public:
diff --git a/src/simutrans/descriptor/reader/pedestrian_reader.cc b/src/simutrans/descriptor/reader/pedestrian_reader.cc
index 7eccbde7bc..161fbeb24b 100644
--- a/src/simutrans/descriptor/reader/pedestrian_reader.cc
+++ b/src/simutrans/descriptor/reader/pedestrian_reader.cc
@@ -12,7 +12,6 @@
 
 #include "pedestrian_reader.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 void pedestrian_reader_t::register_obj(obj_desc_t *&data)
@@ -39,11 +38,8 @@ bool pedestrian_reader_t::successfully_loaded() const
  */
 obj_desc_t * pedestrian_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/roadsign_reader.cc b/src/simutrans/descriptor/reader/roadsign_reader.cc
index 48f48731de..c0933cea3e 100644
--- a/src/simutrans/descriptor/reader/roadsign_reader.cc
+++ b/src/simutrans/descriptor/reader/roadsign_reader.cc
@@ -15,7 +15,6 @@
 
 #include "../../simdebug.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 #include <cinttypes>
 
@@ -40,11 +39,8 @@ bool roadsign_reader_t::successfully_loaded() const
 
 obj_desc_t *roadsign_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	const uint16 v = decode_uint16(p);
 	const int version = v & 0x8000 ? v & 0x7FFF : 0;
diff --git a/src/simutrans/descriptor/reader/sound_reader.cc b/src/simutrans/descriptor/reader/sound_reader.cc
index 304ae7d7f6..59c6810eed 100644
--- a/src/simutrans/descriptor/reader/sound_reader.cc
+++ b/src/simutrans/descriptor/reader/sound_reader.cc
@@ -11,7 +11,6 @@
 #include "../obj_node_info.h"
 
 #include "../../simdebug.h"
-#include "../../tpl/array_tpl.h"
 
 
 void sound_reader_t::register_obj(obj_desc_t *&data)
@@ -25,11 +24,8 @@ void sound_reader_t::register_obj(obj_desc_t *&data)
 
 obj_desc_t * sound_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	const uint16 v = decode_uint16(p);
 	const int version = v & 0x8000 ? v & 0x7FFF : 0;
@@ -45,7 +41,7 @@ obj_desc_t * sound_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->nr = decode_uint16(p);
 		uint16 len = decode_uint16(p);
 		if(  len>0  ) {
-			desc->nr = desc->get_sound_id(p);
+			desc->nr = desc->get_sound_id(p.read_bytes(len));
 		}
 	}
 	else {
diff --git a/src/simutrans/descriptor/reader/tree_reader.cc b/src/simutrans/descriptor/reader/tree_reader.cc
index 5f7c065af5..651284b5f8 100644
--- a/src/simutrans/descriptor/reader/tree_reader.cc
+++ b/src/simutrans/descriptor/reader/tree_reader.cc
@@ -13,7 +13,6 @@
 #include "../obj_node_info.h"
 #include "tree_reader.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 void tree_reader_t::register_obj(obj_desc_t *&data)
@@ -35,11 +34,8 @@ bool tree_reader_t::successfully_loaded() const
 
 obj_desc_t * tree_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the highest bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/tunnel_reader.cc b/src/simutrans/descriptor/reader/tunnel_reader.cc
index 0573776cea..91837b8754 100644
--- a/src/simutrans/descriptor/reader/tunnel_reader.cc
+++ b/src/simutrans/descriptor/reader/tunnel_reader.cc
@@ -17,7 +17,6 @@
 
 #include "../../builder/tunnelbauer.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 #include <cinttypes>
 
@@ -67,12 +66,11 @@ obj_desc_t * tunnel_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		return desc;
 	}
 
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) {
 		delete desc;
 		return NULL;
 	}
-	char *p = desc_buf.begin();
 
 	const uint16 v = decode_uint16(p);
 	const uint16 version = v & 0x8000 ? v & 0x7FFF : 0;
diff --git a/src/simutrans/descriptor/reader/vehicle_reader.cc b/src/simutrans/descriptor/reader/vehicle_reader.cc
index cc90b7d682..339c1bab67 100644
--- a/src/simutrans/descriptor/reader/vehicle_reader.cc
+++ b/src/simutrans/descriptor/reader/vehicle_reader.cc
@@ -14,7 +14,6 @@
 #include "vehicle_reader.h"
 #include "../obj_node_info.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 void vehicle_reader_t::register_obj(obj_desc_t *&data)
@@ -37,11 +36,8 @@ bool vehicle_reader_t::successfully_loaded() const
 
 obj_desc_t *vehicle_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/way_obj_reader.cc b/src/simutrans/descriptor/reader/way_obj_reader.cc
index e897c15543..30a01373d9 100644
--- a/src/simutrans/descriptor/reader/way_obj_reader.cc
+++ b/src/simutrans/descriptor/reader/way_obj_reader.cc
@@ -13,7 +13,6 @@
 #include "way_obj_reader.h"
 #include "../obj_node_info.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 #include <cinttypes>
 
@@ -37,11 +36,8 @@ bool way_obj_reader_t::successfully_loaded() const
 
 obj_desc_t * way_obj_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
-	char *p = desc_buf.begin();
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	// old versions of PAK files have no version stamp.
 	// But we know, the higher most bit was always cleared.
diff --git a/src/simutrans/descriptor/reader/way_reader.cc b/src/simutrans/descriptor/reader/way_reader.cc
index 7b1290f9a6..d4826a7cf9 100644
--- a/src/simutrans/descriptor/reader/way_reader.cc
+++ b/src/simutrans/descriptor/reader/way_reader.cc
@@ -14,7 +14,6 @@
 #include "way_reader.h"
 #include "../obj_node_info.h"
 #include "../../network/pakset_info.h"
-#include "../../tpl/array_tpl.h"
 
 
 void way_reader_t::register_obj(obj_desc_t *&data)
@@ -38,12 +37,9 @@ bool way_reader_t::successfully_loaded() const
 
 obj_desc_t * way_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	array_tpl<char> desc_buf(node.size);
-	if (fread(desc_buf.begin(), node.size, 1, fp) != 1) {
-		return NULL;
-	}
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
-	char *p = desc_buf.begin();
 	way_desc_t *desc = new way_desc_t;
 
 	const uint16 version = node.size==0 ? 0 : decode_uint16(p)&0x7FFFu;
diff --git a/src/simutrans/descriptor/reader/xref_reader.cc b/src/simutrans/descriptor/reader/xref_reader.cc
index a8c97b7a2a..b4274a3849 100644
--- a/src/simutrans/descriptor/reader/xref_reader.cc
+++ b/src/simutrans/descriptor/reader/xref_reader.cc
@@ -4,6 +4,7 @@
  */
 
 #include <stdio.h>
+#include <string.h>
 #include "../../simdebug.h"
 #include "../xref_desc.h"
 #include "xref_reader.h"
@@ -13,22 +14,18 @@
 
 obj_desc_t *xref_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
-	char buf[4 + 1];
-	if (fread(buf, 1, 5, fp) != 5) {
+	if (node.size < 5) {
+		dbg->error("xref_reader_t::read_node", "node.size %u < 5", node.size);
 		return NULL;
 	}
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
 
 	const uint32 name_len = node.size - 4 - 1;
-	char *p = buf;
 	xref_desc_t* desc = new(name_len) xref_desc_t();
-
 	desc->type = static_cast<obj_type>(decode_uint32(p));
 	desc->fatal = (decode_uint8(p) != 0);
-
-	if (fread(desc->name, 1, name_len, fp) != name_len) {
-		delete desc;
-		return NULL;
-	}
+	memcpy(desc->name, p.read_bytes(name_len), name_len);
 
 //	PAKSET_INFO("xref_reader_t::read_node()", "%s",desc->get_text() );
 
