diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 317efd320e749fb4c60e2d5ab36a044d3543a699..eee04434b68e472a04ece80e4473d2dd2a2859af 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -779,13 +779,13 @@ GLMapTexture_t *HWR_GetTexture(INT32 tex)
 {
 	GLMapTexture_t *grtex;
 #ifdef PARANOIA
-	if ((unsigned)tex >= gl_numtextures)
-		I_Error("HWR_GetTexture: tex >= numtextures\n");
+	if (tex < 1 || (unsigned)tex > gl_numtextures)
+		I_Error("HWR_GetTexture: tex > numtextures\n");
 #endif
 
 	// Every texture in memory, stored in the
 	// hardware renderer's bit depth format. Wow!
-	grtex = &gl_textures[tex];
+	grtex = &gl_textures[tex - 1];
 
 	// Generate texture if missing from the cache
 	if (!grtex->mipmap.data && !grtex->mipmap.downloaded)
@@ -901,8 +901,8 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		GLMapTexture_t *grtex;
 		INT32 texturenum = levelflat->u.texture.num;
 #ifdef PARANOIA
-		if ((unsigned)texturenum >= gl_numtextures)
-			I_Error("HWR_GetLevelFlat: texturenum >= numtextures");
+		if (texturenum < 1 || (unsigned)texturenum > gl_numtextures)
+			I_Error("HWR_GetLevelFlat: texturenum > numtextures");
 #endif
 
 		// Who knows?
@@ -910,7 +910,7 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 			return;
 
 		// Every texture in memory, stored as a 8-bit flat. Wow!
-		grtex = &gl_flats[texturenum];
+		grtex = &gl_flats[texturenum - 1];
 
 		// Generate flat if missing from the cache
 		if (!grtex->mipmap.data && !grtex->mipmap.downloaded)
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index d2982afe44eb1df1812e82a79eab25de87f0b31e..1cd8a1ccb7501302b8b1e2fb7e39b39131d5c3ae 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1660,6 +1660,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
 				}
 
+				if (!texnum)
+					continue;
+
 				h  = P_GetFFloorTopZAt   (rover, v1x, v1y);
 				hS = P_GetFFloorTopZAt   (rover, v2x, v2y);
 				l  = P_GetFFloorBottomZAt(rover, v1x, v1y);
@@ -1816,6 +1819,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					newline = rover->master->frontsector->lines[0] + linenum;
 					texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
 				}
+
+				if (!texnum)
+					continue;
+
 				h  = P_GetFFloorTopZAt   (rover, v1x, v1y);
 				hS = P_GetFFloorTopZAt   (rover, v2x, v2y);
 				l  = P_GetFFloorBottomZAt(rover, v1x, v1y);
diff --git a/src/r_data.c b/src/r_data.c
index 2cfe9cb7ace3139d40a32a9d8dd0ba21afa9b794..c9023b5dc401913757fe521ed542b1f5379062dc 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -1254,14 +1254,17 @@ void R_PrecacheLevel(void)
 	texturepresent = calloc(numtextures, sizeof (*texturepresent));
 	if (texturepresent == NULL) I_Error("%s: Out of memory looking up textures", "R_PrecacheLevel");
 
+	// offset by 1 for index from 1
+	texturepresent--;
+
 	for (j = 0; j < numsides; j++)
 	{
-		// huh, a potential bug here????
-		if (sides[j].toptexture >= 0 && sides[j].toptexture < numtextures)
+		// check if any of these textures are valid
+		if (sides[j].toptexture > 0 && sides[j].toptexture <= numtextures)
 			texturepresent[sides[j].toptexture] = 1;
-		if (sides[j].midtexture >= 0 && sides[j].midtexture < numtextures)
+		if (sides[j].midtexture > 0 && sides[j].midtexture <= numtextures)
 			texturepresent[sides[j].midtexture] = 1;
-		if (sides[j].bottomtexture >= 0 && sides[j].bottomtexture < numtextures)
+		if (sides[j].bottomtexture > 0 && sides[j].bottomtexture <= numtextures)
 			texturepresent[sides[j].bottomtexture] = 1;
 	}
 
@@ -1271,7 +1274,7 @@ void R_PrecacheLevel(void)
 	texturepresent[skytexture] = 1;
 
 	texturememory = 0;
-	for (j = 0; j < (unsigned)numtextures; j++)
+	for (j = 1; j <= (unsigned)numtextures; j++)
 	{
 		if (!texturepresent[j])
 			continue;
@@ -1281,7 +1284,7 @@ void R_PrecacheLevel(void)
 		// pre-caching individual patches that compose textures became obsolete,
 		// since we cache entire composite textures
 	}
-	free(texturepresent);
+	free(&texturepresent[1]); // offset from 1 points back to original memory
 
 	//
 	// Precache sprites.
diff --git a/src/r_picformats.c b/src/r_picformats.c
index 5c81d1e02186902818415c84088fcfc182235681..2694a4302826c0fd3a93412b6e092177a087398e 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -753,7 +753,7 @@ void *Picture_TextureToFlat(size_t trickytex)
 	UINT8 *desttop, *dest, *deststop;
 	UINT8 *source;
 
-	if (trickytex >= (unsigned)numtextures)
+	if (trickytex < 1 || trickytex > (unsigned)numtextures)
 		I_Error("Picture_TextureToFlat: invalid texture number!");
 
 	// Check the texture cache
