52f0f117ab
reworked deferred rendering to support specular. mrt can be reconfigured by TCs if desired. reworked q3bsp deluxemap code (so it no longer bugs out). fixed a few warnings. updated fteqcc to try to cope with xonotic. still not working (xonotic fails from bound checks). reworked shader conditionals to support elif. added some directives from QF(aka: warsow) git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5158 fc73d0e0-1445-4013-8a0c-d673dee63da5
3146 lines
78 KiB
C
3146 lines
78 KiB
C
|
|
#include "qcc.h"
|
|
//#include "decomp.h"
|
|
|
|
//This file is derived from frikdec, but has some extra tweaks to make it more friendly with fte's compiler/opcodes (its not perfect though)
|
|
//FIXME: there's still a load of mallocs, so we don't allow this more than once per process.
|
|
//convert vector terms into floats when its revealed that they're using vector ops and not floats
|
|
//FIXME: fteqcc converts do {} while(1); into a goto -1; which is a really weeeird construct.
|
|
|
|
#if defined(_WIN32) || defined(__DJGPP__)
|
|
#include <malloc.h>
|
|
#else
|
|
#include <alloca.h>
|
|
#endif
|
|
|
|
#undef printf
|
|
#define printf GUIprintf
|
|
|
|
#define OP_MARK_END_DO 0x00010000 //do{
|
|
#define OP_MARK_END_ELSE 0x00000400 //}
|
|
|
|
#define MAX_REGS 65536
|
|
#define dstatement_t QCC_dstatement32_t
|
|
#define statements destatements
|
|
#define functions defunctions
|
|
#define strings destrings
|
|
static dstatement_t *statements;
|
|
static float *pr_globals;
|
|
static char *strings;
|
|
static QCC_ddef32_t *globals;
|
|
static dfunction_t *functions;
|
|
|
|
static int ofs_return;
|
|
static int ofs_parms[MAX_PARMS];
|
|
static int ofs_size = 3;
|
|
|
|
static QCC_ddef_t *globalofsdef[MAX_REGS];
|
|
|
|
|
|
//forward declarations.
|
|
QCC_ddef_t *GetField(const char *name);
|
|
|
|
#include <stdio.h>
|
|
|
|
/*int QC_snprintfz(char *buffer, size_t maxlen, const char *format, ...)
|
|
{
|
|
int p;
|
|
va_list argptr;
|
|
|
|
if (!maxlen)
|
|
return -1;
|
|
|
|
va_start (argptr, format);
|
|
p = _vsnprintf (buffer, maxlen, format,argptr);
|
|
va_end (argptr);
|
|
buffer[maxlen-1] = 0;
|
|
|
|
return p;
|
|
}*/
|
|
|
|
extern QCC_opcode_t pr_opcodes [];
|
|
|
|
int endofsystemfields;
|
|
int debug_offs = 0;
|
|
int assumeglobals = 0; //unknown globals are assumed to be actual globals and NOT unlocked temps
|
|
int assumelocals = 0; //unknown locals are assumed to be actual locals and NOT locked temps
|
|
|
|
vfile_t *Decompileofile;
|
|
vfile_t *Decompileprogssrc;
|
|
vfile_t *Decompileprofile;
|
|
char **DecompileProfiles;//[MAX_FUNCTIONS];
|
|
static char **rettypes;//[MAX_FUNCTIONS];
|
|
|
|
extern int quakeforgeremap[];
|
|
|
|
char *type_names[] =
|
|
{
|
|
"void",
|
|
"string",
|
|
"float",
|
|
"vector",
|
|
"entity",
|
|
"ev_field",
|
|
"void()",
|
|
"ev_pointer",
|
|
"int",
|
|
"__variant",
|
|
"ev_struct",
|
|
"ev_union",
|
|
"ev_accessor",
|
|
"ev_quat",
|
|
"ev_uinteger"
|
|
};
|
|
char *typetoname(QCC_type_t *type)
|
|
{
|
|
return type->name;
|
|
}
|
|
|
|
char *temp_type (int temp, dstatement_t *start, dfunction_t *df)
|
|
{
|
|
int i;
|
|
dstatement_t *stat;
|
|
stat = start - 1;
|
|
// determine the type of a temp
|
|
|
|
while(stat > statements)
|
|
{
|
|
if (temp == stat->a)
|
|
return typetoname(*pr_opcodes[stat->op].type_a);
|
|
else if (temp == stat->b)
|
|
return typetoname(*pr_opcodes[stat->op].type_b);
|
|
else if (temp == stat->c)
|
|
return typetoname(*pr_opcodes[stat->op].type_c);
|
|
stat--;
|
|
}
|
|
|
|
// method 2
|
|
// find a call to this function
|
|
for (i = 0; i < numstatements; i++)
|
|
{
|
|
stat = &statements[i];
|
|
|
|
if (stat->op >= OP_CALL0 && stat->op <= OP_CALL8 && ((eval_t *)&pr_globals[stat->a])->function == df - functions)
|
|
{
|
|
for(i++; i < numstatements; i++)
|
|
{
|
|
stat = &statements[i];
|
|
if (ofs_return == stat->a && (*pr_opcodes[stat->op].type_a)->type != ev_void)
|
|
return type_names[(*pr_opcodes[stat->op].type_a)->type];
|
|
else if (ofs_return == stat->b && (*pr_opcodes[stat->op].type_b)->type != ev_void)
|
|
return type_names[(*pr_opcodes[stat->op].type_b)->type];
|
|
else if (stat->op == OP_DONE)
|
|
break;
|
|
else if (stat->op >= OP_CALL0 && stat->op <= OP_CALL8 && stat->a != df - functions)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("warning: Could not determine return type for %s\n", df->s_name + strings);
|
|
|
|
return "float";
|
|
|
|
}
|
|
|
|
boolean IsConstant(QCC_ddef_t *def)
|
|
{
|
|
|
|
int i;
|
|
dstatement_t *d;
|
|
|
|
if (def->type & DEF_SAVEGLOBAL)
|
|
return false;
|
|
|
|
if (pr_globals[def->ofs] == 0)
|
|
return false;
|
|
|
|
for (i = 1; i < numstatements; i++)
|
|
{
|
|
d = &statements[i];
|
|
if (d->b == def->ofs)
|
|
{
|
|
if (pr_opcodes[d->op].associative == ASSOC_RIGHT)
|
|
{
|
|
if (d->op - OP_STORE_F < 6)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
char *type_name (QCC_ddef_t *def)
|
|
{
|
|
QCC_ddef_t *j;
|
|
|
|
switch(def->type&~DEF_SAVEGLOBAL)
|
|
{
|
|
case ev_field:
|
|
case ev_pointer:
|
|
j = GetField(def->s_name + strings);
|
|
if (j)
|
|
return qcva(".%s",type_names[j->type]);
|
|
else
|
|
return type_names[def->type&~DEF_SAVEGLOBAL];
|
|
case ev_void:
|
|
case ev_string:
|
|
case ev_entity:
|
|
case ev_vector:
|
|
case ev_float:
|
|
return type_names[def->type&~DEF_SAVEGLOBAL];
|
|
case ev_function:
|
|
return "void()";
|
|
case ev_integer:
|
|
return "int";
|
|
// case ev_uinteger:
|
|
// return "unsigned";
|
|
// case ev_quat:
|
|
// return "quat";
|
|
default:
|
|
return "float";
|
|
}
|
|
};
|
|
|
|
extern int numstatements;
|
|
|
|
extern int numfunctions;
|
|
|
|
#define FILELISTSIZE 62
|
|
|
|
|
|
|
|
/*
|
|
===============
|
|
PR_String
|
|
|
|
Returns a string suitable for printing (no newlines, max 60 chars length)
|
|
===============
|
|
*/
|
|
const char *PR_String (const char *string)
|
|
{
|
|
static char buf[80];
|
|
char *s;
|
|
|
|
s = buf;
|
|
*s++ = '"';
|
|
while (string && *string)
|
|
{
|
|
if (s == buf + sizeof(buf) - 2)
|
|
break;
|
|
if (*string == '\n')
|
|
{
|
|
*s++ = '\\';
|
|
*s++ = 'n';
|
|
}
|
|
else if (*string == '"')
|
|
{
|
|
*s++ = '\\';
|
|
*s++ = '"';
|
|
}
|
|
else
|
|
*s++ = *string;
|
|
string++;
|
|
if (s - buf > 60)
|
|
{
|
|
*s++ = '.';
|
|
*s++ = '.';
|
|
*s++ = '.';
|
|
break;
|
|
}
|
|
}
|
|
*s++ = '"';
|
|
*s++ = 0;
|
|
return buf;
|
|
}
|
|
/*
|
|
============
|
|
PR_ValueString
|
|
|
|
Returns a string describing *data in a type specific manner
|
|
=============
|
|
*/
|
|
|
|
|
|
static char *PR_ValueString (etype_t type, void *val)
|
|
{
|
|
static char line[8192];
|
|
|
|
dfunction_t *f;
|
|
|
|
switch (type)
|
|
{
|
|
case ev_string:
|
|
QC_snprintfz(line, sizeof(line), "%s", PR_String(strings + *(int *)val));
|
|
break;
|
|
case ev_entity:
|
|
QC_snprintfz(line, sizeof(line), "entity %i", *(int *)val);
|
|
break;
|
|
case ev_function:
|
|
f = functions + *(int *)val;
|
|
if (!f)
|
|
QC_snprintfz(line, sizeof(line), "undefined function");
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "%s()", strings + f->s_name);
|
|
break;
|
|
/*
|
|
case ev_field:
|
|
def = PR_DefForFieldOfs ( *(int *)val );
|
|
sprintf (line, ".%s", def->name);
|
|
break;
|
|
*/
|
|
case ev_void:
|
|
QC_snprintfz(line, sizeof(line), "void");
|
|
break;
|
|
case ev_float:
|
|
{
|
|
unsigned int high = *(unsigned int*)val & 0xff000000;
|
|
if (high == 0xff000000 || !high)
|
|
//FIXME this is probably a string or something, but we don't really know what type it is.
|
|
QC_snprintfz(line, sizeof(line), "(float)(__variant)%ii", *(int*)val);
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "%5.1f", *(float *)val);
|
|
}
|
|
break;
|
|
case ev_vector:
|
|
QC_snprintfz(line, sizeof(line), "'%5.1f %5.1f %5.1f'", ((float *)val)[0], ((float *)val)[1], ((float *)val)[2]);
|
|
break;
|
|
case ev_pointer:
|
|
QC_snprintfz(line, sizeof(line), "pointer");
|
|
break;
|
|
case ev_field:
|
|
QC_snprintfz(line, sizeof(line), "<FIELD@%i>", *(int*)val);
|
|
break;
|
|
default:
|
|
QC_snprintfz(line, sizeof(line), "bad type %i", type);
|
|
break;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
|
|
static char *filenames[] =
|
|
{
|
|
"makevectors", "defs.qc",
|
|
"button_wait", "buttons.qc",
|
|
"anglemod", "ai.qc",
|
|
"boss_face", "boss.qc",
|
|
"info_intermission", "client.qc",
|
|
"CanDamage", "combat.qc",
|
|
"demon1_stand1", "demon.qc",
|
|
"dog_bite", "dog.qc",
|
|
"door_blocked", "doors.qc",
|
|
"Laser_Touch", "enforcer.qc",
|
|
"knight_attack", "fight.qc",
|
|
"f_stand1", "fish.qc",
|
|
"hknight_shot", "hknight.qc",
|
|
"SUB_regen", "items.qc",
|
|
"knight_stand1", "knight.qc",
|
|
"info_null", "misc.qc",
|
|
"monster_use", "monsters.qc",
|
|
"OgreGrenadeExplode", "ogre.qc",
|
|
"old_idle1", "oldone.qc",
|
|
"plat_spawn_inside_trigger", "plats.qc",
|
|
"player_stand1", "player.qc",
|
|
"shal_stand", "shalrath.qc",
|
|
"sham_stand1", "shambler.qc",
|
|
"army_stand1", "soldier.qc",
|
|
"SUB_Null", "subs.qc",
|
|
"tbaby_stand1", "tarbaby.qc",
|
|
"trigger_reactivate", "triggers.qc",
|
|
"W_Precache", "weapons.qc",
|
|
"LaunchMissile", "wizard.qc",
|
|
"main", "world.qc",
|
|
"zombie_stand1", "zombie.qc"
|
|
};
|
|
|
|
//FIXME: parse fteextensions.qc instead, or something.
|
|
#define QW(x) //x,
|
|
static struct {
|
|
int num;
|
|
char *name; //purly for readability.
|
|
QCC_type_t **returns;
|
|
QCC_type_t **params[8];
|
|
char *text;
|
|
} builtins[]=
|
|
{
|
|
{0, NULL, NULL, {NULL}, NULL},
|
|
{1, "makevectors", NULL, {&type_vector}, "void (vector ang)"},
|
|
{2, "setorigin", NULL, {&type_entity, &type_vector}, "void (entity e, vector o)"},
|
|
{3, "setmodel", NULL, {&type_entity, &type_string}, "void (entity e, string m)"},
|
|
{4, "setsize", NULL, {&type_entity, &type_vector, &type_vector}, "void (entity e, vector min, vector max)"},
|
|
{5, NULL, NULL, {NULL}, NULL},
|
|
{6, NULL, NULL, {NULL}, "void ()"},
|
|
{7, "random", NULL, {NULL}, "float ()"},
|
|
{8, "sound", NULL, {&type_entity, &type_float, &type_string, &type_float, &type_float}, "void (entity e, float chan, string samp, float vol, float atten)"},
|
|
{9, "normalize", &type_vector, {&type_vector}, "vector (vector v)"},
|
|
{10, "error", NULL, {&type_string}, "void (string e)"},
|
|
{11, "objerror", NULL, {&type_string}, "void (string e)"},
|
|
{12, "vlen", &type_float, {&type_vector}, "float (vector v)"},
|
|
{13, "vectoyaw", &type_float, {&type_vector}, "float (vector v)"},
|
|
{14, "spawn", &type_entity, {NULL}, "entity ()"},
|
|
{15, "remove", NULL, {&type_entity}, "void (entity e)"},
|
|
{16, "traceline", NULL, {&type_vector, &type_vector, &type_float, &type_entity}, "void (vector v1, vector v2, float nomonsters, entity forent)"},
|
|
{17, NULL, NULL, {NULL}, "entity ()"},
|
|
{18, "find", &type_entity, {&type_entity, &type_field, &type_string}, "entity (entity start, .string fld, string match)"},
|
|
{19, "precache_sound", NULL, {&type_string}, "string (string s)"},
|
|
{20, "precache_model", NULL, {&type_string}, "string (string s)"},
|
|
{21, "stuffcmd", NULL, {&type_entity, &type_string}, "void (entity client, string s)"},
|
|
{22, "findradius", NULL, {&type_vector, &type_float}, "entity (vector org, float rad)"},
|
|
{23, "bprint", NULL, {QW(&type_float) &type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (...)"},
|
|
{24, "sprint", NULL, {&type_entity, QW(&type_float) &type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (...)"},
|
|
{25, "dprint", NULL, {&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (...)"},
|
|
{26, "ftos", &type_string, {&type_float}, "string (float f)"},
|
|
{27, "vtos", &type_string, {&type_vector}, "string (vector v)"},
|
|
{28, "coredump", NULL, {NULL}, "void ()"},
|
|
{29, "traceon", NULL, {NULL}, "void ()"},
|
|
{30, "traceoff", NULL, {NULL}, "void ()"},
|
|
{31, "eprint", NULL, {&type_entity}, "void (entity e)"},
|
|
{32, "walkmove", &type_float, {&type_float, &type_float}, "float (float yaw, float dist)"},
|
|
{33, NULL, NULL, {NULL}, NULL},
|
|
{34, "droptofloor", NULL, {&type_float, &type_float}, "float ()"},
|
|
{35, "lightstyle", NULL, {&type_float, &type_string}, "void (float style, string value)"},
|
|
{36, "rint", &type_float, {&type_vector}, "float (float v)"},
|
|
{37, "floor", &type_float, {&type_vector}, "float (float v)"},
|
|
{38, "ceil", &type_float, {&type_vector}, "float (float v)"},
|
|
{39, NULL, NULL, {NULL}, NULL},
|
|
{40, "checkbottom", &type_float, {&type_entity}, "float (entity e)"},
|
|
{41, "pointcontents", &type_float, {&type_vector}, "float (vector v)"},
|
|
{42, NULL, NULL, {NULL}, NULL},
|
|
{43, "fabs", &type_float, {&type_float}, "float (float f)"},
|
|
{44, "aim", NULL, {&type_entity, &type_float}, "vector (entity e, float speed)"},
|
|
{45, "cvar", &type_string, {&type_string}, "float (string s)"},
|
|
{46, "localcmd", NULL, {&type_string}, "void (string s)"},
|
|
{47, "nextent", &type_entity, {&type_entity}, "entity (entity e)"},
|
|
{48, "", NULL, {&type_vector, &type_vector, &type_float, &type_float}, "void (vector o, vector d, float color, float count)"},
|
|
{49, "changeyaw", NULL, {NULL}, "void ()"},
|
|
{50, NULL, NULL, {NULL}, NULL},
|
|
{51, "vectoangles", &type_vector, {&type_vector}, "vector (vector v)"},
|
|
{52, "WriteByte", NULL, {&type_float, &type_float}, "void (float to, float f)"},
|
|
{53, "WriteChar", NULL, {&type_float, &type_float}, "void (float to, float f)"},
|
|
{54, "WriteShort", NULL, {&type_float, &type_float}, "void (float to, float f)"},
|
|
{55, "WriteLong", NULL, {&type_float, &type_float}, "void (float to, float f)"},
|
|
{56, "WriteCoord", NULL, {&type_float, &type_float}, "void (float to, float f)"},
|
|
{57, "WriteAngle", NULL, {&type_float, &type_float}, "void (float to, float f)"},
|
|
{58, "WriteString", NULL, {&type_float, &type_string}, "void (float to, string s)"},
|
|
{59, "WriteEntity", NULL, {&type_float, &type_entity}, "void (float to, entity s)"},
|
|
{60, NULL, NULL, {NULL}, NULL},
|
|
{61, NULL, NULL, {NULL}, NULL},
|
|
{62, NULL, NULL, {NULL}, NULL},
|
|
{63, NULL, NULL, {NULL}, NULL},
|
|
{64, NULL, NULL, {NULL}, NULL},
|
|
{65, NULL, NULL, {NULL}, NULL},
|
|
{66, NULL, NULL, {NULL}, NULL},
|
|
{67, "movetogoal", NULL, {&type_float}, "void (float step)"},
|
|
{68, "precache_file", NULL, {&type_string}, "string (string s)"},
|
|
{69, "makestatic", NULL, {&type_entity}, "void (entity e)"},
|
|
{70, "changelevel", NULL, {&type_string}, "void (string s)"},
|
|
{71, NULL, NULL, {NULL}, NULL},
|
|
{72, "cvar_set", NULL, {&type_string, &type_string}, "void (string var, string val)"},
|
|
{73, "centerprint", NULL, {&type_entity,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "void (entity client, string s, ...)"},
|
|
{74, "ambientsound", NULL, {&type_vector, &type_string, &type_float, &type_float}, "void (vector pos, string samp, float vol, float atten)"},
|
|
{75, "precache_model2", NULL, {&type_string}, "string (string s)"},
|
|
{76, "precache_sound2", NULL, {&type_string}, "string (string s)"},
|
|
{77, "precache_file2", NULL, {&type_string}, "string (string s)"},
|
|
{78, "setspawnparms", NULL, {&type_entity}, "void (entity e)"},
|
|
|
|
//quakeworld specific
|
|
{79, "logfrag", NULL, {&type_entity, &type_entity}, "void(entity killer, entity killee)"},
|
|
{80, "infokey", &type_string, {&type_entity, &type_string}, "string(entity e, string key)"},
|
|
{81, "stof", &type_float, {&type_string}, "float(string s)"},
|
|
{82, "multicast", NULL, {&type_vector, &type_float}, "void(vector where, float set)"},
|
|
|
|
//these are mvdsv specific
|
|
{83, "executecmd", NULL, {NULL}, NULL},
|
|
{84, "tokenize", NULL, {&type_string}, NULL},
|
|
{85, "argc", &type_float, {NULL}, "float()"},
|
|
{86, "argv", &type_string, {&type_float}, "string(float f)"},
|
|
{87, "teamfield", NULL, {NULL}, "void(.string fs)"},
|
|
{88, "substr", &type_string, {&type_string, &type_float, &type_float}, "string(string, float, float)"},
|
|
{89, "strcat", &type_string, {&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string,&type_string}, "string (...)"},
|
|
{90, "strlen", &type_float, {&type_string}, "float(string s)"},
|
|
{91, "str2byte", &type_float, {&type_string}, "float(string s)"},
|
|
{92, NULL, NULL, {NULL}, NULL},
|
|
{93, "newstr", &type_string, {&type_string}, "string(...)"},
|
|
{94, "freestr", NULL, {&type_string}, "void(string s)"},
|
|
{95, "conprint", NULL, {NULL}, NULL},
|
|
{96, "readcmd", &type_string, {&type_string}, "string(string cmd)"},
|
|
{97, "strcpy", NULL, {NULL}, NULL},
|
|
{98, "strstr", &type_string, {&type_string, &type_string}, "string(string str, string sub)"},
|
|
{99, "strncpy", NULL, {NULL}, NULL},
|
|
{100, "log", NULL, {NULL}, NULL},
|
|
{101, "redirectcmd", NULL, {NULL}, NULL},
|
|
{102, "calltimeofday", NULL, {NULL}, NULL},
|
|
{103, "forcedemoframe", NULL, {NULL}, NULL},
|
|
};
|
|
|
|
char *DecompileValueString(etype_t type, void *val);
|
|
QCC_ddef_t *DecompileGetParameter(gofs_t ofs);
|
|
QCC_ddef_t *DecompileFindGlobal(const char *name);
|
|
char *DecompilePrintParameter(QCC_ddef_t * def);
|
|
QCC_ddef_t *DecompileFunctionGlobal(int funcnum);
|
|
|
|
char *ReadProgsCopyright(char *buf, size_t bufsize)
|
|
{
|
|
char *copyright, *e;
|
|
dprograms_t *progs = (dprograms_t*)buf;
|
|
int lowest = progs->ofs_statements;
|
|
lowest = min(lowest, progs->ofs_globaldefs);
|
|
lowest = min(lowest, progs->ofs_fielddefs);
|
|
lowest = min(lowest, progs->ofs_functions);
|
|
lowest = min(lowest, progs->ofs_strings);
|
|
lowest = min(lowest, progs->ofs_globals);
|
|
lowest = min(lowest, progs->ofs_fielddefs);
|
|
|
|
copyright = (char*)(progs+1);
|
|
if (!strncmp("\r\n\r\n", copyright, 4))
|
|
{
|
|
copyright += 4;
|
|
e = copyright+strlen(copyright)+1;
|
|
if (e && !strncmp(e, "\r\n\r\n", 4))
|
|
{
|
|
if (e+4 <= buf+lowest)
|
|
{
|
|
return copyright;
|
|
}
|
|
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int DecompileReadData(char *srcfilename, char *buf, size_t bufsize)
|
|
{
|
|
dprograms_t progs;
|
|
int i, j;
|
|
void *p;
|
|
char name[1024];
|
|
QCC_ddef_t *fd;
|
|
|
|
int stsz = 16, defsz=16;
|
|
// int quakeforge = false;
|
|
|
|
memcpy(&progs, buf, sizeof(progs));
|
|
|
|
if (progs.version == PROG_VERSION)
|
|
stsz = defsz = 16;
|
|
else if (progs.version == 7)
|
|
{
|
|
if (progs.secondaryversion == PROG_SECONDARYVERSION16)
|
|
{
|
|
//regular 16bit progs, just an extended instruction set probably.
|
|
stsz = defsz = 16;
|
|
}
|
|
else if (progs.secondaryversion == PROG_SECONDARYVERSION32)
|
|
{
|
|
//32bit fte progs. everything is 32bit.
|
|
stsz = defsz = 32;
|
|
}
|
|
else
|
|
{
|
|
//progs is kk7 (certain QW TF mods). defs are 16bit but statements are 32bit. so this is unusable for saved games.
|
|
stsz = 32;
|
|
defsz = 16; //gah! fucked!
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stsz = defsz = 16;
|
|
// quakeforge = true;
|
|
}
|
|
|
|
strings = buf + progs.ofs_strings;
|
|
strofs = progs.numstrings;
|
|
|
|
numstatements = progs.numstatements;
|
|
|
|
// if (numstatements > MAX_STATEMENTS)
|
|
// Sys_Error("Too many statements");
|
|
if (stsz == 16)
|
|
{
|
|
const dstatement16_t *statements6 = (const dstatement16_t*)(buf+progs.ofs_statements);
|
|
statements = malloc(numstatements * sizeof(*statements));
|
|
for (i = 0; i < numstatements; i++)
|
|
{
|
|
statements[i].op = statements6[i].op;
|
|
|
|
if (statements[i].op == OP_GOTO)
|
|
statements[i].a = (signed short)statements6[i].a;
|
|
else
|
|
statements[i].a = (unsigned short)statements6[i].a;
|
|
|
|
if (statements[i].op == OP_IF_I || statements[i].op == OP_IFNOT_I ||
|
|
statements[i].op == OP_IF_F || statements[i].op == OP_IFNOT_F ||
|
|
statements[i].op == OP_IF_S || statements[i].op == OP_IFNOT_S)
|
|
statements[i].b = (signed short)statements6[i].b;
|
|
else
|
|
statements[i].b = (unsigned short)statements6[i].b;
|
|
|
|
statements[i].c = (unsigned short)statements6[i].c;
|
|
}
|
|
}
|
|
else if (stsz == 32)
|
|
statements = (dstatement32_t*)(buf+progs.ofs_statements);
|
|
else
|
|
Sys_Error("Unrecognised progs version");
|
|
|
|
numfunctions = progs.numfunctions;
|
|
functions = (dfunction_t*)(buf+progs.ofs_functions);
|
|
DecompileProfiles = calloc(numfunctions, sizeof(*DecompileProfiles));
|
|
rettypes = calloc(numfunctions, sizeof(*rettypes));
|
|
|
|
numglobaldefs = progs.numglobaldefs;
|
|
numfielddefs = progs.numfielddefs;
|
|
if (defsz == 16)
|
|
{
|
|
const QCC_ddef16_t *gd16 = (const QCC_ddef16_t*)(buf+progs.ofs_globaldefs);
|
|
globals = malloc(numglobaldefs * sizeof(*globals));
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
globals[i].ofs = gd16[i].ofs;
|
|
globals[i].s_name = gd16[i].s_name;
|
|
globals[i].type = gd16[i].type;
|
|
}
|
|
|
|
|
|
gd16 = (const QCC_ddef16_t*)(buf+progs.ofs_fielddefs);
|
|
fields = malloc(numfielddefs * sizeof(*fields));
|
|
for (i = 0; i < numfielddefs; i++)
|
|
{
|
|
fields[i].ofs = gd16[i].ofs;
|
|
fields[i].s_name = gd16[i].s_name;
|
|
fields[i].type = gd16[i].type;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
globals = (QCC_ddef32_t*)(buf+progs.ofs_globaldefs);
|
|
fields = (QCC_ddef32_t*)(buf+progs.ofs_fielddefs);
|
|
}
|
|
|
|
pr_globals = (float*)(buf+progs.ofs_globals);
|
|
numpr_globals = progs.numglobals;
|
|
|
|
printf("Decompiling...\n");
|
|
printf("Read Data from %s:\n", srcfilename);
|
|
printf("Total Size is %6i\n", bufsize);
|
|
printf("Version Code is %i\n", progs.version);
|
|
printf("CRC is %i\n", progs.crc);
|
|
printf("%6i strofs\n", strofs);
|
|
printf("%6i numstatements\n", numstatements);
|
|
printf("%6i numfunctions\n", numfunctions);
|
|
printf("%6i numglobaldefs\n", numglobaldefs);
|
|
printf("%6i numfielddefs\n", numfielddefs);
|
|
printf("%6i numpr_globals\n", numpr_globals);
|
|
printf("----------------------\n");
|
|
|
|
if (numpr_globals > MAX_REGS)
|
|
{
|
|
printf("fatal error: progs exceeds a limit\n");
|
|
exit(1);
|
|
}
|
|
|
|
ofs_return = OFS_RETURN;
|
|
for (i = 0; i < 8; i++)
|
|
ofs_parms[i] = OFS_PARM0 + i * 3;
|
|
ofs_size = 3;
|
|
|
|
/*
|
|
if (quakeforge)
|
|
{
|
|
int typeremap[] = {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer, ev_quat, ev_integer, ev_uinteger};
|
|
for (i = 1; i < numglobaldefs; i++)
|
|
{
|
|
globals[i].type = (globals[i].type & DEF_SAVEGLOBGAL) | typeremap[globals[i].type&~DEF_SAVEGLOBGAL];
|
|
}
|
|
for (i = 1; i < numfielddefs; i++)
|
|
{
|
|
fields[i].type = (fields[i].type & DEF_SAVEGLOBGAL) | typeremap[fields[i].type&~DEF_SAVEGLOBGAL];
|
|
}
|
|
|
|
for (i = 1; i < numstatements; i++)
|
|
{
|
|
if (statements[i].op >= OP_H2_FIRST)// && statements[i].op <= OP_H2_FIRST+sizeof(quakeforgeremap)/sizeof(quakeforgeremap[0]))
|
|
statements[i].op = quakeforgeremap[statements[i].op-OP_H2_FIRST];
|
|
}
|
|
|
|
fd = DecompileFindGlobal(".zero");
|
|
if (fd)
|
|
fd->ofs = -1;
|
|
|
|
fd = DecompileFindGlobal(".return");
|
|
if (fd)
|
|
{
|
|
ofs_return = fd->ofs;
|
|
fd->ofs = -1;
|
|
}
|
|
|
|
for (i = 0; i < 8; i++)
|
|
{
|
|
QC_snprintfz(name, sizeof(name), ".param_%i", i);
|
|
fd = DecompileFindGlobal(name);
|
|
if (fd)
|
|
{
|
|
ofs_parms[i] = fd->ofs;
|
|
fd->ofs = -1;
|
|
}
|
|
}
|
|
|
|
fd = DecompileFindGlobal(".param_size");
|
|
if (fd)
|
|
ofs_size = ((int*)pr_globals)[fd->ofs];
|
|
}
|
|
*/
|
|
|
|
//fix up the globaldefs
|
|
for (i = 1; i < numglobaldefs; i++)
|
|
{
|
|
if (globals[i].ofs < RESERVED_OFS)
|
|
globals[i].ofs += numpr_globals;
|
|
}
|
|
// fix up the functions
|
|
for (i = 1; i < numfunctions; i++)
|
|
{
|
|
if ((unsigned)functions[i].s_name >= (unsigned)strofs || strlen(functions[i].s_name + strings) <= 0)
|
|
{
|
|
fd = DecompileFunctionGlobal(i);
|
|
if (fd)
|
|
{
|
|
functions[i].s_name = fd->s_name;
|
|
continue;
|
|
}
|
|
QC_snprintfz(name, sizeof(name), "function%i", i);
|
|
name[strlen(name)] = 0;
|
|
p = malloc(strlen(name + 1));
|
|
strcpy(p, name);
|
|
functions[i].s_name = (char *)p - strings;
|
|
}
|
|
if (functions[i].first_statement > 0 && !functions[i].locals && functions[i].numparms)
|
|
{ //vanilla qcc apparently had a bug
|
|
for (j = 0; j < functions[i].numparms; j++)
|
|
functions[i].locals += functions[i].parm_size[j];
|
|
}
|
|
}
|
|
|
|
return progs.version;
|
|
}
|
|
|
|
int
|
|
DecompileGetFunctionIdxByName(const char *name)
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 1; i < numfunctions; i++)
|
|
if (!strcmp(name, strings + functions[i].s_name))
|
|
{
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const etype_t DecompileGetFieldTypeByDef(QCC_ddef_t *def)
|
|
{
|
|
int i;
|
|
int ofs = ((int*)pr_globals)[def->ofs];
|
|
|
|
for (i = 1; i < numfielddefs; i++)
|
|
if (fields[i].ofs == ofs)
|
|
{
|
|
if (!strcmp(strings+def->s_name, strings+fields[i].s_name))
|
|
return fields[i].type;
|
|
}
|
|
return ev_void;
|
|
}
|
|
const char *DecompileGetFieldNameIdxByFinalOffset(int ofs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 1; i < numfielddefs; i++)
|
|
if (fields[i].ofs == ofs)
|
|
{
|
|
return fields[i].s_name+strings;
|
|
}
|
|
return "UNKNOWN FIELD";
|
|
}
|
|
void DecompileGetFieldNameIdxByFinalOffset2(char *out, size_t outsize, int ofs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 1; i < numfielddefs; i++)
|
|
{
|
|
if (fields[i].ofs == ofs)
|
|
{
|
|
QC_snprintfz(out, outsize, "%s", fields[i].s_name+strings);
|
|
return;
|
|
}
|
|
else if (fields[i].type == ev_vector && fields[i].ofs+1 == ofs)
|
|
{
|
|
QC_snprintfz(out, outsize, "%s_y", fields[i].s_name+strings);
|
|
return;
|
|
}
|
|
else if (fields[i].type == ev_vector && fields[i].ofs+2 == ofs)
|
|
{
|
|
QC_snprintfz(out, outsize, "%s_z", fields[i].s_name+strings);
|
|
return;
|
|
}
|
|
}
|
|
QC_snprintfz(out, outsize, "<FIELD@%i>", ofs);
|
|
}
|
|
|
|
int
|
|
DecompileAlreadySeen(char *fname, vfile_t **rfile)
|
|
{
|
|
int ret = 1;
|
|
|
|
vfile_t *file;
|
|
|
|
file = QCC_FindVFile(fname);
|
|
if (!file)
|
|
{
|
|
ret = 0;
|
|
if (rfile)
|
|
{
|
|
char *header = "//Decompiled code. Please respect the original copyright.\n";
|
|
*rfile = QCC_AddVFile(fname, header, strlen(header));
|
|
AddSourceFile("progs.src", fname);
|
|
}
|
|
}
|
|
else if (rfile)
|
|
*rfile = file;
|
|
|
|
return ret;
|
|
}
|
|
|
|
char *DecompileReturnType(dfunction_t *df);
|
|
|
|
char *DecompileAgressiveType(dfunction_t *df, dstatement_t *last, gofs_t ofs)
|
|
{
|
|
QCC_ddef_t *par;
|
|
par = DecompileGetParameter(ofs);
|
|
if (par) //single = intended
|
|
{
|
|
return type_name(par);
|
|
}
|
|
|
|
if (ofs == ofs_return && ((last->op >= OP_CALL0 && last->op <= OP_CALL8) || (last->op >= OP_CALL1H && last->op <= OP_CALL8H)))
|
|
{ //offset is a return value, go look at the called function's return type.
|
|
return DecompileReturnType(functions + ((int*)pr_globals)[last->a]);
|
|
}
|
|
|
|
while(last >= &statements[df->first_statement])
|
|
{
|
|
if (last->c == ofs &&
|
|
pr_opcodes[last->op].associative == ASSOC_LEFT &&
|
|
pr_opcodes[last->op].priorityclass)
|
|
{
|
|
//previous was an operation into the temp
|
|
return type_names[(*pr_opcodes[last->op].type_c)->type];
|
|
|
|
|
|
// sprintf(fname, "%s ", temp_type6(rds->a, rds, df));
|
|
}
|
|
last--;
|
|
}
|
|
|
|
return NULL; //got to start of function... shouldn't really happen.
|
|
}
|
|
|
|
char *DecompileReturnType(dfunction_t *df)
|
|
{
|
|
dstatement_t *ds;
|
|
unsigned short dom;
|
|
boolean foundret = false;
|
|
static int recursion;
|
|
char *ret = NULL; //return null if we don't know.
|
|
int couldbeastring = true;
|
|
|
|
if (df->first_statement <= 0)
|
|
{
|
|
if (df->first_statement > -(int)(sizeof(builtins)/sizeof(builtins[0])))
|
|
if (builtins[-df->first_statement].returns)
|
|
return type_names[(*builtins[-df->first_statement].returns)->type];
|
|
|
|
return "void"; //no returns statements found
|
|
}
|
|
|
|
if (rettypes[df - functions])
|
|
return rettypes[df - functions];
|
|
|
|
recursion++;
|
|
|
|
ds = statements + df->first_statement;
|
|
|
|
/*
|
|
* find a return statement, to determine the result type
|
|
*/
|
|
|
|
while (1)
|
|
{
|
|
dom = (ds->op) % OP_MARK_END_ELSE;
|
|
if (!dom)
|
|
break;
|
|
// if (dom == OPQF_RETURN_V)
|
|
// break;
|
|
if (dom == OP_RETURN)
|
|
{
|
|
if (ds->a != 0) //some code is buggy.
|
|
{
|
|
foundret = true;
|
|
|
|
if (recursion < 10)
|
|
{
|
|
ret = DecompileAgressiveType(df, ds-1, ds->a);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
if (((int*)pr_globals)[ds->a] < 0 && ((int*)pr_globals)[ds->a] >= strofs)
|
|
couldbeastring = false; //definatly not
|
|
else
|
|
{
|
|
char buf[64];
|
|
QC_snprintfz(buf, sizeof(buf), "%f", pr_globals[ds->a]);
|
|
if (strcmp(buf, "0.000000"))
|
|
couldbeastring = false; //doesn't fit the profile
|
|
}
|
|
}
|
|
|
|
}
|
|
ds++;
|
|
}
|
|
recursion--;
|
|
|
|
if (foundret)
|
|
{
|
|
if (!ret)
|
|
{
|
|
if (couldbeastring)
|
|
ret = "string /*WARNING: could not determine return type*/";
|
|
else
|
|
ret = "float /*WARNING: could not determine return type*/";
|
|
}
|
|
}
|
|
else
|
|
ret = "void"; //no returns statements found
|
|
|
|
rettypes[df - functions] = ret;
|
|
return ret;
|
|
}
|
|
|
|
void DecompileCalcProfiles(void)
|
|
{
|
|
|
|
int i, ps;
|
|
gofs_t j;
|
|
char *knew;
|
|
static char fname[512];
|
|
static char line[512];
|
|
dfunction_t *df;
|
|
QCC_ddef_t *par;
|
|
|
|
for (i = 1; i < numfunctions; i++)
|
|
{
|
|
|
|
df = functions + i;
|
|
fname[0] = '\0';
|
|
line[0] = '\0';
|
|
DecompileProfiles[i] = NULL;
|
|
|
|
if (df->first_statement <= 0)
|
|
{
|
|
if (-df->first_statement <= sizeof(builtins)/sizeof(builtins[0]) && builtins[-df->first_statement].text)
|
|
QC_snprintfz(fname, sizeof(fname), "%s %s", builtins[-df->first_statement].text, strings + functions[i].s_name);
|
|
else
|
|
{
|
|
QC_snprintfz(fname, sizeof(fname), "__variant(...) %s", strings + functions[i].s_name);
|
|
printf("warning: unknown builtin %s\n", strings + functions[i].s_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char *rettype;
|
|
|
|
rettype = DecompileReturnType(df);
|
|
if (!rettype)
|
|
{ //but we do know that it's not void
|
|
rettype = "float /*WARNING: could not determine return type*/";
|
|
}
|
|
strcpy(fname, rettype);
|
|
strcat(fname, "(");
|
|
|
|
/*
|
|
* determine overall parameter size
|
|
*/
|
|
|
|
for (j = 0, ps = 0; j < df->numparms; j++)
|
|
ps += df->parm_size[j];
|
|
|
|
if (ps > 0)
|
|
{
|
|
for (j = df->parm_start; j < (df->parm_start) + ps; j++)
|
|
{
|
|
line[0] = '\0';
|
|
par = DecompileGetParameter(j);
|
|
if (!par)
|
|
par = DecompileGetParameter((short)j);
|
|
|
|
if (!par)
|
|
{
|
|
//Error("Error - No parameter names with offset %i.", j);
|
|
// printf("No parameter names with offset %i\n", j);
|
|
if (j < (df->parm_start) + ps - 1)
|
|
QC_snprintfz(line, sizeof(line), "float par%i, ", j - df->parm_start);
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "float par%i", j - df->parm_start);
|
|
}
|
|
else
|
|
{
|
|
if (par->type == ev_vector)
|
|
j += 2;
|
|
if (j < (df->parm_start) + ps - 1)
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%s, ", DecompilePrintParameter(par));
|
|
}
|
|
else
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%s", DecompilePrintParameter(par));
|
|
}
|
|
}
|
|
strcat(fname, line);
|
|
}
|
|
}
|
|
strcat(fname, ") ");
|
|
line[0] = '\0';
|
|
QC_snprintfz(line, sizeof(line), strings + functions[i].s_name);
|
|
strcat(fname, line);
|
|
|
|
}
|
|
|
|
knew = (char *)malloc(strlen(fname) + 1);
|
|
strcpy(knew, fname);
|
|
DecompileProfiles[i] = knew;
|
|
}
|
|
|
|
}
|
|
|
|
QCC_ddef_t *GlobalAtOffset(dfunction_t *df, gofs_t ofs)
|
|
{
|
|
QCC_ddef_t *def;
|
|
int i, j;
|
|
|
|
def = globalofsdef[ofs];
|
|
if (def)
|
|
return def;
|
|
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
def = &globals[i];
|
|
|
|
if (def->ofs == ofs)
|
|
{
|
|
|
|
/*if (!strings[def->s_name])
|
|
{
|
|
char line[16];
|
|
char *buf;
|
|
|
|
sprintf(line, "_s_%i", def->ofs); //globals, which are defined after the locals of the function they are first used in...
|
|
buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well
|
|
strcpy(buf, line);
|
|
def->s_name = buf - strings;
|
|
}*/
|
|
globalofsdef[ofs] = def;
|
|
return def;
|
|
}
|
|
}
|
|
|
|
if (ofs >= df->parm_start && ofs < df->parm_start + df->locals)
|
|
{
|
|
static QCC_ddef_t parm[8];
|
|
static char *parmnames[] = {"par0","par1","par2","par3","par4","par5","par6","par7"};
|
|
int parmofs = ofs - df->parm_start;
|
|
for (i = 0; i < df->numparms && i < 8; i++)
|
|
{
|
|
if (parmofs < df->parm_size[i])
|
|
{
|
|
parm[i].ofs = ofs - parmofs;
|
|
parm[i].s_name = parmnames[i]-strings;
|
|
parm[i].type = ev_void;
|
|
|
|
ofs = parm[i].ofs;
|
|
for (j = 0; j < numglobaldefs; j++)
|
|
{
|
|
def = &globals[j];
|
|
if (def->ofs == ofs)
|
|
{
|
|
char line[256], *buf;
|
|
sprintf(line, "%s_%c", strings+def->s_name, 'x'+parmofs); //globals, which are defined after the locals of the function they are first used in...
|
|
def = malloc(sizeof(*def)+strlen(line)+1); //must be static variables, but we can't handle them very well
|
|
buf = (char*)(def+1);
|
|
strcpy(buf, line);
|
|
def->s_name = buf - strings;
|
|
def->type = ev_float;
|
|
return def;
|
|
}
|
|
}
|
|
|
|
return &parm[i];
|
|
}
|
|
parmofs -= df->parm_size[i];
|
|
}
|
|
//moo
|
|
}
|
|
//FIXME: if its within the current function's bounds, its:
|
|
// within param list: argument
|
|
// never written: immediate
|
|
// optimised: a local / locked temp.
|
|
// vanilla qcc: always a temp (other locals will be named)
|
|
//elsewhere:
|
|
// if its assigned to somewhere, then its a temp
|
|
// otherwise its a const.
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char *DecompileGlobal(dfunction_t *df, gofs_t ofs, QCC_type_t * req_t)
|
|
{
|
|
int i;
|
|
QCC_ddef_t *def;
|
|
static char line[8192];
|
|
char *res;
|
|
|
|
line[0] = '\0';
|
|
|
|
/*if (req_t == &def_short)
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%ii", ofs);
|
|
res = (char *)malloc(strlen(line) + 1);
|
|
strcpy(res, line);
|
|
return res;
|
|
}*/
|
|
|
|
def = GlobalAtOffset(df, ofs);
|
|
|
|
if (def)
|
|
{
|
|
|
|
if (!strcmp(strings + def->s_name, "IMMEDIATE") || !strcmp(strings + def->s_name, ".imm"))
|
|
{
|
|
etype_t ty;
|
|
if (!req_t)
|
|
ty = def->type;
|
|
else
|
|
{
|
|
ty = (etype_t)(req_t->type);
|
|
if (!ty)
|
|
ty = def->type;
|
|
}
|
|
QC_snprintfz(line, sizeof(line), "%s", DecompileValueString(ty, &pr_globals[def->ofs]));
|
|
}
|
|
else
|
|
{
|
|
if (!strings[def->s_name])
|
|
{
|
|
char line[16];
|
|
char *buf;
|
|
QCC_ddef_t *parent;
|
|
if (ofs >= df->parm_start && ofs < df->parm_start + df->locals)
|
|
goto lookslikealocal;
|
|
else if ((parent = GlobalAtOffset(df, ofs-1)) && parent->type == ev_vector)
|
|
{ // _y
|
|
QC_snprintfz(line, sizeof(line), "%s_y", strings+parent->s_name); //globals, which are defined after the locals of the function they are first used in...
|
|
buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well
|
|
strcpy(buf, line);
|
|
def->s_name = buf - strings;
|
|
}
|
|
else if ((parent = GlobalAtOffset(df, ofs-2)) && parent->type == ev_vector)
|
|
{ // _z
|
|
QC_snprintfz(line, sizeof(line), "%s_z", strings+parent->s_name); //globals, which are defined after the locals of the function they are first used in...
|
|
buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well
|
|
strcpy(buf, line);
|
|
def->s_name = buf - strings;
|
|
}
|
|
else
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "_sloc_%i", def->ofs); //globals, which are defined after the locals of the function they are first used in...
|
|
buf = malloc(strlen(line)+1); //must be static variables, but we can't handle them very well
|
|
strcpy(buf, line);
|
|
def->s_name = buf - strings;
|
|
}
|
|
}
|
|
|
|
QC_snprintfz(line, sizeof(line), "%s", strings + def->s_name);
|
|
if (def->type == ev_field && req_t == type_field && req_t->aux_type == type_float && DecompileGetFieldTypeByDef(def) == ev_vector)
|
|
strcat(line, "_x");
|
|
else if (def->type == ev_vector && req_t == type_float)
|
|
strcat(line, "_x");
|
|
|
|
}
|
|
res = (char *)malloc(strlen(line) + 1);
|
|
strcpy(res, line);
|
|
|
|
return res;
|
|
}
|
|
|
|
if (ofs >= df->parm_start && ofs < df->parm_start + df->locals)
|
|
{
|
|
int parmofs;
|
|
lookslikealocal:
|
|
QC_snprintfz(line, sizeof(line), "local_%i", ofs);
|
|
for (i = 0, parmofs = ofs - df->parm_start; i < df->numparms && i < 8; i++)
|
|
{
|
|
if (parmofs < df->parm_size[i])
|
|
{
|
|
if (parmofs)
|
|
QC_snprintfz(line, sizeof(line), "par%i_%c", i, 'x'+parmofs);
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "par%i", i);
|
|
break;
|
|
}
|
|
parmofs -= df->parm_size[i];
|
|
}
|
|
if (!assumelocals && i == df->numparms)
|
|
return NULL; //we don't know what this is. assume its a temp
|
|
|
|
res = (char *)malloc(strlen(line) + 1);
|
|
strcpy(res, line);
|
|
return res;
|
|
}
|
|
|
|
if (assumeglobals)
|
|
{ //unknown globals are normally assumed to be temps
|
|
if (ofs >= ofs_parms[7]+ofs_size)
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "tmp_%i", ofs);
|
|
res = (char *)malloc(strlen(line) + 1);
|
|
strcpy(res, line);
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct
|
|
{
|
|
char *text;
|
|
QCC_type_t *type;
|
|
} IMMEDIATES[MAX_REGS];
|
|
gofs_t DecompileScaleIndex(dfunction_t *df, gofs_t ofs)
|
|
{
|
|
gofs_t nofs = 0;
|
|
|
|
/*if (ofs > ofs_parms[7]+ofs_size)
|
|
nofs = ofs - df->parm_start + ofs_parms[7]+ofs_size;
|
|
else*/
|
|
nofs = ofs;
|
|
|
|
if ((nofs < 0) || (nofs > MAX_REGS - 1))
|
|
{
|
|
printf("Fatal Error - Index (%i) out of bounds.\n", nofs);
|
|
return 0;
|
|
exit(1);
|
|
}
|
|
|
|
return nofs;
|
|
}
|
|
|
|
void DecompileImmediate_Free(void)
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_REGS; i++)
|
|
{
|
|
if (IMMEDIATES[i].text)
|
|
{
|
|
free(IMMEDIATES[i].text);
|
|
IMMEDIATES[i].text = NULL;
|
|
}
|
|
}
|
|
}
|
|
void DecompileImmediate_Insert(dfunction_t *df, gofs_t ofs, char *knew, QCC_type_t *type)
|
|
{
|
|
QCC_ddef_t *d;
|
|
int nofs;
|
|
|
|
nofs = DecompileScaleIndex(df, ofs);
|
|
|
|
if (IMMEDIATES[nofs].text)
|
|
{
|
|
// fprintf(Decompileofile, "/*WARNING: Discarding \"%s\"/", IMMEDIATES[nofs]);
|
|
free(IMMEDIATES[nofs].text);
|
|
IMMEDIATES[nofs].text = NULL;
|
|
}
|
|
|
|
|
|
d = GlobalAtOffset(df, ofs);
|
|
if (d && d->s_name)// && strcmp(strings+d->s_name, "IMMEDIATE"))
|
|
{ //every operator has a src (or two) and a dest.
|
|
//many compilers optimise by using the dest of a maths/logic operator to store to a local/global
|
|
//they then skip off the storeopcode.
|
|
//without this, we would never see these stores.
|
|
IMMEDIATES[nofs].text = NULL;
|
|
IMMEDIATES[nofs].type = NULL;
|
|
|
|
QCC_CatVFile(Decompileofile, "%s = %s;\n", strings + d->s_name, knew);
|
|
}
|
|
else
|
|
{
|
|
IMMEDIATES[nofs].text = (char *)malloc(strlen(knew) + 1);
|
|
strcpy(IMMEDIATES[nofs].text, knew);
|
|
IMMEDIATES[nofs].type = type;
|
|
}
|
|
}
|
|
|
|
char *DecompileImmediate_Get(dfunction_t *df, gofs_t ofs, QCC_type_t *req_t)
|
|
{
|
|
char *res;
|
|
|
|
gofs_t nofs;
|
|
|
|
nofs = DecompileScaleIndex(df, ofs);
|
|
// printf("DecompileImmediate - Index scale: %i -> %i.\n", ofs, nofs);
|
|
|
|
// insert at nofs
|
|
if (IMMEDIATES[nofs].text)
|
|
{
|
|
|
|
// printf("DecompileImmediate - Reading \"%s\" at index %i.\n", IMMEDIATES[nofs], nofs);
|
|
|
|
if (IMMEDIATES[nofs].type == type_vector && req_t == type_float)
|
|
{
|
|
res = (char *)malloc(strlen(IMMEDIATES[nofs].text) + 4);
|
|
if (strchr(IMMEDIATES[nofs].text, '('))
|
|
sprintf(res, "%s[0]", IMMEDIATES[nofs].text);
|
|
else
|
|
sprintf(res, "%s_x", IMMEDIATES[nofs].text);
|
|
}
|
|
else
|
|
{
|
|
res = (char *)malloc(strlen(IMMEDIATES[nofs].text) + 1);
|
|
strcpy(res, IMMEDIATES[nofs].text);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
else
|
|
{ //you are now entering the hack zone.
|
|
char temp[8192];
|
|
|
|
switch(req_t?req_t->type:-1)
|
|
{
|
|
case ev_void: //for lack of any better ideas.
|
|
case ev_float:
|
|
if ((float)(int)pr_globals[ofs] == pr_globals[ofs])
|
|
QC_snprintfz(temp, sizeof(temp), "%i", (int)(pr_globals[ofs]));
|
|
else if ((*(int*)&pr_globals[ofs] & 0x7f800000) || !(*(int*)&pr_globals[ofs] & 0x7fffffff))
|
|
QC_snprintfz(temp, sizeof(temp), "%f", pr_globals[ofs]);
|
|
else
|
|
QC_snprintfz(temp, sizeof(temp), "%%%i", *(int*)&pr_globals[ofs]);
|
|
if (pr_globals[ofs] == 0 || ((int*)pr_globals)[ofs] < 0 || ((int*)pr_globals)[ofs] >= strofs || strcmp(temp, "0.000000"))
|
|
break;
|
|
// printf("Hey! That's not a float! error in %s\n", strings + df->s_name);
|
|
// printf("%f could be %s\n", pr_globals[ofs], &strings[((int*)pr_globals)[ofs]]);
|
|
case ev_string:
|
|
{
|
|
const char *in;
|
|
char *out;
|
|
if (((int*)pr_globals)[ofs] < 0 || ((int*)pr_globals)[ofs] > strofs)
|
|
{
|
|
printf("Hey! That's not a string! error in %s\n", strings + df->s_name);
|
|
QC_snprintfz(temp, sizeof(temp), "%f", pr_globals[ofs]);
|
|
break;
|
|
}
|
|
in = &strings[((int*)pr_globals)[ofs]];
|
|
out = temp;
|
|
if (req_t->type != ev_string)
|
|
{
|
|
QC_snprintfz(temp, sizeof(temp), "/*%i*/", ((int*)pr_globals)[ofs]);
|
|
out += strlen(out);
|
|
}
|
|
|
|
*out++ = '\"';
|
|
while (*in)
|
|
{
|
|
if (*in == '\"')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = '\"';
|
|
in++;
|
|
}
|
|
else if (*in == '\n')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = 'n';
|
|
in++;
|
|
}
|
|
else if (*in == '\\')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = '\\';
|
|
in++;
|
|
}
|
|
else if (*in == '\r')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = 'r';
|
|
in++;
|
|
}
|
|
else if (*in == '\a')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = 'a';
|
|
in++;
|
|
}
|
|
else if (*in == '\b')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = 'b';
|
|
in++;
|
|
}
|
|
else if (*in == '\f')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = 'f';
|
|
in++;
|
|
}
|
|
else if (*in == '\t')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = 't';
|
|
in++;
|
|
}
|
|
else if (*in == '\v')
|
|
{
|
|
*out++ = '\\';
|
|
*out++ = 'v';
|
|
in++;
|
|
}
|
|
else
|
|
*out++ = *in++;
|
|
}
|
|
*out++ = '\"';
|
|
*out++ = '\0';
|
|
}
|
|
break;
|
|
case ev_vector:
|
|
QC_snprintfz(temp, sizeof(temp), "\'%f %f %f\'", pr_globals[ofs],pr_globals[ofs+1],pr_globals[ofs+2]);
|
|
break;
|
|
// case ev_quat:
|
|
// QC_snprintfz(temp, sizeof(temp), "\'%f %f %f %f\'", pr_globals[ofs],pr_globals[ofs+1],pr_globals[ofs+2],pr_globals[ofs+3]);
|
|
// break;
|
|
case ev_integer:
|
|
QC_snprintfz(temp, sizeof(temp), "%ii", ((int*)pr_globals)[ofs]);
|
|
break;
|
|
// case ev_uinteger:
|
|
// QC_snprintfz(temp, sizeof(temp), "%uu", ((int*)pr_globals)[ofs]);
|
|
// break;
|
|
case ev_pointer:
|
|
QC_snprintfz(temp, sizeof(temp), "(__variant*)0x%xi", ((int*)pr_globals)[ofs]);
|
|
break;
|
|
case ev_function:
|
|
if (!((int*)pr_globals)[ofs])
|
|
QC_snprintfz(temp, sizeof(temp), "__NULL__/*func*/");
|
|
else if (((int*)pr_globals)[ofs] > 0 && ((int*)pr_globals)[ofs] < numfunctions && functions[((int*)pr_globals)[ofs]].s_name>0)
|
|
QC_snprintfz(temp, sizeof(temp), "%s/*immediate*/", strings+functions[((int*)pr_globals)[ofs]].s_name);
|
|
else
|
|
QC_snprintfz(temp, sizeof(temp), "((__variant(...))%i)", ((int*)pr_globals)[ofs]);
|
|
break;
|
|
case ev_entity:
|
|
if (!pr_globals[ofs])
|
|
QC_snprintfz(temp, sizeof(temp), "((entity)__NULL__)");
|
|
else
|
|
QC_snprintfz(temp, sizeof(temp), "(entity)%i", ((int*)pr_globals)[ofs]);
|
|
break;
|
|
case ev_field:
|
|
if (!pr_globals[ofs])
|
|
QC_snprintfz(temp, sizeof(temp), "((.void)__NULL__)");
|
|
else
|
|
QC_snprintfz(temp, sizeof(temp), "/*field %s*/%i", DecompileGetFieldNameIdxByFinalOffset(((int*)pr_globals)[ofs]), ((int*)pr_globals)[ofs]);
|
|
break;
|
|
default:
|
|
QC_snprintfz(temp, sizeof(temp), "FIXME");
|
|
break;
|
|
}
|
|
|
|
res = (char *)malloc(strlen(temp) + 1);
|
|
strcpy(res, temp);
|
|
|
|
return res;
|
|
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char *DecompileGet(dfunction_t *df, gofs_t ofs, QCC_type_t *req_t)
|
|
{
|
|
char *farg1;
|
|
/*if (req_t == &def_short)
|
|
{
|
|
char temp[16];
|
|
QC_snprintfz(temp, sizeof(temp), "%i", ofs);
|
|
return strdup(temp);
|
|
}*/
|
|
farg1 = NULL;
|
|
|
|
farg1 = DecompileGlobal(df, ofs, req_t);
|
|
|
|
if (farg1 == NULL)
|
|
farg1 = DecompileImmediate_Get(df, ofs, req_t);
|
|
|
|
return farg1;
|
|
}
|
|
|
|
void DecompilePrintStatement(dstatement_t *s);
|
|
|
|
void DecompileIndent(int c)
|
|
{
|
|
int i;
|
|
|
|
if (c < 0)
|
|
c = 0;
|
|
|
|
for (i = 0; i < c; i++)
|
|
{
|
|
QCC_CatVFile(Decompileofile, "\t");
|
|
}
|
|
}
|
|
|
|
void DecompileOpcode(dfunction_t *df, int a, int b, int c, char *opcode, QCC_type_t *typ1, QCC_type_t *typ2, QCC_type_t *typ3, int usebrackets, int *indent)
|
|
{
|
|
static char line[512];
|
|
char *arg1, *arg2, *arg3;
|
|
arg1 = DecompileGet(df, a, typ1);
|
|
arg2 = DecompileGet(df, b, typ2);
|
|
arg3 = DecompileGlobal(df, c, typ3);
|
|
|
|
if (arg3)
|
|
{
|
|
DecompileIndent(*indent);
|
|
if (usebrackets)
|
|
QCC_CatVFile(Decompileofile, "%s = %s %s %s;\n", arg3, arg1, opcode, arg2);
|
|
else
|
|
QCC_CatVFile(Decompileofile, "%s = %s%s%s;\n", arg3, arg1, opcode, arg2);
|
|
}
|
|
else
|
|
{
|
|
if (usebrackets)
|
|
QC_snprintfz(line, sizeof(line), "(%s %s %s)", arg1, opcode, arg2);
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "%s%s%s", arg1, opcode, arg2);
|
|
DecompileImmediate_Insert(df, c, line, typ3);
|
|
}
|
|
}
|
|
|
|
void DecompileDecompileStatement(dfunction_t * df, dstatement_t * s, int *indent)
|
|
{
|
|
static char line[8192];
|
|
static char fnam[512];
|
|
char *arg1, *arg2, *arg3;
|
|
int nargs, i, j;
|
|
dstatement_t *t;
|
|
unsigned int dom, doc, ifc, tom;
|
|
QCC_type_t *typ1, *typ2, *typ3;
|
|
QCC_ddef_t *par;
|
|
dstatement_t *k;
|
|
int dum;
|
|
|
|
|
|
arg1 = arg2 = arg3 = NULL;
|
|
|
|
line[0] = '\0';
|
|
fnam[0] = '\0';
|
|
|
|
dom = s->op;
|
|
|
|
doc = dom / OP_MARK_END_DO;
|
|
ifc = (dom % OP_MARK_END_DO) / OP_MARK_END_ELSE;
|
|
|
|
// use program flow information
|
|
|
|
for (i = 0; i < ifc; i++)
|
|
{
|
|
(*indent)--;
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "}\n");//FrikaC style modification
|
|
}
|
|
for (i = 0; i < doc; i++)
|
|
{
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "do\n");
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
(*indent)++;
|
|
}
|
|
|
|
/*
|
|
* remove all program flow information
|
|
*/
|
|
s->op %= OP_MARK_END_ELSE;
|
|
typ1 = pr_opcodes[s->op].type_a?*pr_opcodes[s->op].type_a:NULL;
|
|
typ2 = pr_opcodes[s->op].type_b?*pr_opcodes[s->op].type_b:NULL;
|
|
typ3 = pr_opcodes[s->op].type_c?*pr_opcodes[s->op].type_c:NULL;
|
|
|
|
/*
|
|
* printf("DecompileDecompileStatement - decompiling %i (%i):\n",(int)(s - statements),dom);
|
|
* DecompilePrintStatement (s);
|
|
*/
|
|
/*
|
|
* states are handled at top level
|
|
*/
|
|
if (s->op == OP_DONE)
|
|
{
|
|
|
|
}
|
|
else if (s->op == OP_BOUNDCHECK)
|
|
{
|
|
/*these are auto-generated as a sideeffect. currently there is no syntax to explicitly use one (other than asm), but we don't want to polute the code when they're autogenerated, so ditch them all*/
|
|
}
|
|
else if (s->op == OP_STATE)
|
|
{
|
|
|
|
par = DecompileGetParameter(s->a);
|
|
if (!par)
|
|
{
|
|
printf("Error - Can't determine frame number.\n");
|
|
exit(1);
|
|
}
|
|
arg2 = DecompileGet(df, s->b, NULL);
|
|
if (!arg2)
|
|
{
|
|
printf("Error - No state parameter with offset %i.\n", s->b);
|
|
exit(1);
|
|
}
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "state [ %s, %s ];\n", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs]), arg2);
|
|
|
|
// free(arg2);
|
|
}
|
|
else if (s->op == OP_RETURN/* || s->op == OPQF_RETURN_V*/)
|
|
{
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "return");
|
|
|
|
if (s->a)
|
|
{
|
|
QCC_CatVFile(Decompileofile, " ");
|
|
arg1 = DecompileGet(df, s->a, type_void); //FIXME: we should know the proper type better than this.
|
|
QCC_CatVFile(Decompileofile, "(%s)", arg1);
|
|
}
|
|
QCC_CatVFile(Decompileofile, ";\n");
|
|
|
|
}
|
|
else if ((OP_MUL_F <= s->op && s->op <= OP_SUB_V) ||
|
|
(OP_EQ_F <= s->op && s->op <= OP_GT_F) ||
|
|
(OP_AND_F <= s->op && s->op <= OP_BITOR_F)
|
|
/*|| pr_opcodes[s->op].associative == ASSOC_LEFT*/)
|
|
{
|
|
DecompileOpcode(df, s->a, s->b, s->c, pr_opcodes[s->op].name, typ1, typ2, typ3, true, indent);
|
|
}
|
|
else if (OP_GLOBALADDRESS == s->op)
|
|
{
|
|
arg1 = DecompileGet(df, s->a, typ1);
|
|
arg2 = DecompileGet(df, s->b, typ2);
|
|
arg3 = DecompileGlobal(df, s->c, typ3);
|
|
|
|
if (arg3)
|
|
{
|
|
DecompileIndent(*indent);
|
|
if (s->b)
|
|
QCC_CatVFile(Decompileofile, "%s = &%s[%s];\n", arg3, arg1, arg2);
|
|
else
|
|
QCC_CatVFile(Decompileofile, "%s = &%s;\n", arg3, arg1);
|
|
}
|
|
else
|
|
{
|
|
if (s->b)
|
|
QC_snprintfz(line, sizeof(line), "%s[%s]", arg1, arg2);
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "%s", arg1);
|
|
DecompileImmediate_Insert(df, s->c, line, typ3);
|
|
}
|
|
}
|
|
else if ((OP_LOAD_F <= s->op && s->op <= OP_ADDRESS) || s->op == OP_LOAD_P || s->op == OP_LOAD_I)
|
|
{
|
|
if (s->op == OP_ADDRESS)
|
|
{
|
|
QCC_ddef_t *def = GlobalAtOffset(df, s->b);
|
|
if (def && DecompileGetFieldTypeByDef(def) == ev_vector)
|
|
typ3 = type_vector;
|
|
}
|
|
|
|
type_field->aux_type = typ3;
|
|
DecompileOpcode(df, s->a, s->b, s->c, ".", typ1, typ2, typ3, false, indent);
|
|
type_field->aux_type = NULL;
|
|
}
|
|
else if ((OP_LOADA_F <= s->op && s->op <= OP_LOADA_I))// || (OPQF_LOADBI_F <= s->op && s->op <= OPQF_LOADBI_P))
|
|
{
|
|
static char line[512];
|
|
char *arg1, *arg2, *arg3;
|
|
arg1 = DecompileGet(df, s->a, typ1);
|
|
arg2 = DecompileGet(df, s->b, typ2);
|
|
arg3 = DecompileGlobal(df, s->c, typ3);
|
|
|
|
if (arg3)
|
|
{
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "%s = %s[%s];\n", arg3, arg1, arg2);
|
|
}
|
|
else
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%s[%s]", arg1, arg2);
|
|
DecompileImmediate_Insert(df, s->c, line, typ3);
|
|
}
|
|
}
|
|
else if ((OP_STORE_F <= s->op && s->op <= OP_STORE_FNC) || s->op == OP_STORE_I || s->op == OP_STORE_P)
|
|
{
|
|
QCC_type_t *parmtype=NULL;
|
|
if (s->b >= ofs_parms[0] && s->b < ofs_parms[7]+ofs_size)
|
|
{ //okay, so typ1 might not be what the store type says it should be.
|
|
k = s+1;
|
|
while(k->op%OP_MARK_END_ELSE)
|
|
{
|
|
if ((k->op >= OP_CALL0 && k->op <= OP_CALL8) || (k->op >= OP_CALL1H && k->op <= OP_CALL8H))
|
|
{
|
|
//well, this is it.
|
|
int fn;
|
|
int pn;
|
|
dfunction_t *cf;
|
|
QCC_ddef_t *def;
|
|
fn = ((int*)pr_globals)[k->a];
|
|
cf = &functions[fn];
|
|
if (cf->first_statement<=0 && cf->first_statement > -(signed)(sizeof(builtins)/sizeof(builtins[0]))) //builtins don't have this info.
|
|
{
|
|
QCC_type_t **p = builtins[-cf->first_statement].params[(s->b-ofs_parms[0])/ofs_size];
|
|
parmtype = p?*p:NULL;
|
|
}
|
|
else
|
|
{
|
|
fn = cf->parm_start;
|
|
for (pn = 0; pn < (s->b-ofs_parms[0])/ofs_size; pn++)
|
|
fn += cf->parm_size[pn];
|
|
|
|
def = DecompileGetParameter(fn);
|
|
if (def)
|
|
{
|
|
switch(def->type)
|
|
{
|
|
case ev_float:
|
|
parmtype = type_float;
|
|
break;
|
|
case ev_string:
|
|
parmtype = type_string;
|
|
break;
|
|
case ev_vector:
|
|
parmtype = type_vector;
|
|
break;
|
|
case ev_entity:
|
|
parmtype = type_entity;
|
|
break;
|
|
case ev_function:
|
|
parmtype = type_function;
|
|
break;
|
|
case ev_integer:
|
|
parmtype = type_integer;
|
|
break;
|
|
// case ev_uinteger:
|
|
// parmtype = type_uinteger;
|
|
// break;
|
|
// case ev_quat:
|
|
// parmtype = type_quat;
|
|
// break;
|
|
default:
|
|
// parmtype = type_float;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (OP_STORE_F <= s->op && s->op <= OP_STORE_FNC)
|
|
{
|
|
if (k->b < s->b) //whoops... older QCCs can nest things awkwardly.
|
|
break;
|
|
}
|
|
k++;
|
|
}
|
|
}
|
|
|
|
if (parmtype)
|
|
arg1 = DecompileGet(df, s->a, parmtype);
|
|
else
|
|
arg1 = DecompileGet(df, s->a, typ1);
|
|
arg3 = DecompileGlobal(df, s->b, typ2);
|
|
|
|
if (arg3)
|
|
{
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "%s = %s;\n", arg3, arg1);
|
|
}
|
|
else
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%s", arg1);
|
|
DecompileImmediate_Insert(df, s->b, line, NULL);
|
|
}
|
|
|
|
}
|
|
else if (OP_STOREP_F <= s->op && s->op <= OP_STOREP_FNC)
|
|
{
|
|
arg1 = DecompileGet(df, s->a, typ2);
|
|
//FIXME: we need to deal with ref types and other crazyness, so we know whether we need to add * or *& or if we can skip that completely
|
|
arg2 = DecompileGet(df, s->b, typ2);
|
|
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "%s = %s;\n", arg2, arg1);
|
|
|
|
}
|
|
else if (OP_CONV_FTOI == s->op)
|
|
{
|
|
|
|
arg1 = DecompileGet(df, s->a, typ1);
|
|
QC_snprintfz(line, sizeof(line), "(int)%s", arg1);
|
|
DecompileImmediate_Insert(df, s->c, line, type_integer);
|
|
|
|
}
|
|
else if (OP_RAND0 == s->op)
|
|
{
|
|
DecompileImmediate_Insert(df, ofs_return, "random()", type_float);
|
|
}
|
|
else if (OP_RAND1 == s->op)
|
|
{
|
|
arg1 = DecompileGet(df, s->a, typ1);
|
|
QC_snprintfz(line, sizeof(line), "random(%s)", arg1);
|
|
DecompileImmediate_Insert(df, ofs_return, line, type_float);
|
|
}
|
|
else if (OP_RAND2 == s->op)
|
|
{
|
|
arg1 = DecompileGet(df, s->a, typ1);
|
|
arg2 = DecompileGet(df, s->b, typ2);
|
|
QC_snprintfz(line, sizeof(line), "random(%s, %s)", arg1, arg2);
|
|
DecompileImmediate_Insert(df, ofs_return, line, type_float);
|
|
}
|
|
else if (OP_NOT_F <= s->op && s->op <= OP_NOT_FNC)
|
|
{
|
|
|
|
arg1 = DecompileGet(df, s->a, typ1);
|
|
QC_snprintfz(line, sizeof(line), "!%s", arg1);
|
|
DecompileImmediate_Insert(df, s->c, line, type_float);
|
|
|
|
}
|
|
else if ((OP_CALL0 <= s->op && s->op <= OP_CALL8) || (OP_CALL1H <= s->op && s->op <= OP_CALL8H))
|
|
{
|
|
if (OP_CALL1H <= s->op && s->op <= OP_CALL8H)
|
|
nargs = (s->op - OP_CALL1H) + 1;
|
|
else
|
|
nargs = s->op - OP_CALL0;
|
|
|
|
arg1 = DecompileGet(df, s->a, type_function);
|
|
QC_snprintfz(line, sizeof(line), "%s(", arg1);
|
|
QC_snprintfz(fnam, sizeof(fnam), "%s", arg1);
|
|
|
|
for (i = 0; i < nargs; i++)
|
|
{
|
|
|
|
typ1 = NULL;
|
|
|
|
if (i == 0 && OP_CALL1H <= s->op && s->op <= OP_CALL8H)
|
|
j = s->b;
|
|
else if (i == 1 && OP_CALL1H <= s->op && s->op <= OP_CALL8H)
|
|
j = s->c;
|
|
else
|
|
j = ofs_parms[i];
|
|
|
|
if (arg1)
|
|
free(arg1);
|
|
|
|
arg1 = DecompileGet(df, (gofs_t)j, typ1);
|
|
strcat(line, arg1);
|
|
|
|
if (i < nargs - 1)
|
|
strcat(line, ", ");//frikqcc modified
|
|
}
|
|
|
|
strcat(line, ")");
|
|
DecompileImmediate_Insert(df, ofs_return, line, NULL);
|
|
|
|
/*
|
|
* if ( ( ( (s+1)->a != 1) && ( (s+1)->b != 1) &&
|
|
* ( (s+2)->a != 1) && ( (s+2)->b != 1) ) ||
|
|
* ( ((s+1)->op) % OP_MARK_END_ELSE == OP_CALL0 ) ) {
|
|
* DecompileIndent(*indent);
|
|
* fprintf(Decompileofile,"%s;\n",line);
|
|
* }
|
|
*/
|
|
|
|
if ((((s + 1)->a != ofs_return) && ((s + 1)->b != ofs_return) &&
|
|
((s + 2)->a != ofs_return) && ((s + 2)->b != ofs_return)) ||
|
|
((((s + 1)->op) % OP_MARK_END_ELSE == OP_CALL0) && ((((s + 2)->a != ofs_return)) || ((s + 2)->b != ofs_return))))
|
|
{
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "%s;\n", line);
|
|
}
|
|
}
|
|
else if (s->op == OP_IF_I || s->op == OP_IFNOT_I ||
|
|
s->op == OP_IF_F || s->op == OP_IFNOT_F ||
|
|
s->op == OP_IF_S || s->op == OP_IFNOT_S/* || s->op == OPQF_IFA || s->op == OPQF_IFB || s->op == OPQF_IFAE || s->op == OPQF_IFBE*/)
|
|
{
|
|
|
|
arg1 = DecompileGet(df, s->a, type_float); //FIXME: this isn't quite accurate...
|
|
arg2 = DecompileGlobal(df, s->a, NULL);
|
|
|
|
if (s->op == OP_IFNOT_I || s->op == OP_IFNOT_F || s->op == OP_IFNOT_S)
|
|
{
|
|
lameifnot:
|
|
if ((signed int)s->b < 1)
|
|
{
|
|
// if (arg1)
|
|
// free(arg1);
|
|
// if (arg2)
|
|
// free(arg2);
|
|
// if (arg3)
|
|
// free(arg3);
|
|
|
|
return;
|
|
|
|
|
|
printf("Found a negative IFNOT jump.\n");
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* get instruction right before the target
|
|
*/
|
|
t = s + (signed int)s->b - 1;
|
|
tom = t->op % OP_MARK_END_ELSE;
|
|
|
|
if (tom != OP_GOTO)
|
|
{
|
|
|
|
/*
|
|
* pure if
|
|
*/
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
|
|
(*indent)++;
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
if ((signed int)t->a > 0)
|
|
{
|
|
/*
|
|
* ite
|
|
*/
|
|
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
|
|
(*indent)++;
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
|
|
if (((signed int)t->a + (signed int)s->b) > 1)
|
|
{
|
|
/*
|
|
* pure if
|
|
*/
|
|
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
(*indent)++;
|
|
}
|
|
else
|
|
{
|
|
|
|
dum = 1;
|
|
for (k = t + (signed int)(t->a); k < s; k++)
|
|
{
|
|
tom = k->op % OP_MARK_END_ELSE;
|
|
if (tom == OP_GOTO ||
|
|
tom == OP_IF_I || tom == OP_IFNOT_I ||
|
|
tom == OP_IF_F || tom == OP_IFNOT_F ||
|
|
tom == OP_IF_S || tom == OP_IFNOT_S)
|
|
dum = 0;
|
|
}
|
|
if (dum)
|
|
{
|
|
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "while (%s)\n", arg1);
|
|
DecompileIndent(*indent); //FrikaC
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
(*indent)++;
|
|
}
|
|
else
|
|
{
|
|
|
|
DecompileIndent(*indent);
|
|
|
|
|
|
QCC_CatVFile(Decompileofile, "if (%s)\n", arg1);//FrikaC modified
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
(*indent)++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*else if (s->op == OPQF_IFA)
|
|
{
|
|
char *t = arg1;
|
|
arg1 = malloc(strlen(arg1)+8);
|
|
sprintf(arg1, "(%s) <= 0", t);
|
|
free(t);
|
|
goto lameifnot;
|
|
}
|
|
else if (s->op == OPQF_IFAE)
|
|
{
|
|
char *t = arg1;
|
|
arg1 = malloc(strlen(arg1)+7);
|
|
sprintf(arg1, "(%s) < 0", t);
|
|
free(t);
|
|
goto lameifnot;
|
|
}
|
|
else if (s->op == OPQF_IFB)
|
|
{
|
|
char *t = arg1;
|
|
arg1 = malloc(strlen(arg1)+8);
|
|
sprintf(arg1, "(%s) >= 0", t);
|
|
free(t);
|
|
goto lameifnot;
|
|
}
|
|
else if (s->op == OPQF_IFBE)
|
|
{
|
|
char *t = arg1;
|
|
arg1 = malloc(strlen(arg1)+7);
|
|
sprintf(arg1, "(%s) > 0", t);
|
|
free(t);
|
|
goto lameifnot;
|
|
}*/
|
|
else
|
|
{
|
|
if ((signed int)s->b>0)
|
|
{
|
|
char *t = arg1;
|
|
//if (!...)
|
|
|
|
arg1 = malloc(strlen(arg1)+2);
|
|
sprintf(arg1, "!%s", t);
|
|
free(t);
|
|
goto lameifnot;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* do ... while
|
|
*/
|
|
|
|
(*indent)--;
|
|
QCC_CatVFile(Decompileofile, "\n");
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "} while (%s);\n", arg1);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else if (s->op == OPD_GOTO_FORSTART)
|
|
{
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "do_tail\n", (s-statements) + (signed int)s->a);
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
(*indent)++;
|
|
}
|
|
else if (s->op == OPD_GOTO_WHILE1)
|
|
{
|
|
(*indent)--;
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "} while(1);\n");
|
|
}
|
|
else if (s->op == OP_GOTO)
|
|
{
|
|
|
|
if ((signed int)s->a > 0)
|
|
{
|
|
/*
|
|
* else
|
|
*/
|
|
(*indent)--;
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "}\n");
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "else\n");
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "{\n");
|
|
(*indent)++;
|
|
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* while
|
|
*/
|
|
(*indent)--;
|
|
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "}\n");
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
int op = s->op%OP_MARK_END_ELSE;
|
|
if (op <= OP_BITOR_F && pr_opcodes[s->op].opname)
|
|
printf("warning: Unknown usage of OP_%s", pr_opcodes[s->op].opname);
|
|
else
|
|
{
|
|
DecompileIndent(*indent);
|
|
QCC_CatVFile(Decompileofile, "[OP_%s", pr_opcodes[op].opname);
|
|
if (s->a)
|
|
QCC_CatVFile(Decompileofile, ", %s", DecompileGet(df, s->a, typ1));
|
|
if (s->b)
|
|
QCC_CatVFile(Decompileofile, ", %s", DecompileGet(df, s->b, typ1));
|
|
if (s->c)
|
|
QCC_CatVFile(Decompileofile, ", %s", DecompileGet(df, s->c, typ1));
|
|
QCC_CatVFile(Decompileofile, "]\n");
|
|
printf("warning: Unknown opcode %i in %s\n", op, strings + df->s_name);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// printf("DecompileDecompileStatement - Current line is \"%s\"\n", line);
|
|
|
|
|
|
if (arg1)
|
|
free(arg1);
|
|
if (arg2)
|
|
free(arg2);
|
|
if (arg3)
|
|
free(arg3);
|
|
|
|
return;
|
|
}
|
|
|
|
boolean DecompileDecompileFunction(dfunction_t * df, dstatement_t *altdone)
|
|
{
|
|
dstatement_t *ds;
|
|
int indent;
|
|
|
|
|
|
// Initialize
|
|
|
|
DecompileImmediate_Free();
|
|
|
|
indent = 1;
|
|
|
|
ds = statements + df->first_statement;
|
|
if(ds->op == OP_STATE)
|
|
ds++;
|
|
while (1)
|
|
{
|
|
if (ds == altdone)
|
|
{
|
|
//decompile the dummy done, cos we can
|
|
DecompileDecompileStatement(df, statements, &indent);
|
|
break;
|
|
}
|
|
DecompileDecompileStatement(df, ds, &indent);
|
|
if (!ds->op)
|
|
break;
|
|
ds++;
|
|
}
|
|
|
|
if (indent != 1)
|
|
{
|
|
printf("warning: Indentation structure corrupt (in func %s)\n", strings+df->s_name);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
char *DecompileString(int qcstring)
|
|
{
|
|
static char buf[8192];
|
|
char *s;
|
|
int c = 1;
|
|
const char *string = strings+qcstring;
|
|
if (qcstring < 0 || qcstring >= strofs)
|
|
return "Invalid String";
|
|
|
|
s = buf;
|
|
*s++ = '"';
|
|
while (string && *string)
|
|
{
|
|
if (c == sizeof(buf) - 2)
|
|
break;
|
|
if (*string == '\n')
|
|
{
|
|
*s++ = '\\';
|
|
*s++ = 'n';
|
|
c++;
|
|
}
|
|
else if (*string == '"')
|
|
{
|
|
*s++ = '\\';
|
|
*s++ = '"';
|
|
c++;
|
|
}
|
|
else
|
|
{
|
|
*s++ = *string;
|
|
c++;
|
|
}
|
|
string++;
|
|
if (c > (int)(sizeof(buf) - 10))
|
|
{
|
|
*s++ = '.';
|
|
*s++ = '.';
|
|
*s++ = '.';
|
|
c += 3;
|
|
break;
|
|
}
|
|
}
|
|
*s++ = '"';
|
|
*s++ = 0;
|
|
return buf;
|
|
}
|
|
|
|
char *DecompileValueString(etype_t type, void *val)
|
|
{
|
|
static char line[8192];
|
|
|
|
line[0] = '\0';
|
|
|
|
switch (type)
|
|
{
|
|
case ev_string:
|
|
QC_snprintfz(line, sizeof(line), "%s", DecompileString(*(int *)val));
|
|
break;
|
|
case ev_void:
|
|
QC_snprintfz(line, sizeof(line), "void");
|
|
break;
|
|
case ev_float:
|
|
if (*(float *)val > 999999 || *(float *)val < -999999) // ugh
|
|
QC_snprintfz(line, sizeof(line), "%.f", *(float *)val);
|
|
else if ((!(*(int*)val & 0x7f800000) || (*(int*)val & 0x7f800000)==0x7f800000) && (*(int*)val & 0x7fffffff))
|
|
QC_snprintfz(line, sizeof(line), "%%%i", *(int*)val);
|
|
else if ((*(float *)val < 0.001) && (*(float *)val > 0))
|
|
QC_snprintfz(line, sizeof(line), "%.6f", *(float *)val);
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "%g", *(float *)val);
|
|
break;
|
|
case ev_vector:
|
|
QC_snprintfz(line, sizeof(line), "'%g %g %g'", ((float *)val)[0], ((float *)val)[1], ((float *)val)[2]);
|
|
break;
|
|
// case ev_quat:
|
|
// QC_snprintfz(line, sizeof(line), "'%g %g %g %g'", ((float *)val)[0], ((float *)val)[1], ((float *)val)[2], ((float *)val)[3]);
|
|
// break;
|
|
case ev_field:
|
|
DecompileGetFieldNameIdxByFinalOffset2(line, sizeof(line), *(int *)val);
|
|
break;
|
|
case ev_entity:
|
|
QC_snprintfz(line, sizeof(line), "(entity)%ii", *(int *)val);
|
|
break;
|
|
case ev_integer:
|
|
QC_snprintfz(line, sizeof(line), "%ii", *(int *)val);
|
|
break;
|
|
// case ev_uinteger:
|
|
// QC_snprintfz(line, sizeof(line), "%uu", *(int *)val);
|
|
// break;
|
|
case ev_pointer:
|
|
QC_snprintfz(line, sizeof(line), "(__variant*)0x%xi", *(int *)val);
|
|
break;
|
|
case ev_function:
|
|
if (*(int *)val>0 && *(int *)val<numfunctions)
|
|
QC_snprintfz(line, sizeof(line), "(/*func 0x%x*/%s)", *(int *)val, strings+functions[*(int *)val].s_name);
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "((void())0x%xi)", *(int *)val);
|
|
break;
|
|
default:
|
|
QC_snprintfz(line, sizeof(line), "bad type %i", type);
|
|
break;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
char *DecompilePrintParameter(QCC_ddef_t * def)
|
|
{
|
|
static char line[128];
|
|
static char debug[128];
|
|
|
|
line[0] = '0';
|
|
|
|
if (debug_offs)
|
|
{
|
|
QC_snprintfz(debug, sizeof(debug), " /*@%i*/", def->ofs);
|
|
}
|
|
else
|
|
*debug = 0;
|
|
|
|
if (!strings[def->s_name]) //null string...
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%s _p_%i%s", type_name(def), def->ofs, debug);
|
|
}
|
|
else if (!strcmp(strings + def->s_name, "IMMEDIATE") || !strcmp(strings + def->s_name, ".imm"))
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%s%s", DecompileValueString((etype_t)(def->type), &pr_globals[def->ofs]), debug);
|
|
}
|
|
else
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "%s %s%s", type_name(def), strings + def->s_name, debug);
|
|
}
|
|
return line;
|
|
}
|
|
|
|
//we only work with prior fields.
|
|
const char *GetMatchingField(QCC_ddef_t *field)
|
|
{
|
|
int i;
|
|
QCC_ddef_t *def;
|
|
int ld, lf;
|
|
|
|
def = NULL;
|
|
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
def = &globals[i];
|
|
|
|
if ((def->type&~DEF_SAVEGLOBAL) == ev_field)
|
|
{
|
|
if (((int*)pr_globals)[def->ofs] == field->ofs)
|
|
{
|
|
if (!strcmp(strings+def->s_name, strings+field->s_name))
|
|
break; //found ourself, give up.
|
|
lf = strlen(strings + field->s_name);
|
|
ld = strlen(strings + def->s_name);
|
|
if (lf - 2 == ld)
|
|
{
|
|
if ((strings + field->s_name)[lf-2] == '_')
|
|
if (!strncmp(strings + field->s_name, strings + def->s_name, ld))
|
|
break;
|
|
}
|
|
return def->s_name+strings;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
QCC_ddef_t *GetField(const char *name)
|
|
{
|
|
int i;
|
|
QCC_ddef_t *d;
|
|
if (!*name)
|
|
return NULL;
|
|
|
|
if (*name == '.')
|
|
name++; //some idiot _intentionally_ decided to fuck shit up. go them!
|
|
|
|
for (i = 0; i < numfielddefs; i++)
|
|
{
|
|
d = &fields[i];
|
|
|
|
if (!strcmp(strings + d->s_name, name))
|
|
return d;
|
|
}
|
|
return NULL;
|
|
}
|
|
QCC_ddef_t *DecompileGetParameter(gofs_t ofs)
|
|
{
|
|
int i;
|
|
QCC_ddef_t *def;
|
|
|
|
def = NULL;
|
|
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
def = &globals[i];
|
|
|
|
if (def->ofs == ofs)
|
|
{
|
|
return def;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
QCC_ddef_t *DecompileFindGlobal(const char *findname)
|
|
{
|
|
int i;
|
|
QCC_ddef_t *def;
|
|
const char *defname;
|
|
|
|
def = NULL;
|
|
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
def = &globals[i];
|
|
defname = strings + def->s_name;
|
|
|
|
if (!strcmp(findname, defname))
|
|
{
|
|
return def;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
QCC_ddef_t *DecompileFunctionGlobal(int funcnum)
|
|
{
|
|
int i;
|
|
QCC_ddef_t *def;
|
|
|
|
def = NULL;
|
|
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
def = &globals[i];
|
|
|
|
if (def->type == ev_function)
|
|
{
|
|
if (((int*)pr_globals)[def->ofs] == funcnum)
|
|
{
|
|
return def;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void DecompileFunction(const char *name, int *lastglobal)
|
|
{
|
|
int i, findex, ps;
|
|
dstatement_t *ds, *ts, *altdone;
|
|
dfunction_t *df;
|
|
QCC_ddef_t *par;
|
|
char *arg2;
|
|
unsigned short dom, tom;
|
|
int j, start, end;
|
|
QCC_ddef_t *ef;
|
|
static char line[8192];
|
|
dstatement_t *k;
|
|
int dum;
|
|
size_t startpos;
|
|
|
|
const char *matchingfield;
|
|
|
|
for (i = 1; i < numfunctions; i++)
|
|
if (!strcmp(name, strings + functions[i].s_name))
|
|
break;
|
|
if (i == numfunctions)
|
|
{
|
|
printf("Fatal Error: No function named \"%s\"\n", name);
|
|
exit(1);
|
|
}
|
|
df = functions + i;
|
|
altdone = statements + numstatements;
|
|
for (j = i+1; j < numfunctions; j++)
|
|
{
|
|
if (functions[j].first_statement <= 0)
|
|
continue;
|
|
altdone = statements + functions[j].first_statement;
|
|
break;
|
|
}
|
|
|
|
findex = i;
|
|
|
|
start = *lastglobal;
|
|
|
|
// if (dfpred->first_statement <= 0 && df->first_statement > 0)
|
|
// start -= 1;
|
|
|
|
end = df->parm_start;
|
|
if (!end)
|
|
{
|
|
par = DecompileFindGlobal(name);
|
|
if (par)
|
|
end = par - globals;
|
|
}
|
|
*lastglobal = max(*lastglobal, end + df->locals);
|
|
|
|
//print globals leading up to the function.
|
|
for (j = start; j < end; j++)
|
|
{
|
|
|
|
par = DecompileGetParameter((gofs_t)j);
|
|
|
|
if (par)
|
|
{
|
|
if (par->type & DEF_SAVEGLOBAL)
|
|
par->type -= DEF_SAVEGLOBAL;
|
|
|
|
if (par->type == ev_function)
|
|
{
|
|
if (strcmp(strings + par->s_name, "IMMEDIATE") && strcmp(strings + par->s_name, ".imm"))
|
|
{
|
|
if (strcmp(strings + par->s_name, name))
|
|
{
|
|
int f = ((int*)pr_globals)[par->ofs];
|
|
//DecompileGetFunctionIdxByName(strings + par->s_name);
|
|
if (f && strcmp(strings+functions[f].s_name, strings + par->s_name))
|
|
{
|
|
char *s = strrchr(DecompileProfiles[f], ' ');
|
|
//happens with void() func = otherfunc;
|
|
//such functions thus don't have their own type+body
|
|
*s = 0;
|
|
QCC_CatVFile(Decompileofile, "var %s %s = %s;\n", DecompileProfiles[f], strings + par->s_name, s+1);
|
|
*s = ' ';
|
|
}
|
|
else
|
|
QCC_CatVFile(Decompileofile, "%s;\n", DecompileProfiles[f]);
|
|
}
|
|
}
|
|
}
|
|
else if (par->type != ev_pointer)
|
|
{
|
|
if (strcmp(strings + par->s_name, "IMMEDIATE") && strcmp(strings + par->s_name, ".imm"))
|
|
{
|
|
|
|
if (par->type == ev_field)
|
|
{
|
|
|
|
ef = GetField(strings + par->s_name);
|
|
|
|
if (!ef)
|
|
{
|
|
QCC_CatVFile(Decompileofile, "var .unknowntype %s;\n", strings + par->s_name);
|
|
printf("Fatal Error: Could not locate a field named \"%s\"\n", strings + par->s_name);
|
|
}
|
|
else
|
|
{
|
|
//if (ef->type == ev_vector)
|
|
// j += 2;
|
|
|
|
matchingfield = GetMatchingField(ef);
|
|
|
|
#ifndef DONT_USE_DIRTY_TRICKS //could try scanning for an op_address+op_storep_fnc pair
|
|
if ((ef->type == ev_function) && !strcmp(strings + ef->s_name, "th_pain"))
|
|
{
|
|
QCC_CatVFile(Decompileofile, ".void(entity attacker, float damage) th_pain;\n");
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (matchingfield)
|
|
QCC_CatVFile(Decompileofile, "var .%s %s = %s;\n", type_name(ef), strings + ef->s_name, matchingfield);
|
|
else
|
|
QCC_CatVFile(Decompileofile, ".%s %s;\n", type_name(ef), strings + ef->s_name);
|
|
|
|
// fprintf(Decompileofile, "//%i %i %i %i\n", ef->ofs, ((int*)pr_globals)[ef->ofs], par->ofs, ((int*)pr_globals)[par->ofs]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (par->type == ev_vector)
|
|
j += 2;
|
|
|
|
if (par->type == ev_entity || par->type == ev_void)
|
|
{
|
|
|
|
QCC_CatVFile(Decompileofile, "%s %s;\n", type_name(par), strings + par->s_name);
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
line[0] = '\0';
|
|
QC_snprintfz(line, sizeof(line), "%s", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs]));
|
|
|
|
if (IsConstant(par))
|
|
{
|
|
QCC_CatVFile(Decompileofile, "%s %s = %s;\n", type_name(par), strings + par->s_name, line);
|
|
}
|
|
else
|
|
{
|
|
if (pr_globals[par->ofs] != 0)
|
|
QCC_CatVFile(Decompileofile, "%s %s /* = %s */;\n", type_name(par), strings + par->s_name, line);
|
|
else
|
|
QCC_CatVFile(Decompileofile, "%s %s;\n", type_name(par), strings + par->s_name, line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Check ''local globals''
|
|
*/
|
|
|
|
if (df->first_statement <= 0)
|
|
{
|
|
|
|
QCC_CatVFile(Decompileofile, "%s", DecompileProfiles[findex]);
|
|
QCC_CatVFile(Decompileofile, " = #%i; \n", -df->first_statement);
|
|
|
|
return;
|
|
}
|
|
ds = statements + df->first_statement;
|
|
|
|
while (1)
|
|
{
|
|
|
|
dom = (ds->op) % OP_MARK_END_ELSE;
|
|
|
|
if (!dom || ds == altdone)
|
|
break;
|
|
else if (dom == OP_GOTO)
|
|
{
|
|
// check for i-t-e
|
|
if ((signed int)ds->a > 0)
|
|
{
|
|
ts = ds + (signed int)ds->a;
|
|
ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct
|
|
}
|
|
else
|
|
{
|
|
ts = ds + (signed int)ds->a;
|
|
//if its a negative goto then it should normally be the end of a while{} loop. if we can't find the while statement itself, then its an infinite loop
|
|
for (k = (ts); k < ds; k++)
|
|
{
|
|
tom = k->op % OP_MARK_END_ELSE;
|
|
if (tom == OP_IF_I || tom == OP_IFNOT_I ||
|
|
tom == OP_IF_F || tom == OP_IFNOT_F ||
|
|
tom == OP_IF_S || tom == OP_IFNOT_S)
|
|
{
|
|
if (k + (signed int)k->b == ds+1)
|
|
break;
|
|
}
|
|
}
|
|
if (k == ds)
|
|
{
|
|
ds->op += OPD_GOTO_WHILE1-OP_GOTO;
|
|
ts->op += OP_MARK_END_DO;
|
|
}
|
|
}
|
|
}
|
|
else if (dom == OP_IFNOT_I || dom == OP_IFNOT_F || dom == OP_IFNOT_S)
|
|
{
|
|
// check for pure if
|
|
|
|
ts = ds + (signed int)ds->b;
|
|
tom = (ts - 1)->op % OP_MARK_END_ELSE;
|
|
|
|
if (tom != OP_GOTO)
|
|
ts->op += OP_MARK_END_ELSE; // mark the end of a if construct
|
|
else if ((signed int)(ts - 1)->a < 0)
|
|
{
|
|
if (((signed int)(ts - 1)->a + (signed int)ds->b) > 1)
|
|
{
|
|
// pure if
|
|
ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct
|
|
}
|
|
else
|
|
{
|
|
|
|
dum = 1;
|
|
for (k = (ts - 1) + (signed int)((ts - 1)->a); k < ds; k++)
|
|
{
|
|
tom = k->op % OP_MARK_END_ELSE;
|
|
if (tom == OP_GOTO ||
|
|
tom == OP_IF_I || tom == OP_IFNOT_I ||
|
|
tom == OP_IF_F || tom == OP_IFNOT_F ||
|
|
tom == OP_IF_S || tom == OP_IFNOT_S)
|
|
dum = 0;
|
|
}
|
|
if (!dum)
|
|
{
|
|
// pure if
|
|
ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (dom == OP_IF_I || dom == OP_IF_F || dom == OP_IF_S)
|
|
{
|
|
if ((signed int)ds->b<1)
|
|
{
|
|
ts = ds + (signed int)ds->b;
|
|
//this is some kind of loop, either a while or for.
|
|
|
|
//if the statement before the 'do' is a forwards goto, and it jumps to within the loop (instead of after), then we have to assume that it is a for loop and not a loop inside an else block.
|
|
if ((ts-1)->op%OP_MARK_END_ELSE == OP_GOTO && (signed int)(ts-1)->a > 0 && (ts-1)+(signed int)(ts-1)->a <= ds)
|
|
{
|
|
(--ts)->op += OPD_GOTO_FORSTART - OP_GOTO;
|
|
//because it was earlier, we need to unmark that goto's target as an end_else
|
|
ts = ts + (signed int)ts->a;
|
|
ts->op -= OP_MARK_END_ELSE;
|
|
}
|
|
else
|
|
ts->op += OP_MARK_END_DO; // mark the start of a do construct
|
|
}
|
|
else
|
|
{
|
|
ts = ds + ds->b;
|
|
if ((ts-1)->op%OP_MARK_END_ELSE != OP_GOTO)
|
|
ts->op += OP_MARK_END_ELSE; // mark the end of an if construct
|
|
else if ((signed int)(ts - 1)->a < 0)
|
|
{
|
|
if (((signed int)(ts - 1)->a + (signed int)ds->b) > 1)
|
|
{
|
|
// pure if
|
|
ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct
|
|
}
|
|
else
|
|
{
|
|
|
|
dum = 1;
|
|
for (k = (ts - 1) + (signed int)((ts - 1)->a); k < ds; k++)
|
|
{
|
|
tom = k->op % OP_MARK_END_ELSE;
|
|
if (tom == OP_GOTO ||
|
|
tom == OP_IF_I || tom == OP_IFNOT_I ||
|
|
tom == OP_IF_F || tom == OP_IFNOT_F ||
|
|
tom == OP_IF_S || tom == OP_IFNOT_S)
|
|
dum = 0;
|
|
}
|
|
if (!dum)
|
|
{
|
|
// pure if
|
|
ts->op += OP_MARK_END_ELSE; // mark the end of a if/ite construct
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ds++;
|
|
}
|
|
|
|
/*
|
|
* print the prototype
|
|
*/
|
|
QCC_CatVFile(Decompileofile, "\n%s", DecompileProfiles[findex]);
|
|
|
|
// handle state functions
|
|
|
|
ds = statements + df->first_statement;
|
|
|
|
if (ds->op == OP_STATE)
|
|
{
|
|
|
|
par = DecompileGetParameter(ds->a);
|
|
if (!par)
|
|
{
|
|
static QCC_ddef_t pars;
|
|
//must be a global (gotta be a float), create the def as needed
|
|
pars.ofs = ds->a;
|
|
pars.s_name = "IMMEDIATE"-strings;
|
|
pars.type = ev_float;
|
|
par = &pars;
|
|
// printf("Fatal Error - Can't determine frame number.");
|
|
// exit(1);
|
|
}
|
|
|
|
arg2 = DecompileGet(df, ds->b, NULL);
|
|
if (!arg2)
|
|
{
|
|
printf("Fatal Error - No state parameter with offset %i.", ds->b);
|
|
exit(1);
|
|
}
|
|
|
|
QCC_CatVFile(Decompileofile, " = [ %s, %s ]", DecompileValueString((etype_t)(par->type), &pr_globals[par->ofs]), arg2);
|
|
|
|
free(arg2);
|
|
|
|
}
|
|
else
|
|
{
|
|
QCC_CatVFile(Decompileofile, " =");
|
|
}
|
|
QCC_CatVFile(Decompileofile, "\n{\n");
|
|
|
|
startpos = Decompileofile->fsize;
|
|
|
|
/*
|
|
fprintf(Decompileprofile, "%s", DecompileProfiles[findex]);
|
|
fprintf(Decompileprofile, ") %s;\n", name);
|
|
*/
|
|
|
|
/*
|
|
* calculate the parameter size
|
|
*/
|
|
|
|
for (j = 0, ps = 0; j < df->numparms; j++)
|
|
{
|
|
par = DecompileGetParameter((gofs_t)(df->parm_start + ps));
|
|
|
|
if (par)
|
|
{
|
|
if (!strings[par->s_name])
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "_p_%i", par->ofs);
|
|
arg2 = malloc(strlen(line)+1);
|
|
strcpy(arg2, line);
|
|
par->s_name = arg2 - strings;
|
|
}
|
|
}
|
|
|
|
ps += df->parm_size[j];
|
|
}
|
|
|
|
/*
|
|
* print the locals
|
|
*/
|
|
|
|
if (df->locals > 0)
|
|
{
|
|
|
|
if ((df->parm_start) + df->locals - 1 >= (df->parm_start) + ps)
|
|
{
|
|
|
|
for (i = df->parm_start + ps; i < (df->parm_start) + df->locals; i++)
|
|
{
|
|
|
|
par = DecompileGetParameter((gofs_t)i);
|
|
|
|
if (!par)
|
|
{
|
|
// temps, or stripped...
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!strcmp(strings + par->s_name, "IMMEDIATE") || !strcmp(strings + par->s_name, ".imm"))
|
|
continue; // immediates don't belong
|
|
|
|
if (!strings[par->s_name])
|
|
{
|
|
QC_snprintfz(line, sizeof(line), "_l_%i", par->ofs);
|
|
arg2 = malloc(strlen(line)+1);
|
|
strcpy(arg2, line);
|
|
par->s_name = arg2 - strings;
|
|
}
|
|
|
|
if (par->type == ev_function)
|
|
{
|
|
printf("Warning Fields and functions must be global\n");
|
|
}
|
|
else
|
|
{
|
|
if (((int*)pr_globals)[par->ofs])
|
|
QCC_CatVFile(Decompileofile, "\tlocal %s = %s;\n", DecompilePrintParameter(par), DecompileValueString(par->type, &pr_globals[par->ofs]));
|
|
else
|
|
QCC_CatVFile(Decompileofile, "\tlocal %s;\n", DecompilePrintParameter(par));
|
|
}
|
|
if (par->type == ev_vector)
|
|
i += 2;
|
|
}
|
|
}
|
|
|
|
QCC_CatVFile(Decompileofile, "\n");
|
|
|
|
}
|
|
}
|
|
/*
|
|
* do the hard work
|
|
*/
|
|
|
|
if (!DecompileDecompileFunction(df, altdone))
|
|
{
|
|
QCC_InsertVFile(Decompileofile, startpos, "#error Corrupt Function: %s\n#if 0\n", strings+df->s_name);
|
|
QCC_CatVFile(Decompileofile, "#endif\n");
|
|
}
|
|
|
|
QCC_CatVFile(Decompileofile, "};\n");
|
|
}
|
|
|
|
extern boolean safedecomp;
|
|
int fake_name;
|
|
char synth_name[1024]; // fake name part2
|
|
|
|
boolean TrySynthName(const char *first)
|
|
{
|
|
int i;
|
|
|
|
// try to figure out the filename
|
|
// based on the first function in the file
|
|
for (i=0; i < FILELISTSIZE; i+=2)
|
|
{
|
|
if (!strcmp(filenames[i], first))
|
|
{
|
|
QC_snprintfz(synth_name, sizeof(synth_name), filenames[i + 1]);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DecompileDecompileFunctions(const char *origcopyright)
|
|
{
|
|
int i;
|
|
unsigned int o;
|
|
dfunction_t *d;
|
|
boolean bogusname;
|
|
vfile_t *f;
|
|
char fname[512];
|
|
int lastglob = 1;
|
|
|
|
DecompileCalcProfiles();
|
|
|
|
AddSourceFile(NULL, "progs.src");
|
|
Decompileprogssrc = QCC_AddVFile("progs.src", NULL, 0);
|
|
if (!Decompileprogssrc)
|
|
{
|
|
printf("Fatal Error - Could not open \"progs.src\" for output.\n");
|
|
exit(1);
|
|
}
|
|
|
|
QCC_CatVFile(Decompileprogssrc, "./progs.dat\n\n");
|
|
|
|
QCC_CatVFile(Decompileprogssrc, "#pragma flag enable lax //remove this line once you've fixed up any decompiler bugs...\n");
|
|
if (origcopyright)
|
|
QCC_CatVFile(Decompileprogssrc, "//#pragma copyright \"%s\"\n", origcopyright);
|
|
QCC_CatVFile(Decompileprogssrc, "\n", origcopyright);
|
|
|
|
|
|
for (i = 1; i < numfunctions; i++)
|
|
{
|
|
d = &functions[i];
|
|
|
|
fname[0] = '\0';
|
|
if (d->s_file <= strofs && d->s_file >= 0)
|
|
sprintf(fname, "%s", strings + d->s_file);
|
|
// FrikaC -- not sure if this is cool or what?
|
|
bogusname = false;
|
|
if (strlen(fname) <= 0)
|
|
bogusname = true;
|
|
else for (o = 0; o < strlen(fname); o++)
|
|
{
|
|
if ((fname[o] < 'a' || fname[o] > 'z') &&
|
|
(fname[o] < '0' || fname[o] > '9') &&
|
|
(fname[o] <'A' || fname[o] > 'Z') &&
|
|
(fname[o] != '.' && fname[o] != '!' && fname[o] != '_'))
|
|
{
|
|
if (fname[o] == '/')
|
|
fname[o] = '.';
|
|
else if (fname[o] == '\\')
|
|
fname[o] = '.';
|
|
else
|
|
{
|
|
bogusname = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bogusname)
|
|
{
|
|
if (*fname && !DecompileAlreadySeen(fname, NULL))
|
|
{
|
|
synth_name[0] = 0;
|
|
}
|
|
if(!TrySynthName(qcva("%s", strings + d->s_name)) && !synth_name[0])
|
|
QC_snprintfz(synth_name, sizeof(synth_name), "frik%i.qc", fake_name++);
|
|
|
|
QC_snprintfz(fname, sizeof(fname), synth_name);
|
|
}
|
|
else
|
|
synth_name[0] = 0;
|
|
|
|
|
|
|
|
if (!DecompileAlreadySeen(fname, &f))
|
|
{
|
|
printf("decompiling %s\n", fname);
|
|
compilecb();
|
|
QCC_CatVFile(Decompileprogssrc, "%s\n", fname);
|
|
}
|
|
if (!f)
|
|
{
|
|
printf("Fatal Error - Could not open \"%s\" for output.\n", fname);
|
|
exit(1);
|
|
}
|
|
Decompileofile = f;
|
|
DecompileFunction(strings + d->s_name, &lastglob);
|
|
}
|
|
}
|
|
|
|
void DecompileProgsDat(char *name, void *buf, size_t bufsize)
|
|
{
|
|
char *c = ReadProgsCopyright(buf, bufsize);
|
|
if (c)
|
|
printf("Copyright: %s\n", c);
|
|
|
|
PreCompile();
|
|
pHash_Get = &Hash_Get;
|
|
pHash_GetNext = &Hash_GetNext;
|
|
pHash_Add = &Hash_Add;
|
|
pHash_RemoveData = &Hash_RemoveData;
|
|
Hash_InitTable(&typedeftable, 1024, qccHunkAlloc(Hash_BytesForBuckets(1024)));
|
|
|
|
maxtypeinfos = 64;
|
|
qcc_typeinfo = (void *)malloc(sizeof(QCC_type_t)*maxtypeinfos);
|
|
numtypeinfos = 0;
|
|
|
|
type_void = QCC_PR_NewType("void", ev_void, true);
|
|
type_string = QCC_PR_NewType("string", ev_string, true);
|
|
type_float = QCC_PR_NewType("float", ev_float, true);
|
|
type_vector = QCC_PR_NewType("vector", ev_vector, true);
|
|
type_entity = QCC_PR_NewType("entity", ev_entity, true);
|
|
type_field = QCC_PR_NewType("__field", ev_field, false);
|
|
type_function = QCC_PR_NewType("__function", ev_function, false);
|
|
type_function->aux_type = type_void;
|
|
type_pointer = QCC_PR_NewType("__pointer", ev_pointer, false);
|
|
type_integer = QCC_PR_NewType("__integer", ev_integer, true);
|
|
type_variant = QCC_PR_NewType("variant", ev_variant, true);
|
|
type_variant = QCC_PR_NewType("__variant", ev_variant, true);
|
|
|
|
DecompileReadData(name, buf, bufsize);
|
|
DecompileDecompileFunctions(c);
|
|
|
|
printf("Done.");
|
|
}
|
|
|
|
char *DecompileGlobalStringNoContents(gofs_t ofs)
|
|
{
|
|
int i;
|
|
QCC_ddef_t *def;
|
|
static char line[128];
|
|
|
|
line[0] = '0';
|
|
QC_snprintfz(line, sizeof(line), "%i(??""?)", ofs);
|
|
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
def = &globals[i];
|
|
|
|
if (def->ofs == ofs)
|
|
{
|
|
line[0] = '0';
|
|
QC_snprintfz(line, sizeof(line), "%i(%s)", def->ofs, strings + def->s_name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
i = strlen(line);
|
|
for (; i < 16; i++)
|
|
strcat(line, " ");
|
|
strcat(line, " ");
|
|
|
|
return line;
|
|
}
|
|
|
|
char *DecompileGlobalString(gofs_t ofs)
|
|
{
|
|
char *s;
|
|
int i;
|
|
QCC_ddef_t *def;
|
|
static char line[128];
|
|
|
|
line[0] = '0';
|
|
QC_snprintfz(line, sizeof(line), "%i(??""?)", ofs);
|
|
|
|
for (i = 0; i < numglobaldefs; i++)
|
|
{
|
|
def = &globals[i];
|
|
|
|
if (def->ofs == ofs)
|
|
{
|
|
|
|
line[0] = '0';
|
|
if (!strcmp(strings + def->s_name, "IMMEDIATE") || !strcmp(strings + def->s_name, ".imm"))
|
|
{
|
|
s = PR_ValueString((etype_t)(def->type), &pr_globals[ofs]);
|
|
QC_snprintfz(line, sizeof(line), "%i(%s)", def->ofs, s);
|
|
}
|
|
else
|
|
QC_snprintfz(line, sizeof(line), "%i(%s)", def->ofs, strings + def->s_name);
|
|
}
|
|
}
|
|
|
|
i = strlen(line);
|
|
for (; i < 16; i++)
|
|
strcat(line, " ");
|
|
strcat(line, " ");
|
|
|
|
return line;
|
|
}
|
|
|
|
void DecompilePrintStatement(dstatement_t * s)
|
|
{
|
|
int i;
|
|
|
|
printf("%4i : %s ", (int)(s - statements), pr_opcodes[s->op].opname);
|
|
i = strlen(pr_opcodes[s->op].opname);
|
|
for (; i < 10; i++)
|
|
printf(" ");
|
|
|
|
if (s->op == OP_IF_I || s->op == OP_IFNOT_I ||
|
|
s->op == OP_IF_F || s->op == OP_IFNOT_F ||
|
|
s->op == OP_IF_S || s->op == OP_IFNOT_S)
|
|
printf("%sbranch %i", DecompileGlobalString(s->a), s->b);
|
|
else if (s->op == OP_GOTO)
|
|
{
|
|
printf("branch %i", s->a);
|
|
}
|
|
else if ((unsigned)(s->op - OP_STORE_F) < 6)
|
|
{
|
|
printf("%s", DecompileGlobalString(s->a));
|
|
printf("%s", DecompileGlobalStringNoContents(s->b));
|
|
}
|
|
else
|
|
{
|
|
if (s->a)
|
|
printf("%s", DecompileGlobalString(s->a));
|
|
if (s->b)
|
|
printf("%s", DecompileGlobalString(s->b));
|
|
if (s->c)
|
|
printf("%s", DecompileGlobalStringNoContents(s->c));
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void DecompilePrintFunction(char *name)
|
|
{
|
|
int i;
|
|
dstatement_t *ds;
|
|
dfunction_t *df;
|
|
|
|
for (i = 0; i < numfunctions; i++)
|
|
if (!strcmp(name, strings + functions[i].s_name))
|
|
break;
|
|
if (i == numfunctions)
|
|
{
|
|
printf("Fatal Error: No function names \"%s\"\n", name);
|
|
exit(1);
|
|
}
|
|
df = functions + i;
|
|
|
|
printf("Statements for %s:\n", name);
|
|
ds = statements + df->first_statement;
|
|
while (1)
|
|
{
|
|
DecompilePrintStatement(ds);
|
|
|
|
if (!ds->op)
|
|
break;
|
|
ds++;
|
|
}
|
|
}
|
|
|
|
|