diff --git a/engine/studio.h b/engine/studio.h index e4e0f387..b33bff0c 100644 --- a/engine/studio.h +++ b/engine/studio.h @@ -71,11 +71,14 @@ Studio models are position independent, so the cache manager can move them. #define STUDIO_NF_CHROME 0x0002 #define STUDIO_NF_FULLBRIGHT 0x0004 #define STUDIO_NF_NOMIPS 0x0008 // ignore mip-maps - +#define STUDIO_NF_NOSMOOTH 0x0010 // don't smooth tangent space #define STUDIO_NF_ADDITIVE 0x0020 // rendering with additive mode #define STUDIO_NF_MASKED 0x0040 // use texture with alpha channel #define STUDIO_NF_NORMALMAP 0x0080 // indexed normalmap +#define STUDIO_NF_SOLID 0x0800 +#define STUDIO_NF_TWOSIDE 0x1000 // render mesh as twosided + #define STUDIO_NF_COLORMAP (1<<30) // internal system flag #define STUDIO_NF_UV_COORDS (1<<31) // using half-float coords instead of ST diff --git a/public/crtlib.c b/public/crtlib.c index 9db0a409..5c9c24e3 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -890,6 +890,37 @@ void COM_ReplaceExtension( char *path, const char *extension ) COM_DefaultExtension( path, extension ); } +/* +============ +COM_RemoveLineFeed +============ +*/ +void COM_RemoveLineFeed( char *str ) +{ + while( *str != '\0' ) + { + if( *str == '\r' || *str == '\n' ) + *str = '\0'; + + ++str; + } +} + +/* +============ +COM_PathSlashFix +============ +*/ +void COM_PathSlashFix( char *path ) +{ + size_t len; + + len = Q_strlen( path ); + + if( path[len - 1] != '\\' || path[len - 1] != '/' ) + Q_strcpy( &path[len], "/" ); +} + int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ) { return matchpattern_with_separator( in, pattern, caseinsensitive, "/\\:", false ); diff --git a/public/crtlib.h b/public/crtlib.h index f7bbfd90..d5c71f21 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -82,6 +82,8 @@ void COM_ReplaceExtension( char *path, const char *extension ); void COM_ExtractFilePath( const char *path, char *dest ); const char *COM_FileWithoutPath( const char *in ); void COM_StripExtension( char *path ); +void COM_RemoveLineFeed( char *str ); +void COM_PathSlashFix( char *path ); #define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 ) int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ); int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one ); diff --git a/utils/mdldec/Makefile b/utils/mdldec/Makefile new file mode 100644 index 00000000..95138166 --- /dev/null +++ b/utils/mdldec/Makefile @@ -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) diff --git a/utils/mdldec/mdldec.c b/utils/mdldec/mdldec.c new file mode 100644 index 00000000..295c35b4 --- /dev/null +++ b/utils/mdldec/mdldec.c @@ -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 +#include +#include +#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; +} + diff --git a/utils/mdldec/mdldec.h b/utils/mdldec/mdldec.h new file mode 100644 index 00000000..c25da1db --- /dev/null +++ b/utils/mdldec/mdldec.h @@ -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 + diff --git a/utils/mdldec/qc.c b/utils/mdldec/qc.c new file mode 100644 index 00000000..fb22bbd4 --- /dev/null +++ b/utils/mdldec/qc.c @@ -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 +#include +#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 ); +} + diff --git a/utils/mdldec/qc.h b/utils/mdldec/qc.h new file mode 100644 index 00000000..05d538ac --- /dev/null +++ b/utils/mdldec/qc.h @@ -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 + diff --git a/utils/mdldec/res/activities.txt b/utils/mdldec/res/activities.txt new file mode 100644 index 00000000..5d966502 --- /dev/null +++ b/utils/mdldec/res/activities.txt @@ -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 diff --git a/utils/mdldec/smd.c b/utils/mdldec/smd.c new file mode 100644 index 00000000..3562bfc7 --- /dev/null +++ b/utils/mdldec/smd.c @@ -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 +#include +#include +#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(); +} + diff --git a/utils/mdldec/smd.h b/utils/mdldec/smd.h new file mode 100644 index 00000000..2bcdcadb --- /dev/null +++ b/utils/mdldec/smd.h @@ -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 + diff --git a/utils/mdldec/texture.c b/utils/mdldec/texture.c new file mode 100644 index 00000000..f27381a2 --- /dev/null +++ b/utils/mdldec/texture.c @@ -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 +#include +#include +#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 ); + } +} + diff --git a/utils/mdldec/texture.h b/utils/mdldec/texture.h new file mode 100644 index 00000000..ba1cbe0b --- /dev/null +++ b/utils/mdldec/texture.h @@ -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 + diff --git a/utils/mdldec/utils.c b/utils/mdldec/utils.c new file mode 100644 index 00000000..89f923b1 --- /dev/null +++ b/utils/mdldec/utils.c @@ -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 +#include +#include +#include +#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; +} + diff --git a/utils/mdldec/utils.h b/utils/mdldec/utils.h new file mode 100644 index 00000000..b2e9958f --- /dev/null +++ b/utils/mdldec/utils.h @@ -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 + diff --git a/utils/mdldec/version.h b/utils/mdldec/version.h new file mode 100644 index 00000000..131b9be8 --- /dev/null +++ b/utils/mdldec/version.h @@ -0,0 +1,8 @@ +#pragma once +#ifndef VERSION_H +#define VERSION_H + +#define APP_VERSION "1.1" + +#endif // VERSION_H +