port Map icon packet from srb2classic #198

Merged
alufolie91 merged 8 commits from servericonpack into Saturn-Next 2025-09-17 23:40:57 +02:00
6 changed files with 513 additions and 80 deletions

No files matched your search

+283 -17
View File
@@ -16,6 +16,8 @@
#include <unistd.h> //for unlink
#endif
#include <zlib.h>
#include "i_time.h"
#include "i_net.h"
#include "i_system.h"
@@ -87,6 +89,17 @@ boolean serverrunning = false;
INT32 serverplayer = 0;
char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
#define MAP_ICON_REQUEST_FREQUENCY 8
static UINT8 map_icon_index; // amount of fragments we have retrieved
static UINT8 map_icon_needed; // amount of fragments we need to get the full data set
static INT32 map_icon_read; // amount of bytes we have read
static UINT32 map_icon_missing; // bitmask of fragments we haven't received yet
static tic_t map_icon_last_request; // last time we sent an icon request, to avoid flood
static int map_icon_request_count; // current count of icon requests sent
static UINT8 *map_icon_data;
static patch_t *map_icon;
plrinfo playerinfo[MAXPLAYERS];
SINT8 joinnode = 0; // used for CL_VIEWSERVER
@@ -1211,6 +1224,10 @@ static inline void CL_DrawConnectionStatus(void)
if (!menuactive) // menu already draws its own fade
V_DrawFadeScreen(0xFF00, 16); // force default
// reset the counter for mapicon
if (cl_mode != CL_VIEWSERVER)
map_icon_request_count = 0;
if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES && cl_mode != CL_CHECKFILES && cl_mode != CL_VIEWSERVER
#ifdef HAVE_CURL
&& cl_mode != CL_DOWNLOADHTTPFILES
@@ -1337,9 +1354,42 @@ static inline void CL_DrawConnectionStatus(void)
V_DrawThinString(12 + 80, 18, V_ALLOWLOWERCASE, va("%s", serverlist[joinnode].info.servername));
const char *map = va("%sP", serverlist[joinnode].info.mapname);
patch_t *current_map = W_LumpExists(map) ? W_CachePatchName(map, PU_CACHE) : W_CachePatchName("BLANKLVL", PU_CACHE);
V_DrawSmallScaledPatch(10, 18, 0, current_map);
if (map_icon == NULL)
{
const char *map = va("%sP", serverlist[joinnode].info.mapname);
patch_t *current_map = W_LumpExists(map) ? W_CachePatchName(map, PU_CACHE) : NULL;
// check first if we may have the map icon loaded already
if (current_map != NULL)
V_DrawSmallScaledPatch(10, 18, 0, current_map);
else if (map_icon_data != NULL && map_icon_request_count <= 5)
{
if (I_GetTime() - map_icon_last_request > MAP_ICON_REQUEST_FREQUENCY)
{
doomdata_t *netbuffer = DOOMCOM_DATA(doomcom);
netbuffer->packettype = PT_NEEDMAPICON;
// failed to retrieve map icon, request it again
HSendPacket(servernode, false, 0, 0);
map_icon_last_request = I_GetTime();
map_icon_request_count++;
}
V_DrawFill(10, 18, MAP_ICON_WIDTH / 2, MAP_ICON_HEIGHT / 2, 31);
char dots[4] = "...";
if ((I_GetTime() & 0xf) < 4)
dots[0] = '\0';
else if ((I_GetTime() & 0xf) < 8)
dots[1] = '\0';
else if ((I_GetTime() & 0xf) < 12)
dots[2] = '\0';
V_DrawString(10 + MAP_ICON_WIDTH / 4 - V_StringWidth("...", 0) / 2, 18 + MAP_ICON_HEIGHT / 4 - 3, 0, dots);
}
else
V_DrawSmallScaledPatch(10, 18, 0, W_CachePatchName("BLANKLVL", PU_CACHE));
}
else
V_DrawSmallScaledPatch(10, 18, 0, map_icon);
if (serverlist[joinnode].info.iszone)
V_DrawThinString(12 + 80, 38, V_ALLOWLOWERCASE, va("%s Zone", serverlist[joinnode].info.maptitle));
@@ -1705,6 +1755,83 @@ static void SV_SendPlayerInfo(INT32 node)
HSendPacket(node, false, 0, sizeof(plrinfo) * MSCOMPAT_MAXPLAYERS);
}
static void SV_SendMapIcon(INT32 node)
{
doomdata_t *netbuffer = DOOMCOM_DATA(doomcom);
const char *map = va("%sP", G_BuildMapName(gamemap));
if (!W_LumpExists(map) || (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU))
{
// send an empty icon to tell the client there is no map icon
netbuffer->packettype = PT_MAPICON;
UINT8 *icondata = netbuffer->u.mapicondata;
WRITEUINT8(icondata, 0x80);
HSendPacket(node, false, 0, 1);
return;
}
patch_t *current_map = W_CachePatchName(map, PU_CACHE);
if (current_map->width != MAP_ICON_WIDTH || current_map->height != MAP_ICON_HEIGHT)
{
// we only support 160x100 for now
netbuffer->packettype = PT_MAPICON;
UINT8 *icondata = netbuffer->u.mapicondata;
WRITEUINT8(icondata, 0x80);
HSendPacket(node, false, 0, 1);
return;
}
UINT8 *data = Z_Malloc(2 * current_map->width * current_map->height, PU_STATIC, NULL);
UINT8 *pixeldata = data + current_map->width * current_map->height;
R_PatchToPixels(current_map, pixeldata);
z_stream stream;
stream.zalloc = NULL;
stream.zfree = NULL;
stream.opaque = NULL;
if (deflateInit(&stream, Z_DEFAULT_COMPRESSION) != Z_OK)
{
CONS_Alert(CONS_WARNING, "Cannot compress map icon, not enough memory\n");
Z_Free(data);
return;
}
stream.next_in = pixeldata;
stream.avail_in = current_map->width * current_map->height;
stream.next_out = data;
stream.avail_out = current_map->width * current_map->height;
if (deflate(&stream, Z_FINISH) != Z_STREAM_END)
{
CONS_Alert(CONS_WARNING, "Cannot compress map icon, error occured during compression\n");
deflateEnd(&stream);
Z_Free(data);
return;
}
size_t count = stream.total_out;
deflateEnd(&stream);
size_t index = 0;
for (size_t i = 0; i < count; i += MAXPACKETLENGTH-BASEPACKETSIZE-1)
{
size_t size = min(MAXPACKETLENGTH-BASEPACKETSIZE-1, count-i);
UINT8 *icondata = netbuffer->u.mapicondata;
netbuffer->packettype = PT_MAPICON;
if (count-i <= MAXPACKETLENGTH-BASEPACKETSIZE-1)
WRITEUINT8(icondata, 0x80 | index);
else
WRITEUINT8(icondata, index);
index++;
WRITEMEM(icondata, &data[i], size);
HSendPacket(node, false, 0, size + 1);
}
Z_Free(data);
}
/** Sends a PT_SERVERCFG packet
*
* \param node The destination
@@ -2687,9 +2814,26 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
if (cl_mode == CL_VIEWSERVER)
{
if (key == KEY_ENTER || key == KEY_JOY1)
{
cl_mode = CL_CHECKFILES;
if (map_icon != NULL)
Patch_Free(map_icon);
map_icon = NULL;
if (map_icon_data != NULL)
Z_Free(map_icon_data);
map_icon_data = NULL;
}
else if (key == KEY_ESCAPE || key == KEY_JOY1+1)
{
cl_mode = CL_ABORTED;
if (map_icon != NULL)
Patch_Free(map_icon);
map_icon = NULL;
if (map_icon_data != NULL)
Z_Free(map_icon_data);
map_icon_data = NULL;
map_icon_request_count = 0;
}
}
// Only ESC and non-keyboard keys abort connection
@@ -2805,6 +2949,17 @@ static void CL_ConnectToServer(void)
serverlist[i].info.version, serverlist[i].info.subversion);
}
SL_ClearServerList(servernode);
if (map_icon_data != NULL)
Z_Free(map_icon_data);
if (map_icon != NULL)
Patch_Free(map_icon);
map_icon_data = Z_Malloc(MAP_ICON_WIDTH * MAP_ICON_HEIGHT, PU_CACHE, NULL);
map_icon_index = 0;
map_icon_needed = 0xff;
map_icon_read = 0;
map_icon_missing = 0xffffffff;
map_icon_last_request = I_GetTime();
#endif
do
@@ -4690,7 +4845,7 @@ static void HandleConnect(SINT8 node)
connectedplayers++;
#ifdef SATURNJOIN
const boolean issaturn = (((doomcom->datalength-BASEPACKETSIZE) == sizeof(clientconfig_pak)) && netbuffer->u.clientcfg.issaturn == ISSATURN); // Check the packet lenght to skip potential garbo data!
const boolean issaturn = (((doomcom->datalength) == sizeof(clientconfig_pak)) && netbuffer->u.clientcfg.issaturn == ISSATURN); // Check the packet lenght to skip potential garbo data!
#endif
if (bannednode && bannednode[node].banid != SIZE_MAX)
@@ -4860,17 +5015,6 @@ static void HandleTimeout(SINT8 node)
M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING);
}
// Helper function for packets that should only be sent by the server
// If it is NOT from the server, bail out and close the connection!
static boolean ServerOnly(SINT8 node)
{
if (node == servernode)
return false;
Net_CloseConnection(node);
return true;
}
#ifndef NONET
/** Called when a PT_SERVERINFO packet is received
*
@@ -4910,6 +5054,115 @@ static void HandlePlayerInfo(void)
playerinfo[i] = netbuffer->u.playerinfo[i];
}
// Helper function for packets that should only be sent by the server
// If it is NOT from the server, bail out and close the connection!
static boolean ServerOnly(SINT8 node)
{
if (node == servernode)
return false;
Net_CloseConnection(node);
return true;
}
static void PT_MapIcon(void)
{
if (map_icon_data == NULL)
return;
if (ServerOnly(doomcom->remotenode))
return;
doomdata_t *netbuffer = DOOMCOM_DATA(doomcom);
UINT8 *icondata = netbuffer->u.mapicondata;
map_icon_last_request = I_GetTime();
map_icon_request_count = 0;
UINT8 index = READUINT8(icondata);
if (index & 0x80)
map_icon_needed = (index & 0x7f) + 1;
index &= 0x7f;
// check if we already got this fragment
if (map_icon_missing & ((UINT32)1 << index))
{
map_icon_missing &= ~((UINT32)1 << index);
INT32 offset = index * (MAXPACKETLENGTH-BASEPACKETSIZE-1);
if (offset + doomcom->datalength-1 > MAP_ICON_WIDTH * MAP_ICON_HEIGHT)
{
CONS_Alert(CONS_WARNING, "Excess amount of data for map icon (expected %d, got %d)\n", MAP_ICON_WIDTH * MAP_ICON_HEIGHT, offset + doomcom->datalength-1);
Z_Free(map_icon_data);
map_icon_data = NULL;
return;
}
READMEM(icondata, &map_icon_data[offset], doomcom->datalength-1);
map_icon_read += doomcom->datalength-1;
map_icon_index++;
}
if (map_icon_index >= map_icon_needed)
{
if (map_icon_read == 0)
{
// map has no icon
Z_Free(map_icon_data);
map_icon_data = NULL;
return;
}
UINT8 *data = Z_Malloc(MAP_ICON_WIDTH * MAP_ICON_HEIGHT, PU_STATIC, NULL);
z_stream stream;
stream.zalloc = NULL;
stream.zfree = NULL;
stream.opaque = NULL;
stream.next_in = map_icon_data;
stream.avail_in = map_icon_read;
if (inflateInit(&stream) != Z_OK)
{
CONS_Alert(CONS_WARNING, "Cannot decompress map icon, not enough memory\n");
Z_Free(data);
Z_Free(map_icon_data);
map_icon_data = NULL;
return;
}
stream.next_out = data;
stream.avail_out = MAP_ICON_WIDTH * MAP_ICON_HEIGHT;
if (inflate(&stream, Z_FINISH) != Z_STREAM_END)
{
CONS_Alert(CONS_WARNING, "Failed to decompress map icon, map icon is invalid or corrupted\n");
deflateEnd(&stream);
Z_Free(data);
Z_Free(map_icon_data);
map_icon_data = NULL;
return;
}
inflateEnd(&stream);
Z_Free(map_icon_data);
map_icon_data = NULL;
if (stream.total_out > MAP_ICON_WIDTH * MAP_ICON_HEIGHT)
{
CONS_Alert(CONS_WARNING, "Excess amount of data for map icon (expected %d, got %ld)\n", MAP_ICON_WIDTH * MAP_ICON_HEIGHT, stream.total_out);
Z_Free(data);
return;
}
else if (stream.total_out < MAP_ICON_WIDTH * MAP_ICON_HEIGHT)
{
CONS_Alert(CONS_WARNING, "Insufficient amount of data for map icon (expected %d, got %ld)\n", MAP_ICON_WIDTH * MAP_ICON_HEIGHT, stream.total_out);
Z_Free(data);
return;
}
//map_icon = Patch_CreateFromData(MAP_ICON_WIDTH, MAP_ICON_HEIGHT, data);
map_icon = (patch_t *)R_PixelsToPatch(data, MAP_ICON_WIDTH, MAP_ICON_HEIGHT, 0, 0, NULL);
Z_Free(data);
}
}
static void PT_TellFilesNeeded(SINT8 node)
{
if (server && serverrunning)
@@ -4958,11 +5211,18 @@ static void PT_AskInfo(SINT8 node)
doomdata_t *netbuffer = DOOMCOM_DATA(doomcom);
SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
SV_SendPlayerInfo(node); // Send extra info
//SV_SendMapIcon(node); // idk i dont think we need this here
}
Net_CloseConnection(node);
}
static void PT_NeedMapIcon(SINT8 node)
{
SV_SendMapIcon(node);
Net_CloseConnection(node);
}
// Negative response of client join request
static void PT_ServerRefuse(SINT8 node)
{
@@ -5223,6 +5483,12 @@ static void HandlePacketFromAwayNode(SINT8 node)
case PT_PLAYERINFO:
HandlePlayerInfo();
break;
case PT_MAPICON:
PT_MapIcon();
break;
case PT_NEEDMAPICON:
PT_NeedMapIcon(node);
break;
case PT_SERVERTICS:
// Do not remove my own server (we have just get a out of order packet)
if (node == servernode)
@@ -5511,10 +5777,10 @@ static void PT_TextCmd(INT32 netconsole, SINT8 node)
// ignore if the textcmd size var is actually larger than it should be
// BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-1)
{
DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-1),
node, netconsole));
Net_UnAcknowledgePacket(node);
return;
+3 -1
View File
@@ -120,6 +120,8 @@ typedef enum
PT_ISSATURN, // Saturn specific identifier packet
#endif
PT_MAPICON, // Send map image icon (gamespy).
PT_NEEDMAPICON, // Request missed map icon (gamespy).
NUMPACKETTYPE
} packettype_t;
@@ -513,6 +515,7 @@ typedef struct
INT32 filesneedednum; // 4 bytes
filesneededconfig_pak filesneededcfg; // ??? bytes
UINT32 pingtable[MAXPLAYERS+1]; // 68 bytes
UINT8 mapicondata[0];
} u; // This is needed to pack diff packet types data together
} ATTRPACK doomdata_t;
@@ -564,7 +567,6 @@ typedef enum
KR_TIMEOUT, //Connection Timeout
KR_BAN, //Banned by server
KR_LEAVE, //Quit the game
} kickreason_t;
/* the max number of name changes in some time period */
+1
View File
@@ -1201,6 +1201,7 @@ boolean HGetPacket(void)
GotAcks();
continue;
}
doomcom->datalength -= BASEPACKETSIZE;
break;
}
#endif // ifndef NONET
+3
View File
@@ -25,6 +25,9 @@
#include "screen.h" // MAXVIDWIDTH, MAXVIDHEIGHT
#define MAP_ICON_WIDTH 160
#define MAP_ICON_HEIGHT 100
//
// ClipWallSegment
// Clips the given range of columns
+159
View File
@@ -451,6 +451,124 @@ void *Patch_CreateGL(patch_t *patch)
}
#endif // HWRENDER
//
// R_MaskedFlatToPatch
//
// Convert raw pixels to a patch.
//
void *R_PixelsToPatch(UINT8 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize)
{
INT16 x, y;
UINT8 *img;
UINT8 *imgptr = imgbuf;
UINT8 *colpointers, *startofspan;
size_t size = 0;
if (!raw)
return NULL;
// Write image size and offset
WRITEINT16(imgptr, width);
WRITEINT16(imgptr, height);
WRITEINT16(imgptr, leftoffset);
WRITEINT16(imgptr, topoffset);
// Leave placeholder to column pointers
colpointers = imgptr;
imgptr += width*4;
// Write columns
for (x = 0; x < width; x++)
{
int lastStartY = 0;
int spanSize = 0;
startofspan = NULL;
// Write column pointer
WRITEINT32(colpointers, imgptr - imgbuf);
// Write pixels
for (y = 0; y < height; y++)
{
UINT8 pixel = raw[((y * width) + x)];
UINT8 opaque = (pixel != 0xf7); // If not 247 (0xf7), we have a pixel
// End span if we have a transparent pixel
if (!opaque)
{
if (startofspan)
WRITEUINT8(imgptr, 0);
startofspan = NULL;
continue;
}
// Start new column if we need to
if (!startofspan || spanSize == 255)
{
int writeY = y;
// If we reached the span size limit, finish the previous span
if (startofspan)
WRITEUINT8(imgptr, 0);
if (y > 254)
{
// Make sure we're aligned to 254
if (lastStartY < 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
lastStartY = 254;
}
// Write stopgap empty spans if needed
writeY = y - lastStartY;
while (writeY > 254)
{
WRITEUINT8(imgptr, 254);
WRITEUINT8(imgptr, 0);
imgptr += 2;
writeY -= 254;
}
}
startofspan = imgptr;
WRITEUINT8(imgptr, writeY);
imgptr += 2;
spanSize = 0;
lastStartY = y;
}
// Write the pixel
WRITEUINT8(imgptr, pixel);
spanSize++;
startofspan[1] = spanSize;
}
if (startofspan)
WRITEUINT8(imgptr, 0);
WRITEUINT8(imgptr, 0xFF);
}
size = imgptr-imgbuf;
img = Z_Malloc(size, PU_STATIC, NULL);
memcpy(img, imgbuf, size);
if (destsize != NULL)
*destsize = size;
patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL);
#ifdef HWRENDER
Patch_CreateGL(converted);
#endif
Z_Free(img);
return converted;
}
//
// R_MaskedFlatToPatch
@@ -615,3 +733,44 @@ UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip)
return 0xFF00;
}
void R_PatchToPixels(patch_t *patch, UINT8 *dst)
{
const INT16 width = patch->width;
// init out destination to cyan so we can easily check for it when we convert it back to a patch
memset(dst, 0xf7, (width * patch->height)); // should this be here or should this be taken care of before passing it?
for (INT32 x = 0; x < width; ++x)
{
INT32 y = 0;
INT32 prevdelta = -1;
column_t *column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[x]));
while (column->topdelta != 0xff)
{
INT32 topdelta = column->topdelta;
if (topdelta <= prevdelta)
topdelta += prevdelta;
prevdelta = topdelta;
for (; y-topdelta < column->length; ++y)
{
UINT8 pixel = 0;
if (y >= topdelta)
{
UINT8 *source = (UINT8*)(column) + 3;
pixel = source[y-topdelta];
}
dst[(y*width) + x] = pixel;
}
column = (column_t *)((UINT8 *)column + column->length + 4);
}
}
}
+64 -62
View File
@@ -1,62 +1,64 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2018-2019 by Jaime "Lactozilla" Passos.
// Copyright (C) 2019 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file r_patch.h
/// \brief Patch generation.
#ifndef __R_PATCH__
#define __R_PATCH__
#include "r_defs.h"
// Structs
typedef enum
{
ROTAXIS_X, // Roll (the default)
ROTAXIS_Y, // Pitch
ROTAXIS_Z // Yaw
} rotaxis_t;
typedef struct
{
INT32 x, y;
rotaxis_t rotaxis;
} spriteframepivot_t;
typedef struct
{
spriteframepivot_t pivot[64];
boolean available;
} spriteinfo_t;
// Patch functions
patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest);
void Patch_Free(patch_t *patch);
#define Patch_FreeTag(tagnum) Patch_FreeTags(tagnum, tagnum)
void Patch_FreeTags(INT32 lowtag, INT32 hightag);
#ifdef HWRENDER
void *Patch_AllocateHardwarePatch(patch_t *patch);
void *Patch_CreateGL(patch_t *patch);
#endif
// Conversions between patches / flats / textures...
boolean R_CheckIfPatch(lumpnum_t lump);
// SpriteInfo
extern spriteinfo_t spriteinfo[NUMSPRITES];
void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
void *R_MaskedFlatToPatch(UINT16 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize);
UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip);
#endif // __R_PATCH__
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2018-2019 by Jaime "Lactozilla" Passos.
// Copyright (C) 2019 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file r_patch.h
/// \brief Patch generation.
#ifndef __R_PATCH__
#define __R_PATCH__
#include "r_defs.h"
// Structs
typedef enum
{
ROTAXIS_X, // Roll (the default)
ROTAXIS_Y, // Pitch
ROTAXIS_Z // Yaw
} rotaxis_t;
typedef struct
{
INT32 x, y;
rotaxis_t rotaxis;
} spriteframepivot_t;
typedef struct
{
spriteframepivot_t pivot[64];
boolean available;
} spriteinfo_t;
// Patch functions
patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest);
void Patch_Free(patch_t *patch);
#define Patch_FreeTag(tagnum) Patch_FreeTags(tagnum, tagnum)
void Patch_FreeTags(INT32 lowtag, INT32 hightag);
#ifdef HWRENDER
void *Patch_AllocateHardwarePatch(patch_t *patch);
void *Patch_CreateGL(patch_t *patch);
#endif
// Conversions between patches / flats / textures...
boolean R_CheckIfPatch(lumpnum_t lump);
// SpriteInfo
extern spriteinfo_t spriteinfo[NUMSPRITES];
void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
void *R_MaskedFlatToPatch(UINT16 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize);
void *R_PixelsToPatch(UINT8 *raw, INT16 width, INT16 height, INT16 leftoffset, INT16 topoffset, size_t *destsize);
UINT16 R_GetPatchPixel(patch_t *patch, INT32 x, INT32 y, boolean flip);
void R_PatchToPixels(patch_t *patch, UINT8 *dst); // Important: dst must have enough space for patch->width*patch->height pixels!
#endif // __R_PATCH__