diff --git a/src/r_segs.c b/src/r_segs.c
index 157cf466e6f05385ef324f5fdf87ce57e76245c0..5deec24f2e931793f0aa259d434e046811359096 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -152,7 +152,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	windowbottom = windowtop = sprbotscreen = INT32_MAX;
 
 	ldef = curline->linedef;
-	if (!ldef->alpha)
+	if (!texnum || !ldef->alpha)
 		return;
 
 	if (ldef->blendmode == AST_FOG)
@@ -602,6 +602,9 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
 	}
 
+	if (!texnum)
+		return;
+
 	if (pfloor->flags & FF_TRANSLUCENT)
 	{
 		boolean fuzzy = true;
@@ -2227,7 +2230,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 			ds_p->numthicksides = numthicksides = i;
 		}
-		if (sidedef->midtexture > 0 && sidedef->midtexture < numtextures)
+		if (sidedef->midtexture > 0 && sidedef->midtexture <= numtextures)
 		{
 			// masked midtexture
 			if (!ds_p->thicksidecol)
@@ -2759,12 +2762,12 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	if (maskedtexture && !(ds_p->silhouette & SIL_TOP))
 	{
 		ds_p->silhouette |= SIL_TOP;
-		ds_p->tsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MIN: INT32_MAX;
+		ds_p->tsilheight = (sidedef->midtexture > 0 && sidedef->midtexture <= numtextures) ? INT32_MIN: INT32_MAX;
 	}
 	if (maskedtexture && !(ds_p->silhouette & SIL_BOTTOM))
 	{
 		ds_p->silhouette |= SIL_BOTTOM;
-		ds_p->bsilheight = (sidedef->midtexture > 0 && sidedef->midtexture < numtextures) ? INT32_MAX: INT32_MIN;
+		ds_p->bsilheight = (sidedef->midtexture > 0 && sidedef->midtexture <= numtextures) ? INT32_MAX: INT32_MIN;
 	}
 	ds_p++;
 }
diff --git a/src/r_textures.c b/src/r_textures.c
index 793e5237f62e64e937c32f97d4d01bbb08a6c065..dc714073ed1084339ef4846c7adcfa24295f4656 100644
--- a/src/r_textures.c
+++ b/src/r_textures.c
@@ -495,7 +495,7 @@ UINT8 *R_GenerateTextureAsFlat(size_t texnum)
 //
 INT32 R_GetTextureNum(INT32 texnum)
 {
-	if (texnum < 0 || texnum >= numtextures)
+	if (texnum < 1 || texnum > numtextures)
 		return 0;
 	return texturetranslation[texnum];
 }
@@ -709,7 +709,7 @@ void R_FlushTextureCache(void)
 	INT32 i;
 
 	if (numtextures)
-		for (i = 0; i < numtextures; i++)
+		for (i = 1; i <= numtextures; i++)
 			Z_Free(texturecache[i]);
 }
 
@@ -939,13 +939,14 @@ void R_LoadTextures(void)
 	// Free previous memory before numtextures change.
 	if (numtextures)
 	{
-		for (i = 0; i < numtextures; i++)
+		for (i = 1; i <= numtextures; i++)
 		{
 			Z_Free(textures[i]);
 			Z_Free(texturecache[i]);
 		}
-		Z_Free(texturetranslation);
-		Z_Free(textures);
+		// these are offset by 1; free the original memory
+		Z_Free(&texturetranslation[1]);
+		Z_Free(&textures[1]);
 	}
 
 	// Load patches and textures.
@@ -1033,6 +1034,7 @@ void R_LoadTextures(void)
 	// Allocate memory and initialize to 0 for all the textures we are initialising.
 	// There are actually 5 buffers allocated in one for convenience.
 	textures = Z_Calloc((numtextures * sizeof(void *)) * 5, PU_STATIC, NULL);
+	textures--; // offset by 1 for index from 1
 
 	// Allocate texture column offset table.
 	texturecolumnofs = (void *)((UINT8 *)textures + (numtextures * sizeof(void *)));
@@ -1043,12 +1045,13 @@ void R_LoadTextures(void)
 	// Allocate texture height table.
 	textureheight    = (void *)((UINT8 *)textures + ((numtextures * sizeof(void *)) * 4));
 	// Create translation table for global animation.
-	texturetranslation = Z_Malloc((numtextures + 1) * sizeof(*texturetranslation), PU_STATIC, NULL);
+	texturetranslation = Z_Malloc(numtextures * sizeof(*texturetranslation), PU_STATIC, NULL);
+	texturetranslation--; // offset by 1 for index from 1
 
-	for (i = 0; i < numtextures; i++)
+	for (i = 1; i <= numtextures; i++)
 		texturetranslation[i] = i;
 
-	for (i = 0, w = 0; w < numwadfiles; w++)
+	for (i = 1, w = 0; w < numwadfiles; w++)
 	{
 #ifdef WALLFLATS
 		i = Rloadflats(i, w);
@@ -1605,7 +1608,7 @@ INT32 R_CheckTextureNumForName(const char *name)
 
 	// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
 	//for (i = 0; i < numtextures; i++) <- old
-	for (i = (numtextures - 1); i >= 0; i--) // <- new
+	for (i = numtextures; i > 0; i--) // <- new
 		if (!strncasecmp(textures[i]->name, name, 8))
 		{
 			tidcachelen++;