utils: add studio model decompiler.

pull/139/head
Andrey Akhmichin 3 years ago committed by Alibek Omarov
parent 3d9c482eae
commit 7a58813254
  1. 5
      engine/studio.h
  2. 31
      public/crtlib.c
  3. 2
      public/crtlib.h
  4. 52
      utils/mdldec/Makefile
  5. 302
      utils/mdldec/mdldec.c
  6. 26
      utils/mdldec/mdldec.h
  7. 649
      utils/mdldec/qc.c
  8. 25
      utils/mdldec/qc.h
  9. 103
      utils/mdldec/res/activities.txt
  10. 549
      utils/mdldec/smd.c
  11. 22
      utils/mdldec/smd.h
  12. 127
      utils/mdldec/texture.c
  13. 22
      utils/mdldec/texture.h
  14. 86
      utils/mdldec/utils.c
  15. 24
      utils/mdldec/utils.h
  16. 8
      utils/mdldec/version.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

@ -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 );

@ -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 );

@ -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 @@
/*
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 @@
/*
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 @@
/*
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 @@
/*
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 @@
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 @@
/*
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++ )