Andrey Akhmichin
5 years ago
committed by
Alibek Omarov
16 changed files with 2032 additions and 1 deletions
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
# Makefile for mdldec
|
||||
# Copyright (c) nekonomicon 2020
|
||||
|
||||
MODULE = mdldec |
||||
|
||||
CC ?= gcc |
||||
CFLAGS ?= -O3 -pipe -DHAVE_TGMATH_H |
||||
LDFLAGS ?= -Wl,--no-undefined |
||||
|
||||
SYS = $(shell $(CC) -dumpmachine) |
||||
|
||||
ifneq (, $(findstring mingw, $(SYS))) |
||||
EXT = .exe |
||||
else |
||||
EXT = |
||||
endif |
||||
|
||||
APP = $(MODULE)$(EXT) |
||||
|
||||
SRC = mdldec.c \
|
||||
qc.c \
|
||||
smd.c \
|
||||
texture.c \
|
||||
utils.c \
|
||||
../../public/xash3d_mathlib.c \
|
||||
../../public/matrixlib.c \
|
||||
../../public/crtlib.c |
||||
|
||||
INCLUDE = -I. \
|
||||
-I../../common \
|
||||
-I../../engine \
|
||||
-I../../engine/common \
|
||||
-I../../engine/common/imagelib \
|
||||
-I../../public |
||||
|
||||
LIBS = -lm |
||||
|
||||
OBJS = $(SRC:%.c=%.o) |
||||
|
||||
all: $(APP) |
||||
|
||||
$(APP): $(OBJS) |
||||
$(CC) $(LDFLAGS) -o $(APP) $(OBJS) $(LIBS) |
||||
|
||||
.c.o: |
||||
$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ |
||||
|
||||
.PHONY: all clean |
||||
|
||||
clean: |
||||
$(RM) $(OBJS) |
||||
$(RM) $(APP) |
@ -0,0 +1,302 @@
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
mdldec.c - Half-Life Studio Model Decompiler |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include "const.h" |
||||
#include "com_model.h" |
||||
#include "crtlib.h" |
||||
#include "studio.h" |
||||
#include "qc.h" |
||||
#include "smd.h" |
||||
#include "texture.h" |
||||
#include "utils.h" |
||||
#include "version.h" |
||||
|
||||
char destdir[MAX_SYSPATH]; |
||||
char modelfile[MAX_SYSPATH]; |
||||
studiohdr_t *model_hdr; |
||||
studiohdr_t *texture_hdr; |
||||
studiohdr_t **anim_hdr; |
||||
|
||||
/*
|
||||
============ |
||||
SequenceNameFix |
||||
============ |
||||
*/ |
||||
static void SequenceNameFix( void ) |
||||
{ |
||||
int i, j, counter; |
||||
qboolean hasduplicates = false; |
||||
mstudioseqdesc_t *seqdesc, *seqdesc1; |
||||
|
||||
for( i = 0; i < model_hdr->numseq; i++ ) |
||||
{ |
||||
seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; |
||||
|
||||
counter = 1; |
||||
|
||||
for( j = 0; j < model_hdr->numseq; j++ ) |
||||
{ |
||||
seqdesc1 = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + j; |
||||
|
||||
if( j != i && !Q_strncmp( seqdesc1->label, seqdesc->label, sizeof( seqdesc1->label ) ) ) |
||||
Q_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), "%s_%i", seqdesc1->label, ++counter ); |
||||
} |
||||
|
||||
if( counter > 1 ) |
||||
{ |
||||
printf( "WARNING: Sequence name \"%s\" is repeated %i times.\n", seqdesc->label, counter ); |
||||
|
||||
Q_snprintf( seqdesc->label, sizeof( seqdesc->label ), "%s_1", seqdesc->label ); |
||||
|
||||
hasduplicates = true; |
||||
} |
||||
} |
||||
|
||||
if( hasduplicates ) |
||||
puts( "WARNING: Added numeric suffix to repeated sequence name(s)." ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
BoneNameFix |
||||
============ |
||||
*/ |
||||
static void BoneNameFix( void ) |
||||
{ |
||||
int i, counter = 0; |
||||
mstudiobone_t *bone; |
||||
|
||||
for( i = 0; i < model_hdr->numbones; i++ ) |
||||
{ |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; |
||||
|
||||
if( bone->name[0] == '\0' ) |
||||
Q_sprintf( bone->name, "MDLDEC_Bone%i", ++counter ); |
||||
} |
||||
|
||||
if( counter ) |
||||
printf( "WARNING: Gived name to %i unnamed bone(s).\n", counter ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
LoadMDL |
||||
============ |
||||
*/ |
||||
static qboolean LoadMDL( const char *modelname ) |
||||
{ |
||||
int i; |
||||
size_t len; |
||||
char texturename[MAX_SYSPATH]; |
||||
char seqgroupname[MAX_SYSPATH]; |
||||
const char *ext; |
||||
const char id_mdlhdr[] = {'I', 'D', 'S', 'T'}; |
||||
const char id_seqhdr[] = {'I', 'D', 'S', 'Q'}; |
||||
|
||||
printf( "MDL: %s\n", modelname ); |
||||
|
||||
len = Q_strlen( modelname ); |
||||
|
||||
if( len > MAX_SYSPATH - 3 ) |
||||
{ |
||||
fputs( "ERROR: Source path is too long.\n", stderr ); |
||||
return false; |
||||
} |
||||
|
||||
ext = COM_FileExtension( modelname ); |
||||
|
||||
if( !ext ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Source file does not have extension.\n" ); |
||||
return false; |
||||
} |
||||
|
||||
if( Q_strcmp( ext, "mdl" ) ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Only .mdl-files is supported.\n" ); |
||||
return false; |
||||
} |
||||
|
||||
model_hdr = (studiohdr_t *)LoadFile( modelname ); |
||||
|
||||
if( !model_hdr ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Can't open %s\n", modelname ); |
||||
return false; |
||||
} |
||||
|
||||
if( memcmp( &model_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) ) ) |
||||
{ |
||||
if( !memcmp( &model_hdr->ident, id_seqhdr, sizeof( id_seqhdr ) ) ) |
||||
fprintf( stderr, "ERROR: %s is not a main HL model file.\n", modelname ); |
||||
else |
||||
fprintf( stderr, "ERROR: %s is not a valid HL model file.\n", modelname ); |
||||
|
||||
return false; |
||||
} |
||||
|
||||
if( model_hdr->version != STUDIO_VERSION ) |
||||
{ |
||||
fprintf( stderr, "ERROR: %s has unknown Studio MDL format version.\n", modelname ); |
||||
return false; |
||||
} |
||||
|
||||
if( !model_hdr->numbodyparts ) |
||||
{ |
||||
fprintf( stderr, "ERROR: %s is not a main HL model file.\n", modelname ); |
||||
return false; |
||||
} |
||||
|
||||
if( destdir[0] != '\0' ) |
||||
{ |
||||
if( !IsFileExists( destdir ) ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Couldn't find directory %s\n", destdir ); |
||||
return false; |
||||
} |
||||
|
||||
COM_PathSlashFix( destdir ); |
||||
} |
||||
else |
||||
COM_ExtractFilePath( modelname, destdir ); |
||||
|
||||
len -= 4; // path length without extension
|
||||
|
||||
if( !model_hdr->numtextures ) |
||||
{ |
||||
Q_strcpy( texturename, modelname ); |
||||
Q_strcpy( &texturename[len], "t.mdl" ); |
||||
|
||||
texture_hdr = (studiohdr_t *)LoadFile( texturename ); |
||||
|
||||
if( !texture_hdr ) |
||||
{ |
||||
#if !XASH_WIN32 |
||||
// dirty hack for casesensetive filesystems
|
||||
texturename[len] = 'T'; |
||||
|
||||
texture_hdr = (studiohdr_t *)LoadFile( texturename ); |
||||
|
||||
if( !texture_hdr ) |
||||
#endif |
||||
{ |
||||
fprintf( stderr, "ERROR: Can't open external textures file %s\n", texturename ); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
if( memcmp( &texture_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) ) ) |
||||
{ |
||||
fprintf( stderr, "ERROR: %s is not a valid external textures file.\n", texturename ); |
||||
return false; |
||||
} |
||||
} |
||||
else |
||||
texture_hdr = model_hdr; |
||||
|
||||
anim_hdr = malloc( sizeof( studiohdr_t* ) * model_hdr->numseqgroups ); |
||||
|
||||
if( !anim_hdr ) |
||||
{ |
||||
fputs( "ERROR: Couldn't allocate memory for sequences.\n", stderr ); |
||||
return false; |
||||
} |
||||
|
||||
anim_hdr[0] = model_hdr; |
||||
|
||||
if( model_hdr->numseqgroups > 1 ) |
||||
{ |
||||
Q_strcpy( seqgroupname, modelname ); |
||||
|
||||
for( i = 1; i < model_hdr->numseqgroups; i++ ) |
||||
{ |
||||
Q_sprintf( &seqgroupname[len], "%02d.mdl", i ); |
||||
|
||||
anim_hdr[i] = (studiohdr_t *)LoadFile( seqgroupname ); |
||||
|
||||
if( !anim_hdr[i] ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Can't open sequence file %s\n", seqgroupname ); |
||||
return false; |
||||
} |
||||
|
||||
if( memcmp( &anim_hdr[i]->ident, id_seqhdr, sizeof( id_seqhdr ) ) ) |
||||
{ |
||||
fprintf( stderr, "ERROR: %s is not a valid sequence file.\n", seqgroupname ); |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
COM_FileBase( modelname, modelfile ); |
||||
|
||||
SequenceNameFix(); |
||||
|
||||
BoneNameFix(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
ShowHelp |
||||
============ |
||||
*/ |
||||
static void ShowHelp( const char *app_name ) |
||||
{ |
||||
printf( "usage: %s source_file\n", app_name ); |
||||
printf( " %s source_file target_directory\n", app_name ); |
||||
} |
||||
|
||||
int main( int argc, char *argv[] ) |
||||
{ |
||||
puts( "\nHalf-Life Studio Model Decompiler " APP_VERSION ); |
||||
puts( "Copyright Flying With Gauss 2020 (c) " ); |
||||
puts( "--------------------------------------------------" ); |
||||
|
||||
if( argc == 1 ) |
||||
{ |
||||
ShowHelp( argv[0] ); |
||||
goto end; |
||||
} |
||||
else if( argc == 3 ) |
||||
{ |
||||
if( Q_strlen( argv[2] ) > MAX_SYSPATH - 1 ) |
||||
{ |
||||
fputs( "ERROR: Destination path is too long.\n", stderr ); |
||||
goto end; |
||||
} |
||||
|
||||
Q_strcpy( destdir, argv[2] ); |
||||
} |
||||
|
||||
if( !LoadActivityList( argv[0] ) || !LoadMDL( argv[1] ) ) |
||||
goto end; |
||||
|
||||
WriteQCScript(); |
||||
WriteSMD(); |
||||
WriteTextures(); |
||||
|
||||
puts( "Done." ); |
||||
|
||||
end: |
||||
puts( "--------------------------------------------------" ); |
||||
|
||||
return 0; |
||||
} |
||||
|
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
mdldec.h - Half-Life Studio Model Decompiler |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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. |
||||
*/ |
||||
#pragma once |
||||
#ifndef MDLDEC_H |
||||
#define MDLDEC_H |
||||
|
||||
extern char destdir[MAX_SYSPATH]; |
||||
extern char modelfile[MAX_SYSPATH]; |
||||
extern studiohdr_t *model_hdr; |
||||
extern studiohdr_t *texture_hdr; |
||||
extern studiohdr_t **anim_hdr; |
||||
|
||||
#endif // MDLDEC_H
|
||||
|
@ -0,0 +1,649 @@
@@ -0,0 +1,649 @@
|
||||
/*
|
||||
qc.c - Quake C script writer |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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 <stdlib.h> |
||||
#include <string.h> |
||||
#include "eiface.h" |
||||
#include "studio.h" |
||||
#include "crtlib.h" |
||||
#include "version.h" |
||||
#include "mdldec.h" |
||||
#include "utils.h" |
||||
#include "qc.h" |
||||
|
||||
static char **activity_names; |
||||
static int activity_count; |
||||
|
||||
/*
|
||||
============ |
||||
LoadActivityList |
||||
============ |
||||
*/ |
||||
qboolean LoadActivityList( const char *appname ) |
||||
{ |
||||
FILE *fp; |
||||
const char *p; |
||||
char path[MAX_SYSPATH]; |
||||
char buf[256]; |
||||
|
||||
fp = fopen( ACTIVITIES_FILE, "r" ); |
||||
|
||||
if( !fp ) |
||||
{ |
||||
p = getenv( "MDLDEC_ACT_PATH" ); |
||||
|
||||
if( !p ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Couldn't find file " ACTIVITIES_FILE ".\n" \ |
||||
"Place " ACTIVITIES_FILE " beside %s or set MDLDEC_ACT_PATH environment variable.\n", appname ); |
||||
return false; |
||||
} |
||||
|
||||
Q_strncpy( path, p, MAX_SYSPATH - 1 ); |
||||
|
||||
COM_PathSlashFix( path ); |
||||
|
||||
Q_strncat( path, ACTIVITIES_FILE, MAX_SYSPATH ); |
||||
|
||||
fp = fopen( path, "r" ); |
||||
|
||||
if( !fp ) |
||||
{ |
||||
fputs( "ERROR: Can't open file " ACTIVITIES_FILE ".\n", stderr ); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
while( fgets( buf, sizeof( buf ), fp ) ) |
||||
{ |
||||
activity_names = realloc( activity_names, sizeof( char* ) * ++activity_count ); |
||||
|
||||
if( !activity_names ) |
||||
{ |
||||
fputs( "ERROR: Couldn't allocate memory for activities strings.\n", stderr ); |
||||
return false; |
||||
} |
||||
|
||||
COM_RemoveLineFeed( buf ); |
||||
|
||||
activity_names[activity_count - 1] = strdup( buf ); |
||||
|
||||
if( !activity_names[activity_count - 1] ) |
||||
{ |
||||
fputs( "ERROR: Couldn't allocate memory for activities strings.\n", stderr ); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
fclose( fp ); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
FindActivityName |
||||
============ |
||||
*/ |
||||
static const char *FindActivityName( int type ) |
||||
{ |
||||
if( type >= 0 && type < activity_count ) |
||||
return activity_names[type]; |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
GetMotionTypeString |
||||
============ |
||||
*/ |
||||
static void GetMotionTypeString( int type, char *str, qboolean is_composite ) |
||||
{ |
||||
const char *p = NULL; |
||||
|
||||
str[0] = '\0'; |
||||
|
||||
if( is_composite ) |
||||
{ |
||||
if( type & STUDIO_X ) |
||||
Q_strcat( str, " X" ); |
||||
|
||||
if( type & STUDIO_Y ) |
||||
Q_strcat( str, " Y" ); |
||||
|
||||
if( type & STUDIO_Z ) |
||||
Q_strcat( str, " Z" ); |
||||
|
||||
if( type & STUDIO_XR ) |
||||
Q_strcat( str, " XR" ); |
||||
|
||||
if( type & STUDIO_YR ) |
||||
Q_strcat( str, " YR" ); |
||||
|
||||
if( type & STUDIO_ZR ) |
||||
Q_strcat( str, " ZR" ); |
||||
|
||||
if( type & STUDIO_LX ) |
||||
Q_strcat( str, " LX" ); |
||||
|
||||
if( type & STUDIO_LY ) |
||||
Q_strcat( str, " LY" ); |
||||
|
||||
if( type & STUDIO_LZ ) |
||||
Q_strcat( str, " LZ" ); |
||||
|
||||
if( type & STUDIO_AX ) |
||||
Q_strcat( str, " AX" ); |
||||
|
||||
if( type & STUDIO_AY ) |
||||
Q_strcat( str, " AY" ); |
||||
|
||||
if( type & STUDIO_AZ ) |
||||
Q_strcat( str, " AZ" ); |
||||
|
||||
if( type & STUDIO_AXR ) |
||||
Q_strcat( str, " AXR" ); |
||||
|
||||
if( type & STUDIO_AYR ) |
||||
Q_strcat( str, " AYR" ); |
||||
|
||||
if( type & STUDIO_AZR ) |
||||
Q_strcat( str, " AZR" ); |
||||
|
||||
return; |
||||
} |
||||
|
||||
type &= STUDIO_TYPES; |
||||
|
||||
switch( type ) |
||||
{ |
||||
case STUDIO_X: p = "X"; break; |
||||
case STUDIO_Y: p = "Y"; break; |
||||
case STUDIO_Z: p = "Z"; break; |
||||
case STUDIO_XR: p = "XR"; break; |
||||
case STUDIO_YR: p = "YR"; break; |
||||
case STUDIO_ZR: p = "ZR"; break; |
||||
case STUDIO_LX: p = "LX"; break; |
||||
case STUDIO_LY: p = "LY"; break; |
||||
case STUDIO_LZ: p = "LZ"; break; |
||||
case STUDIO_AX: p = "AX"; break; |
||||
case STUDIO_AY: p = "AY"; break; |
||||
case STUDIO_AZ: p = "AZ"; break; |
||||
case STUDIO_AXR: p = "AXR"; break; |
||||
case STUDIO_AYR: p = "AYR"; break; |
||||
case STUDIO_AZR: p = "AZR"; break; |
||||
default: break; |
||||
} |
||||
|
||||
if( p ) |
||||
Q_strcpy( str, p ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteTextureRenderMode |
||||
============ |
||||
*/ |
||||
static void WriteTextureRenderMode( FILE *fp ) |
||||
{ |
||||
int i; |
||||
mstudiotexture_t *texture; |
||||
|
||||
for( i = 0; i < texture_hdr->numtextures; i++ ) |
||||
{ |
||||
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + i; |
||||
|
||||
if( texture->flags & STUDIO_NF_FLATSHADE ) |
||||
fprintf( fp,"$texrendermode \"%s\" \"flatshade\" \n", texture->name ); // sven-coop extension
|
||||
|
||||
if( texture->flags & STUDIO_NF_CHROME ) |
||||
fprintf( fp, "$texrendermode \"%s\" \"chrome\" \n", texture->name ); // sven-coop extension/may be added in HLMV
|
||||
|
||||
if( texture->flags & STUDIO_NF_FULLBRIGHT ) |
||||
fprintf( fp, "$texrendermode \"%s\" \"fullbright\" \n", texture->name ); // sven-coop extension/xash3d extension
|
||||
|
||||
if( texture->flags & STUDIO_NF_NOMIPS ) |
||||
fprintf( fp, "$texrendermode \"%s\" \"nomips\" \n", texture->name ); // sven-coop extension
|
||||
|
||||
if( texture->flags & STUDIO_NF_NOSMOOTH ) |
||||
{ |
||||
fprintf( fp, "$texrendermode \"%s\" \"alpha\" \n", texture->name ); // sven-coop extension
|
||||
fprintf( fp, "$texrendermode \"%s\" \"nosmooth\" \n", texture->name ); // xash3d extension
|
||||
} |
||||
|
||||
if( texture->flags & STUDIO_NF_ADDITIVE ) |
||||
fprintf( fp, "$texrendermode \"%s\" \"additive\" \n", texture->name ); |
||||
|
||||
if( texture->flags & STUDIO_NF_MASKED ) |
||||
fprintf( fp, "$texrendermode \"%s\" \"masked\" \n", texture->name ); |
||||
|
||||
if( texture->flags & ( STUDIO_NF_MASKED | STUDIO_NF_SOLID ) ) |
||||
fprintf( fp, "$texrendermode \"%s\" \"masked_solid\" \n", texture->name ); // xash3d extension
|
||||
|
||||
if( texture->flags & STUDIO_NF_TWOSIDE ) |
||||
fprintf( fp, "$texrendermode \"%s\" \"twoside\" \n", texture->name ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteSkinFamilyInfo |
||||
============ |
||||
*/ |
||||
static void WriteSkinFamilyInfo( FILE *fp ) |
||||
{ |
||||
int i, j, k; |
||||
short *skinref, index; |
||||
mstudiotexture_t *texture; |
||||
|
||||
if( texture_hdr->numskinfamilies < 2 ) |
||||
return; |
||||
|
||||
fprintf( fp, "\n// %i skin families\n", texture_hdr->numskinfamilies ); |
||||
|
||||
fputs( "$texturegroup skinfamilies \n{\n", fp ); |
||||
|
||||
skinref = (short *)( (byte *)texture_hdr + texture_hdr->skinindex ); |
||||
|
||||
for( i = 0; i < texture_hdr->numskinfamilies; ++i ) |
||||
{ |
||||
fputs( "{", fp ); |
||||
|
||||
for( j = 0; j < texture_hdr->numskinref; ++j ) |
||||
{ |
||||
index = *( skinref + i * texture_hdr->numskinref + j ); |
||||
|
||||
for( k = 0; k < texture_hdr->numskinfamilies; ++k ) |
||||
{ |
||||
if( index == *( skinref + k * texture_hdr->numskinref + j ) ) |
||||
continue; |
||||
|
||||
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + index; |
||||
|
||||
fprintf( fp, " \"%s\" ", texture->name ); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
fputs( "}\n", fp ); |
||||
} |
||||
|
||||
fputs( "}\n", fp ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteAttachmentInfo |
||||
============ |
||||
*/ |
||||
static void WriteAttachmentInfo( FILE *fp ) |
||||
{ |
||||
int i; |
||||
mstudioattachment_t *attachment; |
||||
mstudiobone_t *bone; |
||||
|
||||
if( !model_hdr->numattachments ) |
||||
return; |
||||
|
||||
fprintf( fp, "\n// %i attachment(s)\n", model_hdr->numattachments ); |
||||
|
||||
for( i = 0; i < model_hdr->numattachments; ++i ) |
||||
{ |
||||
attachment = (mstudioattachment_t *)( (byte *)model_hdr + model_hdr->attachmentindex ) + i; |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + attachment->bone; |
||||
|
||||
fprintf( fp, "$attachment %i \"%s\" %f %f %f\n", i, bone->name, attachment->org[0], attachment->org[1], attachment->org[2] ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteBodyGroupInfo |
||||
============ |
||||
*/ |
||||
static void WriteBodyGroupInfo( FILE *fp ) |
||||
{ |
||||
int i, j; |
||||
mstudiobodyparts_t *bodypart; |
||||
mstudiomodel_t *model; |
||||
char modelname[64]; |
||||
|
||||
fputs( "\n// reference mesh(es)\n", fp ); |
||||
|
||||
for( i = 0; i < model_hdr->numbodyparts; ++i ) |
||||
{ |
||||
bodypart = (mstudiobodyparts_t *) ( (byte *)model_hdr + model_hdr->bodypartindex ) + i; |
||||
|
||||
if( bodypart->nummodels == 1 ) |
||||
{ |
||||
model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); |
||||
|
||||
COM_FileBase( model->name, modelname ); |
||||
|
||||
fprintf( fp, "$body \"%s\" \"%s\"\n\n", bodypart->name, model->name ); |
||||
continue; |
||||
} |
||||
|
||||
fprintf( fp, "$bodygroup \"%s\"\n", bodypart->name ); |
||||
|
||||
fputs( "{\n", fp ); |
||||
|
||||
for( j = 0; j < bodypart->nummodels; ++j ) |
||||
{ |
||||
model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j; |
||||
|
||||
if( !Q_strncmp( model->name, "blank", 5 ) ) |
||||
{ |
||||
fputs( "blank\n", fp ); |
||||
continue; |
||||
} |
||||
|
||||
COM_FileBase( model->name, modelname ); |
||||
|
||||
fprintf( fp, "studio \"%s\"\n", modelname ); |
||||
} |
||||
|
||||
fputs( "}\n\n" , fp ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteControllerInfo |
||||
============ |
||||
*/ |
||||
static void WriteControllerInfo( FILE *fp ) |
||||
{ |
||||
int i; |
||||
mstudiobonecontroller_t *bonecontroller; |
||||
mstudiobone_t *bone; |
||||
char motion_types[64]; |
||||
|
||||
if( !model_hdr->numbonecontrollers ) |
||||
return; |
||||
|
||||
fprintf( fp, "\n// %i bone controller(s)\n", model_hdr->numbonecontrollers ); |
||||
|
||||
for( i = 0; i < model_hdr->numbonecontrollers; ++i ) |
||||
{ |
||||
bonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex ) + i; |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + bonecontroller->bone; |
||||
|
||||
GetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, false ); |
||||
|
||||
fprintf( fp, "$controller %i \"%s\" %s %f %f\n", |
||||
bonecontroller->index, bone->name, motion_types, |
||||
bonecontroller->start, bonecontroller->end ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteHitBoxInfo |
||||
============ |
||||
*/ |
||||
static void WriteHitBoxInfo( FILE *fp ) |
||||
{ |
||||
int i; |
||||
mstudiobbox_t *hitbox; |
||||
mstudiobone_t *bone; |
||||
|
||||
if( !model_hdr->numhitboxes ) |
||||
return; |
||||
|
||||
fprintf( fp, "\n// %i hit box(es)\n", model_hdr->numhitboxes ); |
||||
|
||||
for( i = 0; i < model_hdr->numhitboxes; i++ ) |
||||
{ |
||||
hitbox = (mstudiobbox_t *)( (byte *)model_hdr + model_hdr->hitboxindex ) + i; |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + hitbox->bone; |
||||
|
||||
fprintf( fp, "$hbox %i \"%s\" %f %f %f %f %f %f\n", |
||||
hitbox->group, bone->name, |
||||
hitbox->bbmin[0], hitbox->bbmin[1], hitbox->bbmin[2], |
||||
hitbox->bbmax[0], hitbox->bbmax[1], hitbox->bbmax[2] ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteSequenceInfo |
||||
============ |
||||
*/ |
||||
static void WriteSequenceInfo( FILE *fp ) |
||||
{ |
||||
int i, j; |
||||
const char *activity; |
||||
char motion_types[256]; |
||||
mstudioevent_t *event; |
||||
mstudioseqdesc_t *seqdesc; |
||||
|
||||
if( model_hdr->numseqgroups > 1 ) |
||||
fputs( "\n$sequencegroupsize 64\n", fp ); |
||||
|
||||
if( model_hdr->numseq > 0 ) |
||||
fprintf( fp, "\n// %i animation sequence(s)\n", model_hdr->numseq ); |
||||
|
||||
for( i = 0; i < model_hdr->numseq; ++i ) |
||||
{ |
||||
seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; |
||||
|
||||
fprintf( fp, "$sequence \"%s\" ", seqdesc->label ); |
||||
|
||||
if( seqdesc->numblends > 1 ) |
||||
{ |
||||
if( seqdesc->numblends > 2 ) |
||||
{ |
||||
fputs( "{\n", fp ); |
||||
|
||||
for( j = 0; j < seqdesc->numblends; j++ ) |
||||
{ |
||||
fputs( " ", fp ); |
||||
|
||||
fprintf( fp, "\"%s_blend%i\" ", seqdesc->label, j + 1 ); |
||||
|
||||
fputs( "\n", fp ); |
||||
} |
||||
|
||||
fputs( " ", fp ); |
||||
} |
||||
else |
||||
{ |
||||
fprintf( fp, "\"%s_blend1\" ", seqdesc->label ); |
||||
fprintf( fp, "\"%s_blend2\" ", seqdesc->label ); |
||||
} |
||||
|
||||
GetMotionTypeString( seqdesc->blendtype[0], motion_types, false ); |
||||
|
||||
fprintf( fp, "blend %s %.0f %.0f", |
||||
motion_types, seqdesc->blendstart[0], seqdesc->blendend[0] ); |
||||
} |
||||
else |
||||
{ |
||||
fprintf( fp, "\"%s\"", seqdesc->label ); |
||||
} |
||||
|
||||
if( seqdesc->motiontype ) |
||||
{ |
||||
GetMotionTypeString( seqdesc->motiontype, motion_types, true ); |
||||
|
||||
fprintf( fp, "%s", motion_types ); |
||||
} |
||||
|
||||
fprintf( fp, " fps %.0f ", seqdesc->fps ); |
||||
|
||||
if( seqdesc->flags == 1 ) |
||||
fputs( "loop ", fp ); |
||||
|
||||
if( seqdesc->activity ) |
||||
{ |
||||
activity = FindActivityName( seqdesc->activity ); |
||||
|
||||
if( activity ) |
||||
{ |
||||
fprintf( fp, "%s %i ", activity, seqdesc->actweight ); |
||||
} |
||||
else |
||||
{ |
||||
printf( "WARNING: Sequence %s has a custom activity flag (ACT_%i %i).\n", |
||||
seqdesc->label, seqdesc->activity, seqdesc->actweight ); |
||||
|
||||
fprintf( fp, "ACT_%i %i ", seqdesc->activity, seqdesc->actweight ); |
||||
} |
||||
} |
||||
|
||||
if( seqdesc->entrynode && seqdesc->exitnode ) |
||||
{ |
||||
if( seqdesc->entrynode == seqdesc->exitnode ) |
||||
fprintf( fp, "node %i ", seqdesc->entrynode ); |
||||
else if( seqdesc->nodeflags ) |
||||
fprintf( fp, "rtransition %i %i ", seqdesc->entrynode, seqdesc->exitnode ); |
||||
else |
||||
fprintf( fp, "transition %i %i ", seqdesc->entrynode, seqdesc->exitnode ); |
||||
} |
||||
|
||||
if( seqdesc->numevents > 2 ) |
||||
{ |
||||
fputs( "{\n ", fp ); |
||||
|
||||
for( j = 0; j < seqdesc->numevents; j++ ) |
||||
{ |
||||
if( seqdesc->numblends <= 2 ) |
||||
fputs( " ", fp ); |
||||
else |
||||
fputs( " ", fp ); |
||||
|
||||
event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ) + j; |
||||
|
||||
fprintf( fp, "{ event %i %i", event->event, event->frame ); |
||||
|
||||
if( event->options[0] != '\0' ) |
||||
fprintf( fp, " \"%s\"", event->options ); |
||||
|
||||
fputs( " }\n ", fp ); |
||||
} |
||||
|
||||
fputs( "}", fp ); |
||||
} |
||||
else |
||||
{ |
||||
for( j = 0; j < seqdesc->numevents; j++ ) |
||||
{ |
||||
event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ) + j; |
||||
|
||||
fprintf( fp, "{ event %i %i", event->event, event->frame ); |
||||
|
||||
if( event->options[0] != '\0') |
||||
fprintf( fp, " \"%s\"", event->options ); |
||||
|
||||
fputs( " } ", fp ); |
||||
} |
||||
} |
||||
|
||||
fputs( "\n", fp ); |
||||
|
||||
if( seqdesc->numblends > 2 ) |
||||
fputs( "}\n", fp ); |
||||
|
||||
if( seqdesc->numpivots ) |
||||
printf( "WARNING: Sequence %s uses %i foot pivots, feature not supported.\n", |
||||
seqdesc->label, seqdesc->numpivots ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteQCScript |
||||
============ |
||||
*/ |
||||
void WriteQCScript( void ) |
||||
{ |
||||
FILE *fp; |
||||
char filename[MAX_SYSPATH]; |
||||
size_t len; |
||||
|
||||
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.qc", destdir, modelfile ); |
||||
|
||||
if( len >= MAX_SYSPATH ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.qc\n", modelfile ); |
||||
return; |
||||
} |
||||
|
||||
fp = fopen( filename, "w" ); |
||||
|
||||
if( !fp ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Can't write %s\n", filename ); |
||||
return; |
||||
} |
||||
|
||||
fputs( "/*\n", fp ); |
||||
fputs( "==============================================================================\n\n", fp ); |
||||
fputs( "QC script generated by Half-Life Studio Model Decompiler " APP_VERSION "\n", fp ); |
||||
|
||||
fprintf( fp, "Copyright Flying With Gauss %s (c) \n\n", Q_timestamp( TIME_YEAR_ONLY ) ); |
||||
fprintf( fp, "%s.mdl\n\n", modelfile ); |
||||
|
||||
fputs( "Original internal name:\n", fp ); |
||||
|
||||
fprintf( fp, "\"%s\"\n\n", model_hdr->name ); |
||||
|
||||
fputs( "==============================================================================\n", fp ); |
||||
fputs( "*/\n\n", fp ); |
||||
|
||||
fprintf( fp, "$modelname \"%s.mdl\"\n", modelfile ); |
||||
|
||||
fputs( "$cd \".\\\"\n", fp ); |
||||
fputs( "$cdtexture \".\\\"\n", fp ); |
||||
fputs( "$scale 1.0\n", fp ); |
||||
fputs( "$cliptotextures\n", fp ); |
||||
fputs( "\n", fp ); |
||||
|
||||
if( !model_hdr->numtextures ) |
||||
fputs( "$externaltextures\n", fp ); |
||||
|
||||
if( model_hdr->flags != 0 ) |
||||
{ |
||||
fprintf( fp, "$flags %i\n", model_hdr->flags ); |
||||
|
||||
printf( "WARNING: This model uses the $flags keyword set to %i\n", model_hdr->flags ); |
||||
} |
||||
|
||||
fputs( "\n", fp ); |
||||
|
||||
fprintf( fp, "$bbox %f %f %f", model_hdr->min[0], model_hdr->min[1], model_hdr->min[2] ); |
||||
fprintf( fp, " %f %f %f\n", model_hdr->max[0], model_hdr->max[1], model_hdr->max[2] ); |
||||
fprintf( fp, "$cbox %f %f %f", model_hdr->bbmin[0], model_hdr->bbmin[1], model_hdr->bbmin[2] ); |
||||
fprintf( fp, " %f %f %f\n", model_hdr->bbmax[0], model_hdr->bbmax[1], model_hdr->bbmax[2] ); |
||||
fprintf( fp, "$eyeposition %f %f %f\n", model_hdr->eyeposition[0], model_hdr->eyeposition[1], model_hdr->eyeposition[2] ); |
||||
|
||||
fputs( "\n", fp ); |
||||
|
||||
WriteBodyGroupInfo( fp ); |
||||
WriteTextureRenderMode( fp ); |
||||
WriteSkinFamilyInfo( fp ); |
||||
WriteAttachmentInfo( fp ); |
||||
WriteControllerInfo( fp ); |
||||
WriteHitBoxInfo( fp ); |
||||
WriteSequenceInfo( fp ); |
||||
|
||||
fputs( "\n// End of QC script.\n", fp ); |
||||
fclose( fp ); |
||||
|
||||
printf( "QC Script: %s\n", filename ); |
||||
} |
||||
|
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
qc.h - Quake C script writer |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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. |
||||
*/ |
||||
#pragma once |
||||
#ifndef QC_H |
||||
#define QC_H |
||||
|
||||
#define ACTIVITIES_FILE "activities.txt" |
||||
|
||||
qboolean LoadActivityList( const char *appname ); |
||||
void WriteQCScript( void ); |
||||
|
||||
#endif // QC_H
|
||||
|
@ -0,0 +1,103 @@
@@ -0,0 +1,103 @@
|
||||
ACT_RESET |
||||
ACT_IDLE |
||||
ACT_GUARD |
||||
ACT_WALK |
||||
ACT_RUN |
||||
ACT_FLY |
||||
ACT_SWIM |
||||
ACT_HOP |
||||
ACT_LEAP |
||||
ACT_FALL |
||||
ACT_LAND |
||||
ACT_STRAFE_LEFT |
||||
ACT_STRAFE_RIGHT |
||||
ACT_ROLL_LEFT |
||||
ACT_ROLL_RIGHT |
||||
ACT_TURN_LEFT |
||||
ACT_TURN_RIGHT |
||||
ACT_CROUCH |
||||
ACT_CROUCHIDLE |
||||
ACT_STAND |
||||
ACT_USE |
||||
ACT_SIGNAL1 |
||||
ACT_SIGNAL2 |
||||
ACT_SIGNAL3 |
||||
ACT_TWITCH |
||||
ACT_COWER |
||||
ACT_SMALL_FLINCH |
||||
ACT_BIG_FLINCH |
||||
ACT_RANGE_ATTACK1 |
||||
ACT_RANGE_ATTACK2 |
||||
ACT_MELEE_ATTACK1 |
||||
ACT_MELEE_ATTACK2 |
||||
ACT_RELOAD |
||||
ACT_ARM |
||||
ACT_DISARM |
||||
ACT_EAT |
||||
ACT_DIESIMPLE |
||||
ACT_DIEBACKWARD |
||||
ACT_DIEFORWARD |
||||
ACT_DIEVIOLENT |
||||
ACT_BARNACLE_HIT |
||||
ACT_BARNACLE_PULL |
||||
ACT_BARNACLE_CHOMP |
||||
ACT_BARNACLE_CHEW |
||||
ACT_SLEEP |
||||
ACT_INSPECT_FLOOR |
||||
ACT_INSPECT_WALL |
||||
ACT_IDLE_ANGRY |
||||
ACT_WALK_HURT |
||||
ACT_RUN_HURT |
||||
ACT_HOVER |
||||
ACT_GLIDE |
||||
ACT_FLY_LEFT |
||||
ACT_FLY_RIGHT |
||||
ACT_DETECT_SCENT |
||||
ACT_SNIFF |
||||
ACT_BITE |
||||
ACT_THREAT_DISPLAY |
||||
ACT_FEAR_DISPLAY |
||||
ACT_EXCITED |
||||
ACT_SPECIAL_ATTACK1 |
||||
ACT_SPECIAL_ATTACK2 |
||||
ACT_COMBAT_IDLE |
||||
ACT_WALK_SCARED |
||||
ACT_RUN_SCARED |
||||
ACT_VICTORY_DANCE |
||||
ACT_DIE_HEADSHOT |
||||
ACT_DIE_CHESTSHOT |
||||
ACT_DIE_GUTSHOT |
||||
ACT_DIE_BACKSHOT |
||||
ACT_FLINCH_HEAD |
||||
ACT_FLINCH_CHEST |
||||
ACT_FLINCH_STOMACH |
||||
ACT_FLINCH_LEFTARM |
||||
ACT_FLINCH_RIGHTARM |
||||
ACT_FLINCH_LEFTLEG |
||||
ACT_FLINCH_RIGHTLEG |
||||
ACT_VM_NONE |
||||
ACT_VM_DEPLOY |
||||
ACT_VM_DEPLOY_EMPTY |
||||
ACT_VM_HOLSTER |
||||
ACT_VM_HOLSTER_EMPTY |
||||
ACT_VM_IDLE1 |
||||
ACT_VM_IDLE2 |
||||
ACT_VM_IDLE3 |
||||
ACT_VM_RANGE_ATTACK1 |
||||
ACT_VM_RANGE_ATTACK2 |
||||
ACT_VM_RANGE_ATTACK3 |
||||
ACT_VM_MELEE_ATTACK1 |
||||
ACT_VM_MELEE_ATTACK2 |
||||
ACT_VM_MELEE_ATTACK3 |
||||
ACT_VM_SHOOT_EMPTY |
||||
ACT_VM_START_RELOAD |
||||
ACT_VM_RELOAD |
||||
ACT_VM_RELOAD_EMPTY |
||||
ACT_VM_TURNON |
||||
ACT_VM_TURNOFF |
||||
ACT_VM_PUMP |
||||
ACT_VM_PUMP_EMPTY |
||||
ACT_VM_START_CHARGE |
||||
ACT_VM_CHARGE |
||||
ACT_VM_OVERLOAD |
||||
ACT_VM_IDLE_EMPTY |
@ -0,0 +1,549 @@
@@ -0,0 +1,549 @@
|
||||
/*
|
||||
smd.c - Studio Model Data format writer |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include "const.h" |
||||
#include "com_model.h" |
||||
#include "xash3d_mathlib.h" |
||||
#include "crtlib.h" |
||||
#include "studio.h" |
||||
#include "mdldec.h" |
||||
#include "smd.h" |
||||
|
||||
static matrix3x4 *bonetransform; |
||||
|
||||
/*
|
||||
============ |
||||
CreateBoneTransformMatrices |
||||
============ |
||||
*/ |
||||
static qboolean CreateBoneTransformMatrices( void ) |
||||
{ |
||||
bonetransform = calloc( model_hdr->numbones, sizeof( matrix3x4 ) ); |
||||
|
||||
if( !bonetransform ) |
||||
{ |
||||
fputs( "ERROR: Couldn't allocate memory for bone transformation matrices!\n", stderr ); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
FillBoneTransformMatrices |
||||
============ |
||||
*/ |
||||
static void FillBoneTransformMatrices( void ) |
||||
{ |
||||
int i; |
||||
mstudiobone_t *bone; |
||||
matrix3x4 bonematrix; |
||||
vec4_t q; |
||||
|
||||
for( i = 0; i < model_hdr->numbones; i++ ) |
||||
{ |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; |
||||
|
||||
AngleQuaternion( &bone->value[3], q, true ); |
||||
Matrix3x4_FromOriginQuat( bonematrix, q, bone->value ); |
||||
|
||||
if( bone->parent == -1 ) |
||||
{ |
||||
Matrix3x4_Copy( bonetransform[i], bonematrix ); |
||||
continue; |
||||
} |
||||
|
||||
Matrix3x4_ConcatTransforms( bonetransform[i], bonetransform[bone->parent], bonematrix ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
RemoveBoneTransformMatrices |
||||
============ |
||||
*/ |
||||
static void RemoveBoneTransformMatrices( void ) |
||||
{ |
||||
free( bonetransform ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
ClipRotations |
||||
============ |
||||
*/ |
||||
static void ClipRotations( vec3_t angle ) |
||||
{ |
||||
int i; |
||||
|
||||
for( i = 0; i < 3; i++ ) |
||||
{ |
||||
while( angle[i] >= M_PI_F ) |
||||
angle[i] -= M_PI2_F; |
||||
|
||||
while( angle[i] < -M_PI_F ) |
||||
angle[i] += M_PI2_F; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
ProperBoneRotationZ |
||||
============ |
||||
*/ |
||||
static void ProperBoneRotationZ( mstudioseqdesc_t *seqdesc, vec_t *motion, int frame, float angle ) |
||||
{ |
||||
int i; |
||||
float c, s, x, y; |
||||
float rot; |
||||
|
||||
for( i = 0; i < 3; i++ ) |
||||
motion[i] += frame * 1.0f / seqdesc->numframes * seqdesc->linearmovement[i]; |
||||
|
||||
rot = DEG2RAD( angle ); |
||||
|
||||
s = sin( rot ); |
||||
c = cos( rot ); |
||||
|
||||
x = motion[0]; |
||||
y = motion[1]; |
||||
|
||||
motion[0] = c * x - s * y; |
||||
motion[1] = s * x + c * y; |
||||
|
||||
motion[5] += rot; |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
CalcBonePosition |
||||
============ |
||||
*/ |
||||
static void CalcBonePosition( mstudioanim_t *anim, mstudiobone_t *bone, vec_t *motion, int frame ) |
||||
{ |
||||
int i, j; |
||||
float value; |
||||
mstudioanimvalue_t *animvalue; |
||||
|
||||
for( i = 0; i < 6; i++ ) |
||||
{ |
||||
motion[i] = bone->value[i]; |
||||
|
||||
if( !anim->offset[i] ) |
||||
continue; |
||||
|
||||
animvalue = (mstudioanimvalue_t *)( (byte *)anim + anim->offset[i] ); |
||||
|
||||
j = frame; |
||||
|
||||
while( animvalue->num.total <= j ) |
||||
{ |
||||
j -= animvalue->num.total; |
||||
animvalue += animvalue->num.valid + 1; |
||||
} |
||||
|
||||
if( animvalue->num.valid > j ) |
||||
value = animvalue[j + 1].value; |
||||
else |
||||
value = animvalue[animvalue->num.valid].value; |
||||
|
||||
motion[i] += value * bone->scale[i]; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteNodes |
||||
============ |
||||
*/ |
||||
static void WriteNodes( FILE *fp ) |
||||
{ |
||||
int i; |
||||
mstudiobone_t *bone; |
||||
|
||||
fputs( "nodes\n", fp ); |
||||
|
||||
for( i = 0; i < model_hdr->numbones; i++ ) |
||||
{ |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; |
||||
|
||||
fprintf( fp, "%3i \"%s\" %i\n", i, bone->name, bone->parent ); |
||||
} |
||||
|
||||
fputs( "end\n", fp ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteSkeleton |
||||
============ |
||||
*/ |
||||
static void WriteSkeleton( FILE *fp ) |
||||
{ |
||||
int i, j; |
||||
mstudiobone_t *bone; |
||||
|
||||
fputs( "skeleton\n", fp ); |
||||
fputs( "time 0\n", fp ); |
||||
|
||||
for( i = 0; i < model_hdr->numbones; i++ ) |
||||
{ |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; |
||||
|
||||
fprintf( fp, "%3i", i ); |
||||
|
||||
for( j = 0; j < 6; j++ ) |
||||
fprintf( fp, " %f", bone->value[j] ); |
||||
|
||||
fputs( "\n", fp ); |
||||
} |
||||
|
||||
fputs( "end\n", fp ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteTriangleInfo |
||||
============ |
||||
*/ |
||||
static void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t *texture, mstudiotrivert_t **triverts, qboolean isevenstrip ) |
||||
{ |
||||
int i, indices[3]; |
||||
int vert_index; |
||||
int norm_index; |
||||
int bone_index; |
||||
float s, t, u, v; |
||||
byte *vertbone; |
||||
vec3_t *studioverts; |
||||
vec3_t *studionorms; |
||||
vec3_t vert, norm; |
||||
|
||||
if( isevenstrip ) |
||||
{ |
||||
indices[0] = 1; |
||||
indices[1] = 2; |
||||
indices[2] = 0; |
||||
} |
||||
else |
||||
{ |
||||
indices[0] = 0; |
||||
indices[1] = 1; |
||||
indices[2] = 2; |
||||
} |
||||
|
||||
vertbone = ( (byte *)model_hdr + model->vertinfoindex ); |
||||
studioverts = (vec3_t *)( (byte *)model_hdr + model->vertindex ); |
||||
studionorms = (vec3_t *)( (byte *)model_hdr + model->normindex ); |
||||
|
||||
s = 1.0f / texture->width; |
||||
t = 1.0f / texture->height; |
||||
|
||||
fprintf( fp, "%s\n", texture->name ); |
||||
|
||||
for( i = 0; i < 3; i++ ) |
||||
{ |
||||
vert_index = triverts[indices[i]]->vertindex; |
||||
norm_index = triverts[indices[i]]->normindex; |
||||
bone_index = vertbone[vert_index]; |
||||
|
||||
Matrix3x4_VectorTransform( bonetransform[bone_index], studioverts[vert_index], vert ); |
||||
Matrix3x4_VectorRotate( bonetransform[bone_index], studionorms[norm_index], norm ); |
||||
VectorNormalize( norm ); |
||||
|
||||
u = ( triverts[indices[i]]->s + 1.0f ) * s; |
||||
v = 1.0f - triverts[indices[i]]->t * t; |
||||
|
||||
fprintf( fp, "%3i %f %f %f %f %f %f %f %f\n", |
||||
bone_index, |
||||
vert[0], vert[1], vert[2], |
||||
norm[0], norm[1], norm[2], |
||||
u, v ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteTriangles |
||||
============ |
||||
*/ |
||||
static void WriteTriangles( FILE *fp, mstudiomodel_t *model ) |
||||
{ |
||||
int i, j, k; |
||||
mstudiomesh_t *mesh; |
||||
mstudiotexture_t *texture; |
||||
mstudiotrivert_t *triverts[3]; |
||||
short *tricmds; |
||||
|
||||
fputs( "triangles\n", fp ); |
||||
|
||||
for( i = 0; i < model->nummesh; i++ ) |
||||
{ |
||||
mesh = (mstudiomesh_t *)( (byte *)model_hdr + model->meshindex ) + i; |
||||
tricmds = (short *)( (byte *)model_hdr + mesh->triindex ); |
||||
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + mesh->skinref; |
||||
|
||||
while( ( j = *( tricmds++ ) ) ) |
||||
{ |
||||
if( j >= 0 ) |
||||
{ |
||||
// triangle fan
|
||||
for( k = 0; j > 0; j--, k++, tricmds += 4 ) |
||||
{ |
||||
if( k == 0 ) |
||||
{ |
||||
triverts[0] = (mstudiotrivert_t *)tricmds; |
||||
} |
||||
else if( k == 1 ) |
||||
{ |
||||
triverts[2] = (mstudiotrivert_t *)tricmds; |
||||
} |
||||
else if( k == 2 ) |
||||
{ |
||||
triverts[1] = (mstudiotrivert_t *)tricmds; |
||||
|
||||
WriteTriangleInfo( fp, model, texture, triverts, true ); |
||||
} |
||||
else if( k % 2 ) |
||||
{ |
||||
triverts[0] = triverts[2]; |
||||
triverts[2] = (mstudiotrivert_t *)tricmds; |
||||
|
||||
WriteTriangleInfo( fp, model, texture, triverts, false ); |
||||
} |
||||
else |
||||
{ |
||||
triverts[0] = triverts[1]; |
||||
triverts[1] = (mstudiotrivert_t *)tricmds; |
||||
|
||||
WriteTriangleInfo( fp, model, texture, triverts, true ); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// triangle strip
|
||||
j = abs( j ); |
||||
|
||||
for( k = 0; j > 0; j--, k++, tricmds += 4 ) |
||||
{ |
||||
if( k == 0 ) |
||||
{ |
||||
triverts[0] = (mstudiotrivert_t *)tricmds; |
||||
} |
||||
else if( k == 1 ) |
||||
{ |
||||
triverts[2] = (mstudiotrivert_t *)tricmds; |
||||
} |
||||
else if( k == 2 ) |
||||
{ |
||||
triverts[1] = (mstudiotrivert_t *)tricmds; |
||||
|
||||
WriteTriangleInfo( fp, model, texture, triverts, false ); |
||||
} |
||||
else |
||||
{ |
||||
triverts[2] = triverts[1]; |
||||
triverts[1] = (mstudiotrivert_t *)tricmds; |
||||
|
||||
WriteTriangleInfo( fp, model, texture, triverts, false ); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fputs( "end\n", fp ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteFrameInfo |
||||
============ |
||||
*/ |
||||
static void WriteFrameInfo( FILE *fp, mstudioanim_t *anim, mstudioseqdesc_t *seqdesc, int frame ) |
||||
{ |
||||
int i, j; |
||||
vec_t motion[6]; // x, y, z, xr, yr, zr
|
||||
mstudiobone_t *bone; |
||||
|
||||
fprintf( fp, "time %i\n", frame ); |
||||
|
||||
for( i = 0; i < model_hdr->numbones; i++, anim++ ) |
||||
{ |
||||
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; |
||||
|
||||
CalcBonePosition( anim, bone, motion, frame ); |
||||
|
||||
if( bone->parent == -1 ) |
||||
ProperBoneRotationZ( seqdesc, motion, frame, 270.0f ); |
||||
|
||||
ClipRotations( &motion[3] ); |
||||
|
||||
fprintf( fp, "%3i ", i ); |
||||
|
||||
for( j = 0; j < 6; j++ ) |
||||
fprintf( fp, " %f", motion[j] ); |
||||
|
||||
fputs( "\n", fp ); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteAnimations |
||||
============ |
||||
*/ |
||||
static void WriteAnimations( FILE *fp, mstudioseqdesc_t *seqdesc, int blend ) |
||||
{ |
||||
int i; |
||||
mstudioanim_t *anim; |
||||
|
||||
fputs( "skeleton\n", fp ); |
||||
|
||||
anim = (mstudioanim_t *)( (byte *)anim_hdr[seqdesc->seqgroup] + seqdesc->animindex ); |
||||
anim += blend * model_hdr->numbones; |
||||
|
||||
for( i = 0; i < seqdesc->numframes; i++ ) |
||||
WriteFrameInfo( fp, anim, seqdesc, i ); |
||||
|
||||
fputs( "end\n", fp ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteReferences |
||||
============ |
||||
*/ |
||||
static void WriteReferences( void ) |
||||
{ |
||||
int i, j; |
||||
size_t len; |
||||
FILE *fp; |
||||
mstudiomodel_t *model; |
||||
mstudiobodyparts_t *bodypart; |
||||
char name[64]; |
||||
char filename[MAX_SYSPATH]; |
||||
|
||||
if( !CreateBoneTransformMatrices() ) |
||||
return; |
||||
|
||||
FillBoneTransformMatrices(); |
||||
|
||||
for( i = 0; i < model_hdr->numbodyparts; i++ ) |
||||
{ |
||||
bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ) + i; |
||||
|
||||
for( j = 0; j < bodypart->nummodels; j++ ) |
||||
{ |
||||
model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j; |
||||
|
||||
if( !Q_strncmp( model->name, "blank", 5 ) ) |
||||
continue; |
||||
|
||||
COM_FileBase( model->name, name ); |
||||
|
||||
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, name ); |
||||
|
||||
if( len >= MAX_SYSPATH ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.smd\n", name ); |
||||
RemoveBoneTransformMatrices(); |
||||
return; |
||||
} |
||||
|
||||
fp = fopen( filename, "w" ); |
||||
|
||||
if( !fp ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Can't write %s\n", filename ); |
||||
RemoveBoneTransformMatrices(); |
||||
return; |
||||
} |
||||
|
||||
fputs( "version 1\n", fp ); |
||||
|
||||
WriteNodes( fp ); |
||||
WriteSkeleton( fp ); |
||||
WriteTriangles( fp, model ); |
||||
|
||||
fclose( fp ); |
||||
|
||||
printf( "Reference: %s\n", filename ); |
||||
} |
||||
} |
||||
|
||||
RemoveBoneTransformMatrices(); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteSequences |
||||
============ |
||||
*/ |
||||
static void WriteSequences( void ) |
||||
{ |
||||
int i, j; |
||||
size_t len; |
||||
FILE *fp; |
||||
char filename[MAX_SYSPATH]; |
||||
mstudioseqdesc_t *seqdesc; |
||||
|
||||
for( i = 0; i < model_hdr->numseq; i++ ) |
||||
{ |
||||
seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; |
||||
|
||||
for( j = 0; j < seqdesc->numblends; j++ ) |
||||
{ |
||||
if( seqdesc->numblends == 1 ) |
||||
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, seqdesc->label ); |
||||
else |
||||
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s_blend%i.smd", destdir, seqdesc->label, j + 1 ); |
||||
|
||||
if( len >= MAX_SYSPATH ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.smd\n", seqdesc->label ); |
||||
return; |
||||
} |
||||
|
||||
fp = fopen( filename, "w" ); |
||||
|
||||
if( !fp ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Can't write %s\n", filename ); |
||||
return; |
||||
} |
||||
|
||||
fputs( "version 1\n", fp ); |
||||
|
||||
WriteNodes( fp ); |
||||
WriteAnimations( fp, seqdesc, j ); |
||||
|
||||
fclose( fp ); |
||||
|
||||
printf( "Sequence: %s\n", filename ); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void WriteSMD( void ) |
||||
{ |
||||
WriteReferences(); |
||||
WriteSequences(); |
||||
} |
||||
|
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
smd.h - Studio Model Data format writer |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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. |
||||
*/ |
||||
#pragma once |
||||
#ifndef SMD_H |
||||
#define SMD_H |
||||
|
||||
void WriteSMD( void ); |
||||
|
||||
#endif // SMD_H
|
||||
|
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
texture.c - texture writer |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include "const.h" |
||||
#include "crtlib.h" |
||||
#include "studio.h" |
||||
#include "img_bmp.h" |
||||
#include "mdldec.h" |
||||
#include "texture.h" |
||||
|
||||
/*
|
||||
============ |
||||
WriteBMP |
||||
============ |
||||
*/ |
||||
static void WriteBMP( mstudiotexture_t *texture ) |
||||
{ |
||||
int i; |
||||
FILE *fp; |
||||
const byte *p; |
||||
byte *palette, *pic, *buf; |
||||
char filename[MAX_SYSPATH], texturename[64]; |
||||
rgba_t rgba_palette[256]; |
||||
bmp_t bmp_hdr = {0,}; |
||||
size_t texture_size, len; |
||||
|
||||
COM_FileBase( texture->name, texturename ); |
||||
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.bmp", destdir, texturename ); |
||||
|
||||
if( len >= MAX_SYSPATH ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.bmp\n", texturename ); |
||||
return; |
||||
} |
||||
|
||||
fp = fopen( filename, "wb" ); |
||||
|
||||
if( !fp ) |
||||
{ |
||||
fprintf( stderr, "ERROR: Can't write texture file %s\n", filename ); |
||||
return; |
||||
} |
||||
|
||||
texture_size = texture->height * texture->width; |
||||
pic = (byte *)texture_hdr + texture->index; |
||||
palette = pic + texture_size; |
||||
|
||||
bmp_hdr.id[0] = 'B'; |
||||
bmp_hdr.id[1] = 'M'; |
||||
bmp_hdr.width = texture->width; |
||||
bmp_hdr.height = texture->height; |
||||
bmp_hdr.planes = 1; |
||||
bmp_hdr.bitsPerPixel = 8; |
||||
bmp_hdr.bitmapDataSize = texture_size; |
||||
bmp_hdr.colors = 256; |
||||
|
||||
bmp_hdr.fileSize = sizeof( bmp_hdr ) + texture_size + sizeof( rgba_palette ); |
||||
bmp_hdr.bitmapDataOffset = sizeof( bmp_hdr ) + sizeof( rgba_palette ); |
||||
bmp_hdr.bitmapHeaderSize = BI_SIZE; |
||||
|
||||
fwrite( &bmp_hdr, sizeof( bmp_hdr ), 1, fp ); |
||||
|
||||
p = palette; |
||||
|
||||
for( i = 0; i < (int)bmp_hdr.colors; i++ ) |
||||
{ |
||||
rgba_palette[i][2] = *p++; |
||||
rgba_palette[i][1] = *p++; |
||||
rgba_palette[i][0] = *p++; |
||||
rgba_palette[i][3] = 0; |
||||
} |
||||
|
||||
fwrite( rgba_palette, sizeof( rgba_palette ), 1, fp ); |
||||
|
||||
buf = malloc( texture_size ); |
||||
|
||||
p = pic; |
||||
p += ( bmp_hdr.height - 1 ) * bmp_hdr.width; |
||||
|
||||
for( i = 0; i < bmp_hdr.height; i++ ) |
||||
{ |
||||
memcpy( buf + bmp_hdr.width * i, p, bmp_hdr.width ); |
||||
p -= bmp_hdr.width; |
||||
} |
||||
|
||||
fwrite( buf, texture_size, 1, fp ); |
||||
|
||||
fclose( fp ); |
||||
|
||||
free( buf ); |
||||
|
||||
printf( "Texture: %s\n", filename ); |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
WriteTextures |
||||
============ |
||||
*/ |
||||
void WriteTextures( void ) |
||||
{ |
||||
int i; |
||||
mstudiotexture_t *texture; |
||||
|
||||
for( i = 0; i < texture_hdr->numtextures; i++ ) |
||||
{ |
||||
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + i; |
||||
|
||||
WriteBMP( texture ); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
texture.h - texture writer |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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. |
||||
*/ |
||||
#pragma once |
||||
#ifndef TEXTURE_H |
||||
#define TEXTURE_H |
||||
|
||||
void WriteTextures( void ); |
||||
|
||||
#endif // TEXTURE_H
|
||||
|
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
utils.c - Useful helper functions |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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 <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <sys/stat.h> |
||||
#include "xash3d_types.h" |
||||
#include "crtlib.h" |
||||
#include "utils.h" |
||||
|
||||
/*
|
||||
============ |
||||
IsFileExists |
||||
============ |
||||
*/ |
||||
qboolean IsFileExists( const char *filename ) |
||||
{ |
||||
struct stat st; |
||||
int ret; |
||||
|
||||
ret = stat( filename, &st ); |
||||
|
||||
if( ret == -1 ) |
||||
return false; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
GetFileSize |
||||
============ |
||||
*/ |
||||
off_t GetFileSize( FILE *fp ) |
||||
{ |
||||
struct stat st; |
||||
int fd; |
||||
|
||||
fd = fileno( fp ); |
||||
fstat( fd, &st ); |
||||
|
||||
return st.st_size; |
||||
} |
||||
|
||||
/*
|
||||
============ |
||||
LoadFile |
||||
============ |
||||
*/ |
||||
byte *LoadFile( const char *filename ) |
||||
{ |
||||
FILE *fp; |
||||
byte *buf; |
||||
off_t size; |
||||
|
||||
fp = fopen( filename, "rb" ); |
||||
|
||||
if( !fp ) |
||||
return NULL; |
||||
|
||||
size = GetFileSize( fp ); |
||||
|
||||
buf = malloc( size ); |
||||
|
||||
if( !buf ) |
||||
return NULL; |
||||
|
||||
fread( buf, size, 1, fp ); |
||||
fclose( fp ); |
||||
|
||||
return buf; |
||||
} |
||||
|
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
utils.h - Useful helper functions |
||||
Copyright (C) 2020 Andrey Akhmichin |
||||
|
||||
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. |
||||
*/ |
||||
#pragma once |
||||
#ifndef UTILS_H |
||||
#define UTILS_H |
||||
|
||||
qboolean IsFileExists( const char *filename ); |
||||
off_t GetFileSize( FILE *fp ); |
||||
byte *LoadFile( const char *filename ); |
||||
|
||||
#endif // UTILS_H
|
||||
|
Loading…
Reference in new issue