Files
xash3d-fwgs/engine/common/mod_sprite.c

336 lines
8.3 KiB
C

/*
mod_sprite.c - sprite loading
Copyright (C) 2010 Uncle Mike
Copyright (C) 2019 a1batross
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "common.h"
#include "sprite.h"
#include "studio.h"
#include "mod_local.h"
#include "swaplib.h"
le_struct_begin( dsprite_q1_swap )
le_struct_field( dsprite_q1_t, ident )
le_struct_field( dsprite_q1_t, version )
le_struct_field( dsprite_q1_t, type )
le_struct_field( dsprite_q1_t, boundingradius )
le_struct_array( dsprite_q1_t, bounds, 2 )
le_struct_field( dsprite_q1_t, numframes )
le_struct_field( dsprite_q1_t, beamlength )
le_struct_field( dsprite_q1_t, synctype )
le_struct_end();
le_struct_begin( dsprite_hl_swap )
le_struct_field( dsprite_hl_t, ident )
le_struct_field( dsprite_hl_t, version )
le_struct_field( dsprite_hl_t, type )
le_struct_field( dsprite_hl_t, texFormat )
le_struct_field( dsprite_hl_t, boundingradius )
le_struct_array( dsprite_hl_t, bounds, 2 )
le_struct_field( dsprite_hl_t, numframes )
le_struct_field( dsprite_hl_t, facetype )
le_struct_field( dsprite_hl_t, synctype )
le_struct_end();
le_struct_begin( dspriteframe_swap )
le_struct_array( dspriteframe_t, origin, 2 )
le_struct_field( dspriteframe_t, width )
le_struct_field( dspriteframe_t, height )
le_struct_end();
static byte *Mod_SwapSpriteFrame( byte *p, byte *end, int bytes )
{
dspriteframe_t frame;
if( p + sizeof( frame ) > end )
return NULL;
memcpy( &frame, p, sizeof( frame ));
le_struct_swap( dspriteframe_swap, &frame );
memcpy( p, &frame, sizeof( frame ));
p += sizeof( frame );
// skip pixel data
if( frame.width < 0 || frame.height < 0 )
return NULL;
size_t pixels = (size_t)frame.width * frame.height * bytes;
if( pixels > (size_t)( end - p ))
return NULL;
p += pixels;
return p;
}
static byte *Mod_SwapSpriteGroup( byte *p, byte *end, int bytes, int min_frames )
{
dspritegroup_t group;
if( p + sizeof( group ) > end )
return NULL;
memcpy( &group, p, sizeof( group ));
group.numframes = LittleLong( group.numframes );
memcpy( p, &group, sizeof( group ));
p += sizeof( group );
// swap intervals
int numframes = group.numframes;
if( numframes < min_frames || numframes * sizeof( dspriteinterval_t ) > (size_t)( end - p ))
return NULL;
for( int i = 0; i < numframes; i++ )
{
dspriteinterval_t interval;
memcpy( &interval, p, sizeof( interval ));
interval.interval = LittleFloat( interval.interval );
memcpy( p, &interval, sizeof( interval ));
p += sizeof( interval );
}
// swap each frame in the group
for( int i = 0; i < numframes; i++ )
{
p = Mod_SwapSpriteFrame( p, end, bytes );
if( !p )
return NULL;
}
return p;
}
static qboolean Mod_SwapSprite( void *buffer, size_t buffersize, int *out_version )
{
byte *end = (byte *)buffer + buffersize;
int numframes;
byte *p;
if( buffersize < sizeof( dsprite_t ))
return false;
// peek at ident + version before full swap
int version = LittleLong(((dsprite_t *)buffer)->version );
switch( version )
{
case SPRITE_VERSION_Q1:
case SPRITE_VERSION_32:
if( buffersize < sizeof( dsprite_q1_t ))
return false;
le_struct_swap( dsprite_q1_swap, buffer );
numframes = ((dsprite_q1_t *)buffer)->numframes;
p = (byte *)buffer + sizeof( dsprite_q1_t );
break;
case SPRITE_VERSION_HL:
{
if( buffersize < sizeof( dsprite_hl_t ))
return false;
le_struct_swap( dsprite_hl_swap, buffer );
numframes = ((dsprite_hl_t *)buffer)->numframes;
p = (byte *)buffer + sizeof( dsprite_hl_t );
// HL sprites have a palette count (short) + palette data
if( p + sizeof( short ) > end )
return false;
short numi = LittleShort( *(short *)p );
*(short *)p = numi;
p += sizeof( short );
if( numi < 0 || p + numi * 3 > end )
return false;
p += numi * 3;
break;
}
default:
return false;
}
if( numframes < 0 )
return false;
*out_version = version;
int bytes = ( version == SPRITE_VERSION_32 ) ? 4 : 1;
// swap all frames
for( int i = 0; i < numframes; i++ )
{
dframetype_t frametype;
if( p + sizeof( frametype ) > end )
return false;
memcpy( &frametype, p, sizeof( frametype ));
frametype.type = LittleLong( frametype.type );
memcpy( p, &frametype, sizeof( frametype ));
p += sizeof( frametype );
switch( frametype.type )
{
case FRAME_SINGLE:
p = Mod_SwapSpriteFrame( p, end, bytes );
break;
case FRAME_GROUP:
p = Mod_SwapSpriteGroup( p, end, bytes, 1 );
break;
case FRAME_ANGLED:
// angled groups are indexed by 0..7 at render time
p = Mod_SwapSpriteGroup( p, end, bytes, 8 );
break;
default:
return false;
}
if( !p )
return false;
}
return true;
}
/*
====================
Mod_LoadSpriteModel
load sprite model
====================
*/
void Mod_LoadSpriteModel( model_t *mod, void *buffer, size_t buffersize, qboolean *loaded )
{
msprite_t *psprite;
if( loaded )
*loaded = false;
int version;
if( !Mod_SwapSprite( buffer, buffersize, &version ))
{
Con_DPrintf( S_ERROR "%s: %s is not a valid sprite\n", __func__, mod->name );
return;
}
mod->type = mod_sprite;
char poolname[MAX_VA_STRING];
Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name );
mod->mempool = Mem_AllocPool( poolname );
if( version == SPRITE_VERSION_Q1 || version == SPRITE_VERSION_32 )
{
dsprite_q1_t *pinq1 = buffer;
if( pinq1->numframes == 0 )
{
Con_DPrintf( S_ERROR "%s: %s has no frames\n", __func__, mod->name );
return;
}
size_t size = sizeof( msprite_t ) + ( pinq1->numframes - 1 ) * sizeof( psprite->frames );
psprite = Mem_Calloc( mod->mempool, size );
mod->cache.data = psprite; // make link to extradata
psprite->type = pinq1->type;
psprite->texFormat = SPR_ADDITIVE;
psprite->numframes = mod->numframes = pinq1->numframes;
psprite->facecull = SPR_CULL_FRONT;
psprite->radius = pinq1->boundingradius;
psprite->synctype = pinq1->synctype;
// LadyHavoc: hack to allow sprites to be non-fullbright
if( Q_strchr( mod->name, '!' ))
psprite->texFormat = SPR_ALPHTEST;
mod->mins[0] = mod->mins[1] = -pinq1->bounds[0] * 0.5f;
mod->maxs[0] = mod->maxs[1] = pinq1->bounds[0] * 0.5f;
mod->mins[2] = -pinq1->bounds[1] * 0.5f;
mod->maxs[2] = pinq1->bounds[1] * 0.5f;
}
else // if( version == SPRITE_VERSION_HL )
{
dsprite_hl_t *pinhl = buffer;
if( pinhl->numframes == 0 )
{
Con_DPrintf( S_WARN "%s: %s has no frames\n", __func__, mod->name );
return;
}
size_t size = sizeof( msprite_t ) + ( pinhl->numframes - 1 ) * sizeof( psprite->frames );
psprite = Mem_Calloc( mod->mempool, size );
mod->cache.data = psprite; // make link to extradata
psprite->type = pinhl->type;
psprite->texFormat = pinhl->texFormat;
psprite->numframes = mod->numframes = pinhl->numframes;
psprite->facecull = pinhl->facetype;
psprite->radius = pinhl->boundingradius;
psprite->synctype = pinhl->synctype;
mod->mins[0] = mod->mins[1] = -pinhl->bounds[0] * 0.5f;
mod->maxs[0] = mod->maxs[1] = pinhl->bounds[0] * 0.5f;
mod->mins[2] = -pinhl->bounds[1] * 0.5f;
mod->maxs[2] = pinhl->bounds[1] * 0.5f;
}
if( loaded )
*loaded = true; // done
if( Host_IsDedicated() )
{
// skip frames loading
psprite->numframes = 0;
return;
}
#if !XASH_DEDICATED
Mod_SpriteLoadTextures( mod, buffer );
#endif
}
#if XASH_LLVM_LIBFUZZER
int EXPORT Fuzz_Mod_LoadSpriteModel( const uint8_t *Data, size_t Size );
int EXPORT Fuzz_Mod_LoadSpriteModel( const uint8_t *Data, size_t Size )
{
model_t mod = { .name = "#internal.spr", .needload = NL_NEEDS_LOADED };
qboolean loaded = false;
byte *buf;
if( Size == 0 )
return 0;
Memory_Init();
// dedicated mode exercises the parser/validator without needing a renderer
host.type = HOST_DEDICATED;
host.mempool = Mem_AllocPool( "fuzzing pool" );
// the loader byteswaps the buffer in place, so hand it a writable copy
buf = Mem_Malloc( host.mempool, Size );
memcpy( buf, Data, Size );
Mod_LoadSpriteModel( &mod, buf, Size, &loaded );
if( mod.mempool )
Mem_FreePool( &mod.mempool );
Mem_FreePool( &host.mempool );
return 0;
}
#endif // XASH_LLVM_LIBFUZZER