// Utility functions API:
// - Heap memory allocation engine
// - Checksum calculation: MD5
// - Data compression/decompression: SZP
// - C-like parser for scripts and config files
// - Uncompressed file archives (fastfiles)
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <windows.h>
#include "Commondefs.h"
#include "Utilscommon.h"
#include "DebugReport.h"
#define LOG(p1)
#define LOG2(p1, p2)
static char *save_string(char *string)
{
char *ptr = (char *)Alloc(strlen(string) + 1);
if(ptr == NULL) return NULL;
strcpy(ptr, string);
return ptr;
}
#ifdef WINDOWS_X86
__declspec(naked) u16 FASTCALL Swap16(u16 data)
{
__asm xchg ch, cl
__asm movzx eax, cx
__asm ret
}
__declspec(naked) u32 FASTCALL Swap32(u32 data)
{
__asm bswap ecx
__asm mov eax, ecx
__asm ret
}
#else
u16 FASTCALL Swap16(u16 data)
{
return ((data & 0x00ff) << 8) |
((data & 0xff00) >> 8) ;
}
u32 FASTCALL Swap32(u32 data)
{
return ((data >> 24) ) |
((data >> 8) & 0x0000ff00) |
((data << 8) & 0x00ff0000) |
((data << 24) );
}
#endif // WINDOWS_X86
/*
***************************************************************************
* Block Memory Allocation.
***************************************************************************
*/
// Initial heap size (it will grow two times, whenever overflow occure)
#define HEAP_SIZE (64*1024*1024)
#define MAGIC_VALUE 0xF00DBA5E
struct Mem_Chain;
typedef struct Mem_Chain
{
Mem_Chain* next;
u32 magic;
int size;
bool used;
} Mem_Chain;
// Local variables.
static int M_Allocated, M_Blocks, M_HeapSiz, M_HeapTotal = HEAP_SIZE;
static u8* M_Heap;
static Mem_Chain M_List[32];
static bool M_Mutex[2]; // Stupid multi-thread workaround :)
// Analogy with UNIX brk system call.
static void *my_brk(int length)
{
// Check if heap is not allocated.
if(M_Heap == NULL)
{
// You can allocate heap here by appropriate library call,
// or assign it directly to some memory location (if your platform
// doesn't have malloc/free library support).
M_Heap = (u8 *)malloc(M_HeapTotal);
if(M_Heap == NULL) return (void *)-1;
}
// Extend heap.
M_HeapSiz += length;
if(M_HeapSiz >= HEAP_SIZE)
{
M_HeapSiz -= length;
return (void *)-1;
}
return &M_Heap[M_HeapSiz];
}
static Mem_Chain *find_space(int blk)
{
Mem_Chain *ptr = M_List[blk].next;
while(ptr)
{
if(ptr->used == false) return ptr;
ptr = ptr->next;
}
return NULL;
}
static Mem_Chain *allo_space(int blk)
{
Mem_Chain *ptr = &M_List[blk], *prev, *tmp;
int tries = 3;
while(ptr)
{
prev = ptr;
ptr = ptr->next;
}
while(tries > 0)
{
ptr = (Mem_Chain *)my_brk(0);
tmp = (Mem_Chain *)my_brk((2 << blk) + sizeof(Mem_Chain));
if((int)tmp == -1)
{
// Extend heap twice.
M_HeapTotal *= 2;
M_Heap = (u8 *)realloc(M_Heap, M_HeapTotal);
if(M_Heap == NULL) return NULL;
tries--;
}
else break;
}
if(tries == 0) return NULL;
ptr->used = false;
ptr->magic = MAGIC_VALUE;
prev->next = ptr;
ptr->next = NULL;
return ptr;
}
// Allocate 'size' bytes from the heap. Return block pointer, or NULL if no space available.
void *Alloc(int size)
{
Mem_Chain *ptr;
int shift = size, block = 0;
while(shift >>= 1) block++;
while(M_Mutex[0]) ;
M_Mutex[0] = 1;
LOG2("Alloc: %i\n", size);
if((ptr = find_space(block)) == NULL)
if((ptr = allo_space(block)) == NULL)
{
LOG("Alloc: Unsufficient memory!\n");
M_Mutex[0] = 0;
return NULL;
}
ptr->size = size;
ptr->used = true;
// Modify stats
M_Blocks++;
M_Allocated += size;
M_Mutex[0] = 0;
return (void *)(ptr + 1);
}
// Free block from heap.
void Free(void *ptr)
{
while(M_Mutex[1]) ;
M_Mutex[1] = 1;
Mem_Chain *blk = (Mem_Chain *)ptr - 1;
if(blk->magic != MAGIC_VALUE || blk->used == false)
{
M_Mutex[1] = 0;
return;
}
blk->used = false;
LOG2("Free: %i\n", blk->size);
// Change stats
M_Blocks--;
M_Allocated -= blk->size;
M_Mutex[1] = 0;
}
// Return memory allocation stats.
void MemStats(int *blocks, int *bytes, int *heapsiz)
{
if(blocks) *blocks = M_Blocks;
if(bytes) *bytes = M_Allocated;
if(heapsiz) *heapsiz = M_HeapTotal;
}
/*
***************************************************************************
* C-like Parser.
***************************************************************************
*/
#define PARSE_ALLOW_BUCKCHAR
#define PARSE_ALLOW_0B_PREFIX
// Parser will output next token and its type. Input string will be automatically
// adjusted on next token. Return T_NONE (0), if there is no more tokens.
// Optionally you can specify keywords list (last member must be NULL).
//
// Double lexems: ++ -- == <= >= << >> != -> += -= |= ^= &= && || *= /= %=
// Single lexems: + - = < > ! | ^ & * / %
// , : ; ~ ( ) { } [ ] ? .
// Triple lexems: ... <<= >>=
// Multichar : "string" 'constant' NUMBER NAME
//
// Operation will be saved as 1-3 chars with trailing zeroes, so you
// can access 'em like '+\0\0\0' or '<=\0\0'
// Strings and constants will be saved without quotes.
//
// Differencies from ANSI/ISO:
// - Possibility to declare binary constants: 0B and 0b prefix
// - Additional character is allowed for names: $
char *TrimComments(char *text)
{
if(text == NULL) return NULL;
int len = strlen(text), i, quot, dquot, line = 0;
char *s = text;
// TEXT SQUEEZING OPERATIONS
// '\' character at the end of string.
for(i=1; i<len; i++)
{
if(s[i] == '\n')
{
if(s[i-1] == '\\')
{
strcpy(&s[i-1], &s[i+1]);
len -= 2; i -= 2; // repeat from character before '\'
}
}
}
// Erase comments (replace 'em to spaces). Pizdets zaumnii poluchilsa, i kak ja dadumalsa do takogo? :)
// ## concatenator
for(i=0, quot=dquot=0; i<len; i++)
{
if(s[i] == '\"')
{
if((s[i-1] != '\\') && i) if(!quot) dquot ^= 1;
}
if(s[i] == '\'')
{
if((s[i-1] != '\\') && i) if(!dquot) quot ^= 1;
}
if((s[i] == '/' && s[i+1] == '*') && !quot && !dquot) // gotcha!
{
s[i] = s[i+1] = ' '; i+= 2;
for(i; i<len; i++)
{
if(s[i] == '*' && s[i+1] == '/')
{
s[i] = s[i+1] = ' '; i+= 2;
break;
}
s[i] = ' ';
}
}
if((s[i] == '/' && s[i+1] == '/') && !quot && !dquot) // gotcha!
{
s[i] = s[i+1] = ' '; i+= 2;
for(i; i<len; i++)
{
if(s[i] == '\n') break;
else s[i] = ' ';
}
}
if((s[i] == '#' && s[i+1] == '#') && !quot && !dquot) // '##'
{
strcpy(&s[i], &s[i+2]);
len -= 2; i--; // repeat from same place
}
}
return text;
}
static char INLINE p_nextchar(char **str)
{
char c = **str;
if(c) (*str)++;
return c;
}
static void INLINE p_putchar(char c, char **tok, int *maxchars)
{
if(*maxchars >= 1)
{
**tok = c;
(*tok)++;
*maxchars--;
}
}
static void INLINE p_putop1(char c, char **p, int *cc)
{
p_putchar(c, p, cc);
p_putchar('\0', p, cc);
p_putchar('\0', p, cc);
p_putchar('\0', p, cc);
}
static void INLINE p_putop2(char c1, char c2, char **p, int *cc)
{
p_putchar(c1, p, cc);
p_putchar(c2, p, cc);
p_putchar('\0', p, cc);
p_putchar('\0', p, cc);
}
static void INLINE p_putop3(char c1, char c2, char c3, char **p, int *cc)
{
p_putchar(c1, p, cc);
p_putchar(c2, p, cc);
p_putchar(c3, p, cc);
p_putchar('\0', p, cc);
}
static int INLINE p_ishex(int c)
{
if( isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) return 1;
else return 0;
}
static int INLINE p_htoi(int c)
{
if( isdigit(c) ) return c - '0';
if( (c >= 'a' && c <= 'f') ) return c - 'a' + 10;
if( (c >= 'A' && c <= 'F') ) return c - 'A' + 10;
return 0;
}
static int INLINE p_isalpha(int c)
{
#ifdef PARSE_ALLOW_BUCKCHAR
if(c == '$') return 1;
#endif
if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return 1;
else return 0;
}
// Input string is sure to be number.
static int INLINE p_parse_number(char **s, char **p, int *cc)
{
int type = T_INTEGER, point = 0, prefix = 0;
char c = p_nextchar(s);
if(c == '0')
{
c = p_nextchar(s);
// Hexa-, octa-decimal
switch(c)
{
case 'x':
case 'X':
p_putchar('0', p, cc);
p_putchar('x', p, cc);
prefix = 1;
while(1)
{
c = p_nextchar(s);
if(p_ishex(c) && c) p_putchar(c, p, cc);
else break;
}
goto postfix;
#ifdef PARSE_ALLOW_0B_PREFIX
case 'b':
case 'B':
p_putchar('0', p, cc);
p_putchar('b', p, cc);
prefix = 1;
while(1)
{
c = p_nextchar(s);
if((c == '0' || c == '1') && c) p_putchar(c, p, cc);
else break;
}
goto postfix;
#endif // PARSE_ALLOW_0B_PREFIX
case '\0':
p_putchar('0', p, cc);
goto postfix;
default:
if(c >= '0' && c <= '7')
{
p_putchar('0', p, cc);
p_putchar(c, p, cc);
prefix = 1;
while(1)
{
c = p_nextchar(s);
if((c >= '0' && c <= '7') && c) p_putchar(c, p, cc);
else break;
}
goto postfix;
}
else { (*s)--; (*s)--; }
}
}
else (*s)--;
// Decimal and float
while(1)
{
c = p_nextchar(s);
if(c == '.')
{
if(point) goto postfix;
p_putchar('.', p, cc);
point = 1;
type = T_FLOAT;
}
else if(c == 'e' || c == 'E')
{
p_putchar('E', p, cc);
type = T_FLOAT;
c = p_nextchar(s);
if(c == '+' || c == '-')
{
p_putchar(c, p, cc);
while(1)
{
c = p_nextchar(s);
if(isdigit(c) && c) p_putchar(c, p, cc);
else break;
}
}
goto postfix;
}
else
{
if(isdigit(c)) p_putchar(c, p, cc);
else break;
}
}
postfix:
if(c)
{
switch(c)
{
case 'l':
case 'L':
if(type == T_INTEGER)
{
c = p_nextchar(s);
if( (c == 'l') || (c = 'L') )
{
p_putchar('L', p, cc);
p_putchar('L', p, cc);
}
else
{
(*s)--;
p_putchar('L', p, cc);
}
}
else (*s)--;
break;
case 'u':
case 'U':
if(type == T_INTEGER)
{
p_putchar('U', p, cc);
}
else (*s)--;
break;
case 'f':
case 'F':
if(!prefix)
{
type = T_FLOAT;
p_putchar('F', p, cc);
}
else (*s)--;
break;
default:
(*s)--;
break;
}
}
p_putchar('\0', p, cc);
return type;
}
// ---
int Parse(char **string, char *token, int maxchars, char *keywords[])
{
static char *singl = ",:;~(){}[]?";
char c, prevc, **s = string, *p = token, *old = token;
int i, cc = maxchars;
if(token && maxchars) token[0] = '\0';
if(*s == NULL) return 0;
if(**s == '\0') return 0;
// Skip spaces before
while(**s <= ' ' && **s) (*s)++;
if(**s == '\0') return 0;
// Skip comments
for(i=0; (*s)[i]; i++)
{
// Skip spaces before
while((*s)[i] <= ' ' && (*s)[i]) i++;
if((*s)[i] == '\0') return 0;
if(((*s)[i] == '/' && (*s)[i+1] == '*')) // gotcha!
{
i+= 2;
for(i; (*s)[i]; i++)
{
if((*s)[i] == '*' && (*s)[i+1] == '/')
{
i+= 2;
break;
}
}
}
else if(((*s)[i] == '/' && (*s)[i+1] == '/')) // gotcha!
{
i+= 2;
for(i; (*s)[i]; i++)
{
if((*s)[i] == '\n') break;
}
}
else { *s += i; break; }
}
if(**s == '\0') return 0;
// True single lexem.
for(i=0, c=p_nextchar(s); singl[i]; i++)
{
if(c == singl[i])
{
p_putop1(c, &p, &cc);
return T_OP;
}
}
// Number.
if(isdigit(c)) goto number;
if(c == '.')
{
prevc = c;
c = p_nextchar(s);
if(isdigit(c))
{
(*s)--;
number:
(*s)--;
return p_parse_number(s, &p, &cc);
}
else { c = prevc; (*s)--; }
}
// Composite lexems.
prevc = c;
switch(c)
{
case '+':
case '-':
c = p_nextchar(s);
if(prevc == '-' && c == '>') p_putop2('-', '>', &p, &cc);
else if(c == prevc) p_putop2(prevc, prevc, &p, &cc);
else if(c == '=') p_putop2(prevc, '=', &p, &cc);
else { p_putop1(prevc, &p, &cc); if(c) (*s)--; }
return T_OP;
case '<':
case '>':
if((c = p_nextchar(s)) == '=') p_putop2(prevc, '=', &p, &cc);
else if(c == prevc)
{
if(p_nextchar(s) == '=') p_putop3(prevc, prevc, '=', &p, &cc);
else { p_putop2(prevc, prevc, &p, &cc); if(c) (*s)--; (*s)--; }
}
else { p_putop1(prevc, &p, &cc); if(c) (*s)--; }
return T_OP;
case '.':
if((c = p_nextchar(s)) == '.')
{
if((c = p_nextchar(s)) == '.')
{
p_putop3('.', '.', '.', &p, &cc);
return T_OP;
}
else return 0;
}
else { p_putop1(prevc, &p, &cc); if(c) (*s)--; }
return T_OP;
case '|':
case '&':
c = p_nextchar(s);
if(c == prevc) p_putop2(prevc, prevc, &p, &cc);
else if(c == '=') p_putop2(prevc, '=', &p, &cc);
else { p_putop1(prevc, &p, &cc); if(c) (*s)--; }
return T_OP;
case '^':
case '*':
case '/':
case '%':
case '!':
case '=':
c = p_nextchar(s);
if(c == '=') p_putop2(prevc, '=', &p, &cc);
else { p_putop1(prevc, &p, &cc); if(c) (*s)--; }
return T_OP;
}
// String.
prevc = '\0';
if(c == '\"')
{
while(1)
{
c = p_nextchar(s);
if((c == '\"' && prevc != '\\') || c == '\0')
{
p_putchar('\0', &p, &cc);
return T_STRING;
}
p_putchar(c, &p, &cc);
prevc = c;
}
}
// Constant.
prevc = '\0';
if(c == '\'')
{
while(1)
{
c = p_nextchar(s);
if((c == '\'' && prevc != '\\') || c == '\0')
{
p_putchar('\0', &p, &cc);
return T_CONST;
}
p_putchar(c, &p, &cc);
prevc = c;
}
}
// Name.
if(c == '_' || p_isalpha(c))
{
p_putchar(c, &p, &cc);
while(1)
{
c = p_nextchar(s);
if(c == '_' || p_isalpha(c) || isdigit(c)) p_putchar(c, &p, &cc);
else
{
if(c) (*s)--;
p_putchar('\0', &p, &cc);
if(keywords)
{
while(*keywords)
{
if(!strcmp(old, *keywords)) return T_KEYWORD;
keywords++;
}
}
return T_NAME;
}
}
}
return 0;
}
// Command line parser.
char *NextArgument(char **cmdline)
{
static char arg[257];
char *s = *cmdline;
u8 p = 0, c = 0, q = 0, dq = 0; // Nicely avoid string overflow by 8-bit counters :P
#define endl ( s[p] == '\0' )
#define space ( s[p] == ' ' )
#define quot ( s[p] == '\'' )
#define dquot ( s[p] == '\"' )
if(cmdline == NULL) return NULL;
arg[0] = 0;
// skip spaces
while(space) p++;
while(!endl)
{
if(space)
{
if(q || dq) arg[c++] = s[p++];
else break;
}
else if(quot && !dq)
{
q ^= 1;
p++;
}
else if(dquot && !q)
{
dq ^= 1;
p++;
}
else arg[c++] = s[p++];
}
arg[c] = 0;
(*cmdline) += p;
return c ? arg : NULL;
#undef endl
#undef space
#undef quot
#undef dquot
}
// Resolve escape sequencies. Return number of sequencies.
int EscapeString(char *string, char *out, int maxchars)
{
char *s = string, *t = out;
u8 c;
int seq = 0, n, number;
while(*s)
{
if(*s == '\\')
{
s++;
if(*s >= '0' && *s <= '7')
{
n = 0; number = 0;
while(*s >= '0' && *s <= '7' && n < 3)
{
number += (*s - '0') * (1 << (3*n));
n++; s++;
}
number &= 0xFF;
c = number;
}
else switch(*s++)
{
case 'a': case 'A': c = 0x7; break;
case 'b': case 'B': c = 0x8; break;
case 't': case 'T': c = 0x9; break;
case 'n': case 'N': c = 0xA; break;
case 'v': case 'V': c = 0xB; break;
case 'f': case 'F': c = 0xC; break;
case 'r': case 'R': c = 0xD; break;
case '\'': c = '\''; break;
case '\"': c = '\"'; break;
case '\\': c = '\\'; break;
case 'x': case 'X':
if(p_ishex(*s))
{
n = 0; number = 0;
while(p_ishex(*s) && n < 2)
{
number += p_htoi(*s) * (1 << (4*n));
n++; s++;
}
number &= 0xFF;
c = number;
}
else continue;
break;
default: continue;
}
seq++;
goto Putchar;
}
else
{
c = *s++;
Putchar:
if(maxchars--) *t++ = c;
else break;
}
}
if(maxchars--) *t++ = 0;
return seq;
}
/*
***************************************************************************
* INI-Like Files.
***************************************************************************
*/
// GET
static void NextLine(char **str)
{
char *s = *str;
int len = 0;
while(*s != '\n' && *s) { s++; len++; }
*str += len + 1;
}
void INIGetKeyValue(char *ini, char *section, char *key, char *string, int maxchars, char *defstring)
{
// Load INI file and preprocess.
char *buf = (char *)FF_FileLoad(ini, NULL), *saved;
if(buf == NULL)
{
strncpy(string, defstring, maxchars);
return;
}
saved = buf;
//buf = TrimComments(buf); // Not necessary, since I've added comment skip in Parse call.
// Parse through sections.
bool breakthrough = false;
char token[1024], sect[1024];
int op, i;
while( (op = Parse(&buf, token, sizeof(token))) != T_NONE && !breakthrough)
{
// Get section name to the ]
if(op == T_OP && token[0] == '[')
{
i = 0;
while(*buf != ']') sect[i++] = *buf++;
sect[i++] = '\0'; buf++; // skip ]
if(!stricmp(sect, section)) // FOUND section!
{
// Parse thorugh keys until next section or end of file.
while(1)
{
op = Parse(&buf, token, sizeof(token));
if(op == T_OP && token[0] == '[') break;
if(op == T_NONE) { breakthrough = true; break; }
if(!strcmp(token, key) && op == T_NAME) // FOUND key!
{
op = Parse(&buf, token, sizeof(token));
if(op == T_OP && token[0] == '=')
{
Parse(&buf, string, maxchars); // Parse Argument Value.
Free(saved);
return;
}
}
NextLine(&buf);
}
}
}
}
// Not found, copy back default
strncpy(string, defstring, maxchars);
Free(saved);
}
// SET
static void create_section(char *ini, char *section)
{
FILE *f = fopen(ini, "rt+");
if(f == NULL) return;
fseek(f, 0, SEEK_END);
fprintf(f, "\n[%s]\n", section);
fclose(f);
}
static void create_key(char *ini, char *section, char *key, char *value)
{
FILE *f;
char *buf = (char *)FileLoad(ini, NULL, "rt"), *begin;
if(buf == NULL) return;
begin = buf;
char token[1024], sect[1024];
int op, i;
while( (op = Parse(&buf, token, sizeof(token))) != T_NONE)
{
// Get section name to the ]
if(op == T_OP && token[0] == '[')
{
i = 0;
while(*buf != ']') sect[i++] = *buf++;
sect[i++] = '\0'; buf++; // skip ]
if(!strcmp(sect, section)) // FOUND section!
{
// Add new 'key=value' to the begin of section.
NextLine(&buf);
f = fopen(ini, "wt");
fwrite(begin, 1, buf-begin, f);
fprintf(f, "%s=%s\n", key, value);
fprintf(f, "%s", buf);
fclose(f);
break;
}
}
}
Free(begin);
}
void INISetKeyValue(char *ini, char *section, char *key, char *string)
{
FILE *f;
bool s = false; // No section
// Create INI file if its missing.
if( (f = fopen(ini, "rt")) == NULL )
{
char sp = ' ';
f = fopen(ini, "w");
fwrite(&sp, 1, 1, f);
}
fclose(f);
// Load INI.
char *buf = (char *)FileLoad(ini, NULL, "rt"), *begin;
if(buf == NULL) return;
begin = buf;
// Change existing key.
//
bool breakthrough = false;
char token[1024], sect[1024];
int op, i;
while( (op = Parse(&buf, token, sizeof(token))) != T_NONE && !breakthrough)
{
// Get section name to the ]
if(op == T_OP && token[0] == '[')
{
i = 0;
while(*buf != ']') sect[i++] = *buf++;
sect[i++] = '\0'; buf++; // skip ]
if(!stricmp(sect, section)) // FOUND section!
{
s = true;
// Parse thorugh keys until next section or end of file.
while(1)
{
op = Parse(&buf, token, sizeof(token));
if(op == T_OP && token[0] == '[') break;
if(op == T_NONE) { breakthrough = true; break; }
if(!strcmp(token, key) && op == T_NAME) // FOUND key!
{
// Change key value.
f = fopen(ini, "wt");
fwrite(begin, 1, buf-begin, f);
fprintf(f, "=%s\n", string);
NextLine(&buf);
fprintf(f, "%s", buf);
fclose(f);
Free(begin);
return;
}
NextLine(&buf);
}
}
}
}
// Create section and key if its missing.
//
Free(begin);
if(!s) create_section(ini, section);
create_key(ini, section, key, string);
}
/*
***************************************************************************
* File Utilities.
***************************************************************************
*/
u32 FileSize(char *filename)
{
FILE *f = fopen(filename, "rb");
if(f == NULL) return 0;
fseek(f, 0, SEEK_END);
u32 size = ftell(f);
fclose(f);
return size;
}
// Load data from file.
void *FileLoad(char *filename, u32 *size, char *mode)
{
FILE* f;
void* buffer;
u32 filesize;
if(size) *size = 0;
f = fopen(filename, mode);
if(f == NULL) return NULL;
fseek(f, 0, SEEK_END);
filesize = ftell(f);
fseek(f, 0, SEEK_SET);
buffer = Alloc(filesize + 1);
if(buffer == NULL)
{
fclose(f);
return NULL;
}
memset(buffer, 0, filesize + 1);
fread(buffer, 1, filesize, f);
fclose(f);
if(size) *size = filesize;
return buffer;
}
// Save data in file.
bool FileSave(char *filename, void *data, u32 size, char *mode)
{
FILE *f = fopen(filename, mode);
if(f == NULL) return false;
fwrite(data, size, 1, f);
fclose(f);
return true;
}
// Make path to file shorter for "lvl" levels.
char *FileShortName(char *filename, int lvl)
{
static char tempBuf[1024];
int c = 0, i;
tempBuf[0] = filename[0];
tempBuf[1] = filename[1];
tempBuf[2] = filename[2];
filename += 3;
for(i=(int)strlen(filename)-1; i; i--)
{
if(filename[i] == '\\') c++;
if(c == lvl) break;
}
if(c == lvl)
{
sprintf(&tempBuf[3], "...%s", &filename[i]);
}
else return filename - 3;
return tempBuf;
}
// Nice value of KB, MB or GB, for output.
char *FileSmartSize(u32 size)
{
static char tempBuf[256];
if(size < 1024)
{
sprintf(tempBuf, "%i byte", size);
}
else if(size < 1024*1024)
{
sprintf(tempBuf, "%i KB", size/1024);
}
else if(size < 1024*1024*1024)
{
sprintf(tempBuf, "%i MB", size/1024/1024);
}
else sprintf(tempBuf, "%1.1f GB", (float)size/1024/1024/1024);
return tempBuf;
}
// Seacrh files by given wildcard filter. Return file count and file names in 'files'.
// Dont forget to Free files[n].
// You can just count files, by specifying 'files' = NULL.
int FileSearch(char *wildcard, char **files)
{
WIN32_FIND_DATA fd;
HANDLE hfff;
int count = 0;
hfff = FindFirstFile(wildcard, &fd);
if(hfff != INVALID_HANDLE_VALUE)
{
do
{
if(~fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if(files) files[count] = save_string(fd.cFileName);
count++; // Just count files.
}
}
while(FindNextFile(hfff, &fd));
}
FindClose(hfff);
return count;
}
// Return true if file exist, otherwise return false.
bool FileExist(char *filename)
{
FILE *f = fopen(filename, "rb");
if(f == NULL) return false;
fclose(f);
return true;
}
// -------------------------------------- Fastfiles -------------------------------------
// Note: Added compatibility with Quake2 pak-files ;)
#define FF_LISTFILE "Fastfiles.lst"
#define FF_MAGIC (('K'<<24)+('C'<<16)+('A'<<8)+'P') // PACK
#define FF_MAXNAME 56
#define FF_TEMP "TEMP.PAK"
// From documentation:
// [PAK Header]
// [File 1] [File 2] ... [File N]
// [PAK FAT]
typedef struct FF_Head
{
u32 sign; // FF_MAGIC
long FAT; // Location of FAT (offset from begin of file)
long FATLen; // FAT length in bytes.
} FF_Head;
typedef struct FF_Entry
{
char name[FF_MAXNAME]; // Filename.
long offset; // Offset within start of file.
long size; // File size in bytes.
} FF_Entry;
typedef struct FFILE_Priv // Opened fastfile descriptor.
{
bool fast; // true: if opened file is really Fastfile.
FF_Head head; // PAK header (Free required!)
FF_Entry* fat; // PAK FAT (Free required!)
unsigned entry; // Opened fastfile entry num.
long ff_origin; // For Seek/Tell.
FILE* fd; // for non-fastfiles / PAK file (fclose required!)
} FFILE_Priv;
#define FF FFILE_Priv
// End of Declarations.
// ------- Fastfile basic API.
static void ff_replace_backslash(char *s)
{
while(*s)
{
if(*s == '\\') *s = '/';
s++;
}
}
static FF *ffopen(char *filename, char *fastfile)
{
FF *ff;
FILE *f;
FF_Head head;
FF_Entry *fat;
size_t fatlen;
int entries = 0;
// Check if PAK present.
f = fopen(fastfile, "rb");
if(f == NULL) return NULL;
fread(&head, 1, sizeof(FF_Head), f);
if(head.sign != FF_MAGIC)
{
fclose(f);
return NULL;
}
fatlen = head.FATLen;
entries = fatlen / sizeof(FF_Entry);
// Load FAT.
fat = (FF_Entry *)Alloc(fatlen);
if(fat == NULL)
{
fclose(f);
return NULL;
}
memset(fat, 0, fatlen + sizeof(FF_Entry));
fseek(f, head.FAT, SEEK_SET);
if( fread(fat, 1, fatlen, f) != fatlen)
{
fclose(f);
Free(fat);
return NULL;
}
// Check if this file is present in PAK.
for(int i=0; i<entries; i++)
{
ff_replace_backslash(fat[i].name);
if(!stricmp(fat[i].name, filename)) // Found.
{
ff = (FF *)Alloc(sizeof(FF));
if(ff == NULL)
{
fclose(f);
Free(fat);
return NULL;
}
ff->fast = true;
ff->head = head;
ff->fat = fat;
ff->fd = f;
ff->entry = i;
ff->ff_origin = 0;
return ff;
}
}
// Not found.
fclose(f);
Free(fat);
return NULL;
}
FFILE *FF_Open(char *filename)
{
FF *ff;
char *list, **p = &list, fastfile[256];
ff_replace_backslash(filename);
// Try to open real file.
FILE *f = fopen(filename, "rb");
if(f != NULL)
{
ff = (FF *)Alloc(sizeof(FF));
if(ff == NULL)
{
fclose(f);
return NULL;
}
ff->fast = false;
ff->fd = f;
return (FFILE *)ff;
}
// Enumerate all PAK files in FF_LISTFILE.
list = (char *)FileLoad(FF_LISTFILE, NULL, "rt");
if(list == NULL) return NULL;
while(Parse(p, fastfile, sizeof(fastfile)-1))
{
if( (ff = ffopen(filename, fastfile)) != NULL)
{
Free(list);
return (FFILE *)ff;
}
}
Free(list);
return NULL;
}
int FF_Read(void *buffer, int length, FFILE *f)
{
FF *ff = (FF *)f;
if(f == NULL) return EINVAL;
if(ff->fast)
{
int seek, bytes;
if(ff->entry >= (ff->head.FATLen / sizeof(FF_Entry))) return 0;
seek = ff->ff_origin;
bytes = length;
if(seek < 0) { seek = 0; bytes -= -seek; }
if(seek >= ff->fat[ff->entry].size) return 0;
if(bytes < 0) return 0;
if(bytes >= ff->fat[ff->entry].size) bytes = ff->fat[ff->entry].size;
if((seek + bytes) >= ff->fat[ff->entry].size) bytes = (ff->fat[ff->entry].size - seek);
if(bytes == 0) return 0;
fseek(ff->fd, ff->fat[ff->entry].offset + seek, SEEK_SET);
ff->ff_origin += bytes;
return fread(buffer, 1, bytes, ff->fd);
}
else return fread(buffer, 1, length, ff->fd);
}
long FF_Tell(FFILE *f)
{
FF *ff = (FF *)f;
if(f == NULL) return EINVAL;
if(ff->fast)
{
if(ff->entry >= (ff->head.FATLen / sizeof(FF_Entry))) return EBADF;
else return ff->ff_origin;
}
else return ftell(ff->fd);
}
void FF_Seek(FFILE *f, long pos, int hint)
{
FF *ff = (FF *)f;
if(f == NULL) return;
if(ff->fast)
{
if(ff->entry >= (ff->head.FATLen / sizeof(FF_Entry))) return;
switch(hint)
{
case SEEK_SET:
ff->ff_origin = pos;
break;
case SEEK_CUR:
ff->ff_origin += pos;
break;
case SEEK_END:
ff->ff_origin = ff->fat[ff->entry].size - pos;
break;
}
}
else fseek(ff->fd, pos, hint);
}
void FF_Close(FFILE *f)
{
FF *ff = (FF *)f;
if(f == NULL) return;
if(ff->fast)
{
if(ff->fat) Free(ff->fat);
}
if(ff->fd) fclose(ff->fd);
Free(f);
}
// High-level version of FileLoad for fastfiles.
void *FF_FileLoad(char *filename, u32 *size)
{
FFILE* f;
void* buffer;
u32 filesize;
if(size) *size = 0;
f = FF_Open(filename);
if(f == NULL) return NULL;
FF_Seek(f, 0, SEEK_END);
filesize = FF_Tell(f);
FF_Seek(f, 0, SEEK_SET);
buffer = Alloc(filesize + 1);
if(buffer == NULL)
{
FF_Close(f);
return NULL;
}
memset(buffer, 0, filesize + 1);
FF_Read(buffer, filesize, f);
FF_Close(f);
if(size) *size = filesize;
return buffer;
}
// ------- For Command Line Tool.
static bool fcopy(FILE *to, FILE *from, int blocklen, int numbytes)
{
u8 *tempbuf = (u8 *)Alloc(blocklen);
if(tempbuf == NULL) return false;
while(numbytes > 0)
{
if(numbytes < blocklen) blocklen = numbytes;
int readed = fread(tempbuf, 1, blocklen, from);
if(readed) fwrite(tempbuf, 1, readed, to);
else break;
numbytes -= blocklen;
}
Free(tempbuf);
return true;
}
bool FF_New(char *fastfile) // Create new empty PAK
{
FILE *ff;
FF_Head head;
head.sign = FF_MAGIC;
head.FAT = 0;
head.FATLen = 0;
ff = fopen(fastfile, "wb");
if(ff == NULL) return false;
fwrite(&head, 1, sizeof(FF_Head), ff);
fclose(ff);
return true;
}
bool FF_Add(char *fastfile, char *file)
{
FILE *ff;
FF_Head head;
FF_Entry *fat;
size_t fatlen;
long maxoffset = sizeof(FF_Head);
int lastfile = 0, entries = 0;
// Check if filename too long
if(strlen(file) >= FF_MAXNAME-1) return false;
ff_replace_backslash(file);
// Try to open added file.
ff = fopen(file, "rb");
if(ff == NULL) return false;
fclose(ff);
// Check if PAK present.
ff = fopen(fastfile, "rb");
if(ff == NULL) return false;
fread(&head, 1, sizeof(FF_Head), ff);
if(head.sign != FF_MAGIC)
{
fclose(ff);
return false;
}
fatlen = head.FATLen;
entries = fatlen / sizeof(FF_Entry);
// Load FAT.
fat = (FF_Entry *)malloc(fatlen + sizeof(FF_Entry)); /* Reserve 1 slot for future entry */
if(fat == NULL)
{
fclose(ff);
return false;
}
memset(fat, 0, fatlen + sizeof(FF_Entry));
fseek(ff, head.FAT, SEEK_SET);
if( fread(fat, 1, fatlen, ff) != fatlen)
{
fclose(ff);
free(fat);
return false;
}
// Check if this file is already present in PAK.
for(int i=0; i<entries; i++)
{
ff_replace_backslash(fat[i].name);
if(fat[i].offset > maxoffset)
{
maxoffset = fat[i].offset;
lastfile = i;
}
if(!stricmp(fat[i].name, file))
{
fclose(ff); // Found.
free(fat);
return false;
}
}
// Add new file!
strncpy(fat[entries].name, file, FF_MAXNAME-1);
fat[entries].offset = maxoffset + fat[lastfile].size;
fat[entries].size = FileSize(file);
lastfile = entries++;
head.FAT = fat[lastfile].offset + fat[lastfile].size;
head.FATLen = entries * sizeof(FF_Entry);
// Fix header.
fclose(ff);
ff = fopen(fastfile, "rb+");
fseek(ff, 0, SEEK_SET);
fwrite(&head, 1, sizeof(FF_Head), ff);
// Add file and fix FAT.
u8 *data = (u8 *)FileLoad(file);
if(data == NULL)
{
fclose(ff);
free(fat);
return false;
}
fseek(ff, fat[lastfile].offset, SEEK_SET);
fwrite(data, 1, fat[lastfile].size, ff);
fwrite(fat, 1, sizeof(FF_Entry) * entries, ff);
// Done. Cleanup.
Free(data);
fclose(ff);
free(fat);
return true;
}
bool FF_Delete(char *fastfile, char *file)
{
FILE *ff, *temp;
FF_Head head;
FF_Entry *fat;
int i, entries, rm;
long offset;
ff_replace_backslash(file);
// Check if PAK present.
ff = fopen(fastfile, "rb");
if(ff == NULL) return false;
// Load header and FAT.
if( fread(&head, 1, sizeof(FF_Head), ff) != sizeof(FF_Head) )
{
fclose(ff);
return false;
}
if(head.sign != FF_MAGIC)
{
fclose(ff);
return false;
}
fseek(ff, head.FAT, SEEK_SET);
fat = (FF_Entry *)Alloc(head.FATLen);
if(fat == NULL)
{
fclose(ff);
return false;
}
if( fread(fat, 1, head.FATLen, ff) != (unsigned)head.FATLen )
{
Free(fat);
fclose(ff);
return false;
}
// Check if file is present in PAK.
for(i=0, entries=head.FATLen / sizeof(FF_Entry); i<entries; i++)
{
ff_replace_backslash(fat[i].name);
if(!stricmp(fat[i].name, file)) break;
}
if(i == entries)
{
Free(fat);
fclose(ff);
return false;
}
rm = i; // Entry to remove
// Create temporary PAK file.
temp = fopen(FF_TEMP, "wb");
if(temp == NULL)
{
Free(fat);
fclose(ff);
return false;
}
fseek(ff, 0, SEEK_SET);
// Cut file "i" from PAK (using temporary file).
head.FATLen -= sizeof(FF_Entry);
head.FAT -= fat[i].size;
fwrite(&head, 1, sizeof(FF_Head), temp);
offset = ftell(temp);
// Copy files.
for(i=0; i<entries; i++)
{
if(i != rm)
{
fseek(ff, fat[i].offset, SEEK_SET);
fcopy(temp, ff, 32768, fat[i].size);
fat[i].offset = offset;
offset = ftell(temp);
}
}
// Copy FAT.
for(i=0; i<entries; i++)
{
if(i != rm)
{
fwrite(&fat[i], 1, sizeof(FF_Entry), temp);
}
}
// Done. Cleanup.
Free(fat);
fclose(ff);
fclose(temp);
// Replace.
remove(fastfile);
rename(FF_TEMP, fastfile);
remove(FF_TEMP);
return true;
}
void FF_List(char *fastfile, void (*List_callback)(int entry, char *name))
{
FILE *ff;
FF_Head head;
FF_Entry *fat;
int i, entries;
// Check if PAK present.
ff = fopen(fastfile, "rb");
if(ff == NULL) return;
// Load header and FAT.
if( fread(&head, 1, sizeof(FF_Head), ff) != sizeof(FF_Head) )
{
fclose(ff);
return;
}
if(head.sign != FF_MAGIC)
{
fclose(ff);
return;
}
fseek(ff, head.FAT, SEEK_SET);
fat = (FF_Entry *)Alloc(head.FATLen);
if(fat == NULL)
{
fclose(ff);
return;
}
if( fread(fat, 1, head.FATLen, ff) != (unsigned)head.FATLen )
{
Free(fat);
fclose(ff);
return;
}
// Enumerate.
for(i=0, entries=head.FATLen / sizeof(FF_Entry); i<entries; i++)
{
if(List_callback) List_callback(i, fat[i].name);
}
// Done. Cleanup.
Free(fat);
fclose(ff);
}
// Directory tree must exist!
bool FF_Extract(char *fastfile, char *file)
{
FILE *ff, *f;
FF_Head head;
FF_Entry *fat;
int i, entries;
ff_replace_backslash(file);
// Check if PAK present.
ff = fopen(fastfile, "rb");
if(ff == NULL) return false;
// Load header and FAT.
if( fread(&head, 1, sizeof(FF_Head), ff) != sizeof(FF_Head) )
{
fclose(ff);
return false;
}
if(head.sign != FF_MAGIC)
{
fclose(ff);
return false;
}
fseek(ff, head.FAT, SEEK_SET);
fat = (FF_Entry *)Alloc(head.FATLen);
if(fat == NULL)
{
fclose(ff);
return false;
}
if( fread(fat, 1, head.FATLen, ff) != (unsigned)head.FATLen )
{
Free(fat);
fclose(ff);
return false;
}
// Locate 'file' in FAT.
for(i=0, entries=head.FATLen / sizeof(FF_Entry); i<entries; i++)
{
ff_replace_backslash(fat[i].name);
if(!stricmp(fat[i].name, file)) break;
}
if(i == entries) // Not Found!
{
Free(fat);
fclose(ff);
return false;
}
// Open extract file for writing. (Directory tree must exist!)
f = fopen(file, "wb");
if(f == NULL)
{
Free(fat);
fclose(ff);
}
// Extract it.
fseek(ff, fat[i].offset, SEEK_SET);
fcopy(f, ff, 32768, fat[i].size);
// Done. Cleanup mess.
Free(fat);
fclose(f);
fclose(ff);
return true;
}
bool FF_Rename(char *fastfile, int entry, char *newname)
{
FILE *ff;
FF_Head head;
FF_Entry *fat;
int i = entry, entries;
// Check if filename length exceeds limit.
if(strlen(newname) >= FF_MAXNAME-1) return false;
// Check if PAK present.
ff = fopen(fastfile, "rb+");
if(ff == NULL) return false;
// Load header and FAT.
if( fread(&head, 1, sizeof(FF_Head), ff) != sizeof(FF_Head) )
{
fclose(ff);
return false;
}
if(head.sign != FF_MAGIC)
{
fclose(ff);
return false;
}
fseek(ff, head.FAT, SEEK_SET);
fat = (FF_Entry *)Alloc(head.FATLen);
if(fat == NULL)
{
fclose(ff);
return false;
}
if( fread(fat, 1, head.FATLen, ff) != (unsigned)head.FATLen )
{
Free(fat);
fclose(ff);
return false;
}
entries = head.FATLen / sizeof(FF_Entry);
// Replace name.
if(0 > i || i >= entries)
{
Free(fat);
fclose(ff);
return false;
}
memset(fat[i].name, 0, FF_MAXNAME);
strcpy(fat[i].name, newname);
// Update PAK.
fseek(ff, head.FAT, SEEK_SET);
fwrite(fat, 1, entries * sizeof(FF_Entry), ff);
// cleanup.
Free(fat);
fclose(ff);
return true;
}
// ---
static void My_ff_list_callback(int entry, char *name)
{
//Report("%i: %s\n", entry, name);
}
// Usage:
// New: paker -n <fastfile>
// Add: paker -a <file> <fastfile>
// Del: paker -d <file> <fastfile>
// List: paker -l <fastfile>
// Extract: paker -x <file> <fastfile>
// Rename: paker -r <entry> <newname> <fastfile>
void FF_Command(char *cmdline)
{
// WTF?
if(cmdline == NULL) return;
// Command list:
#define FF_NEW 0
#define FF_LIST 1
#define FF_ADD 2
#define FF_DELETE 3
#define FF_EXTRACT 4
#define FF_RENAME 5
int cmd, entry = 0;
char file[256]; // file/entry/fastfile
char pak[256];
// For DEBUG purposes: List arguments.
char **s = &cmdline, *ptr;
#if 0
while( ptr = NextArgument(s) )
{
DB_Report("%s", ptr);
}
s = &cmdline;
#endif
// Parse command.
int argc = 0;
while( ptr = NextArgument(s) )
{
argc++;
if(argc == 1) { if(stricmp(ptr, "paker")) return; }
if(argc == 2)
{
;;;;;if(!stricmp(ptr, "-n")) cmd = FF_NEW;
else if(!stricmp(ptr, "-a")) cmd = FF_ADD;
else if(!stricmp(ptr, "-d")) cmd = FF_DELETE;
else if(!stricmp(ptr, "-l")) cmd = FF_LIST;
else if(!stricmp(ptr, "-x")) cmd = FF_EXTRACT;
else if(!stricmp(ptr, "-r")) cmd = FF_RENAME;
else return; // Unknown command
}
if(argc == 3)
{
strcpy(file, ptr);
if(cmd <= FF_LIST)
{
strcpy(pak, ptr);
break;
}
}
if(argc == 4)
{
strcpy(pak, ptr);
if(cmd <= FF_EXTRACT) break;
}
if(argc == 5)
{
entry = atoi(file);
strcpy(file, pak);
strcpy(pak, ptr);
break;
}
}
// Execute command.
switch(cmd)
{
case FF_NEW:
if(argc != 3) return;
//Report("New %s", pak);
FF_New(pak);
break;
case FF_ADD:
if(argc != 4) return;
//Report("Add %s to %s", file, pak);
FF_Add(pak, file);
break;
case FF_DELETE:
if(argc != 4) return;
//Report("Delete %s from %s", file, pak);
FF_Delete(pak, file);
break;
case FF_LIST:
if(argc != 3) return;
//Report("List %s", pak);
FF_List(pak, My_ff_list_callback);
break;
case FF_EXTRACT:
if(argc != 4) return;
//Report("Extract %s from %s", file, pak);
FF_Extract(pak, file);
break;
case FF_RENAME:
if(argc != 5) return;
//Report("Rename %i as %s in %s", entry, file, pak);
FF_Rename(pak, entry, file);
break;
}
}
#undef FF
/*
***************************************************************************
* Short MD5 checksum calculation.
***************************************************************************
*/
typedef struct MD5_CB
{
u32 state[4]; // context state (ABCD)
u32 count[2]; // number of bits, mod 2^64
u8 buffer[64]; // input buffer
} MD5_CB;
// Basic MD5 functions
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
#define ROTATE_LEFT(x, n) (_rotl(x, n))
// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
// Rotation is separate from addition to prevent recomputation.
#define FF(a, b, c, d, x, s, ac) { \
(a) += F ((b), (c), (d)) + (x) + (u32)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define GG(a, b, c, d, x, s, ac) { \
(a) += G ((b), (c), (d)) + (x) + (u32)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define HH(a, b, c, d, x, s, ac) { \
(a) += H ((b), (c), (d)) + (x) + (u32)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define II(a, b, c, d, x, s, ac) { \
(a) += I ((b), (c), (d)) + (x) + (u32)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
// Copy 'len' bytes from input to output, avoiding endianess order problems.
static void hash_encode(u8 *output, u32 *input, u32 len)
{
u32 i, j;
for(i=0, j=0; j<len; i++, j+=4)
{
output[j] = (u8)(input[i] & 0xff);
output[j+1] = (u8)((input[i] >> 8) & 0xff);
output[j+2] = (u8)((input[i] >> 16) & 0xff);
output[j+3] = (u8)((input[i] >> 24) & 0xff);
}
}
// Copy 'len' bytes from input to output, avoiding endianess order problems.
static void hash_decode(u32 *output, u8 *input, u32 len)
{
u32 i, j;
for(i=0, j=0; j<len; i++, j+=4)
{
output[i] = ( (u32)input[j]) |
(((u32)input[j+1]) << 8) |
(((u32)input[j+2]) << 16) |
(((u32)input[j+3]) << 24);
}
}
// MD5 basic transformation (update state, by hashing block)
static void hash_transform(u32 state[4], u8 block[64])
{
u32 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
hash_decode(x, block, 64);
// Round 1
FF(a, b, c, d, x[ 0], 7, 0xd76aa478); /* 1 */
FF(d, a, b, c, x[ 1], 12, 0xe8c7b756); /* 2 */
FF(c, d, a, b, x[ 2], 17, 0x242070db); /* 3 */
FF(b, c, d, a, x[ 3], 22, 0xc1bdceee); /* 4 */
FF(a, b, c, d, x[ 4], 7, 0xf57c0faf); /* 5 */
FF(d, a, b, c, x[ 5], 12, 0x4787c62a); /* 6 */
FF(c, d, a, b, x[ 6], 17, 0xa8304613); /* 7 */
FF(b, c, d, a, x[ 7], 22, 0xfd469501); /* 8 */
FF(a, b, c, d, x[ 8], 7, 0x698098d8); /* 9 */
FF(d, a, b, c, x[ 9], 12, 0x8b44f7af); /* 10 */
FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */
FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */
FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */
FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */
FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */
FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */
// Round 2
GG(a, b, c, d, x[ 1], 5, 0xf61e2562); /* 17 */
GG(d, a, b, c, x[ 6], 9, 0xc040b340); /* 18 */
GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */
GG(b, c, d, a, x[ 0], 20, 0xe9b6c7aa); /* 20 */
GG(a, b, c, d, x[ 5], 5, 0xd62f105d); /* 21 */
GG(d, a, b, c, x[10], 9, 0x2441453); /* 22 */
GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */
GG(b, c, d, a, x[ 4], 20, 0xe7d3fbc8); /* 24 */
GG(a, b, c, d, x[ 9], 5, 0x21e1cde6); /* 25 */
GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */
GG(c, d, a, b, x[ 3], 14, 0xf4d50d87); /* 27 */
GG(b, c, d, a, x[ 8], 20, 0x455a14ed); /* 28 */
GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */
GG(d, a, b, c, x[ 2], 9, 0xfcefa3f8); /* 30 */
GG(c, d, a, b, x[ 7], 14, 0x676f02d9); /* 31 */
GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */
// Round 3
HH(a, b, c, d, x[ 5], 4, 0xfffa3942); /* 33 */
HH(d, a, b, c, x[ 8], 11, 0x8771f681); /* 34 */
HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */
HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */
HH(a, b, c, d, x[ 1], 4, 0xa4beea44); /* 37 */
HH(d, a, b, c, x[ 4], 11, 0x4bdecfa9); /* 38 */
HH(c, d, a, b, x[ 7], 16, 0xf6bb4b60); /* 39 */
HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */
HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */
HH(d, a, b, c, x[ 0], 11, 0xeaa127fa); /* 42 */
HH(c, d, a, b, x[ 3], 16, 0xd4ef3085); /* 43 */
HH(b, c, d, a, x[ 6], 23, 0x4881d05); /* 44 */
HH(a, b, c, d, x[ 9], 4, 0xd9d4d039); /* 45 */
HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */
HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */
HH(b, c, d, a, x[ 2], 23, 0xc4ac5665); /* 48 */
// Round 4
II(a, b, c, d, x[ 0], 6, 0xf4292244); /* 49 */
II(d, a, b, c, x[ 7], 10, 0x432aff97); /* 50 */
II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */
II(b, c, d, a, x[ 5], 21, 0xfc93a039); /* 52 */
II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */
II(d, a, b, c, x[ 3], 10, 0x8f0ccc92); /* 54 */
II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */
II(b, c, d, a, x[ 1], 21, 0x85845dd1); /* 56 */
II(a, b, c, d, x[ 8], 6, 0x6fa87e4f); /* 57 */
II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */
II(c, d, a, b, x[ 6], 15, 0xa3014314); /* 59 */
II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */
II(a, b, c, d, x[ 4], 6, 0xf7537e82); /* 61 */
II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */
II(c, d, a, b, x[ 2], 15, 0x2ad7d2bb); /* 63 */
II(b, c, d, a, x[ 9], 21, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
#undef F
#undef G
#undef H
#undef I
// Set initial context state.
static void hash_init(MD5_CB *cb)
{
cb->count[0] = cb->count[1] = 0;
cb->state[0] = 0x67452301;
cb->state[1] = 0xefcdab89;
cb->state[2] = 0x98badcfe;
cb->state[3] = 0x10325476;
}
// Process next block.
static void hash_update(MD5_CB *cb, u8 *buf, u32 length)
{
u32 i, index, parts;
// Compute number of bytes mod 64
index = (u32)((cb->count[0] >> 3) & 0x3F);
// Update number of bits
if ((cb->count[0] += ((u32)length << 3)) < ((u32)length << 3))
cb->count[1]++;
cb->count[1] += ((u32)length >> 29);
parts = 64 - index;
// Transform as many times as possible.
if(length >= parts)
{
memcpy(&cb->buffer[index], buf, parts);
hash_transform(cb->state, cb->buffer);
for(i=parts; i+63<length; i+=64)
hash_transform(cb->state, &buf[i]);
index = 0;
}
else i = 0;
// Buffer remaining input
memcpy(&cb->buffer[index], &buf[i], length-i);
}
// Write message digest and reset context
static void hash_finish(MD5_CB *cb, u8 digest[16])
{
u8 bits[8];
u32 index, padd_len;
u8 padding[64];
memset(padding, 0, sizeof(padding));
padding[0] = 0x80;
// Save number of bits
hash_encode(bits, cb->count, 8);
// Pad out to 56 mod 64.
index = (u32)((cb->count[0] >> 3) & 0x3f);
padd_len = (index < 56) ? (56 - index) : (120 - index);
hash_update(cb, padding, padd_len);
// Append length (before padding)
hash_update(cb, bits, 8);
// Store state in digest
hash_encode(digest, cb->state, 16);
// Zeroize sensitive information.
memset(cb, 0, sizeof(MD5_CB));
}
void BlockChecksum(u8 *md5, void *buf, int length)
{
u32 digest[4];
MD5_CB md5cb;
hash_init(&md5cb);
hash_update(&md5cb, (u8 *)buf, length);
hash_finish(&md5cb, (u8 *)digest);
if(md5) memcpy(md5, digest, sizeof(digest));
}
void FileChecksumLong(u8 *md5, char *filename)
{
u32 digest[4];
s32 filesize;
MD5_CB md5cb;
FILE* fcb;
#define CHUNK_SIZE (128*1024)
u8 buffer[CHUNK_SIZE];
if(md5) memset (md5, 0, sizeof digest);
// Try open file.
fcb = fopen(filename, "rb");
if(fcb == NULL) return;
// Calculate file size.
fseek(fcb, 0, SEEK_END);
filesize = ftell(fcb);
fseek(fcb, 0, SEEK_SET);
hash_init(&md5cb);
// Proceed hash calculation by small pieces.
while(filesize >= CHUNK_SIZE)
{
fread(buffer, 1, CHUNK_SIZE, fcb);
hash_update(&md5cb, buffer, CHUNK_SIZE);
filesize -= CHUNK_SIZE;
}
if(filesize) // Tail.
{
fread(buffer, 1, filesize, fcb);
hash_update(&md5cb, buffer, filesize);
}
fclose(fcb);
hash_finish(&md5cb, (u8 *)digest);
if(md5) memcpy(md5, digest, 16);
}
u32 FileChecksum(char *filename)
{
u32 digest[4];
FileChecksumLong((u8 *)digest, filename);
return (digest[0] ^ digest[1] ^ digest[2] ^ digest[3]);
}
/*
***************************************************************************
* Compress API. Credits to hotquik for his Compress/Decompress implementation!!
* (SZP is actually LZSS modification).
***************************************************************************
*/
// Note: SZP compression is not optimized and *MUCH* slower than zlib. Although decompression is quite fast.
#define USEZLIB
#ifdef USEZLIB
#include "zlib.h"
#pragma comment(lib, "zlibstat.lib")
#endif // USEZLIB
#define SZP_ALLOC malloc
#define SZP_FREE free
#define SZP_MAGIC (('0'<<24)+('y'<<16)+('a'<<8)+'Y') // Yay0
typedef struct SZP_S // Szp Header.
{
u32 check;
u32 decodedSize;
u32 linkOffset;
u32 chunkOffset;
#pragma warning (disable: 4200)
u32 flagTable[];
} SZP_S;
// Encodes a data range in a SZP block.
// In: Memory mapped source buffer and its length in bytes.
// Out: Pre-allocated destination buffer (recommended length is twice as source),
// compressed buffer length in bytes.
void SZP_Compress(void *dest, int *encodedSize, void *src, int srcLen)
{
#define OFSBITS 12
u8 * decodedBuffer = (u8 *)src, *encodedBuffer = (u8 *)dest;
u32 decodedSize = srcLen;
u8 * decPtr, * decEndPtr;
SZP_S * header;
// masks buffer
u32 maskMaxSize = (decodedSize + 32) >> 3; // 1 bit per byte
u32 maskSize = 0;
u32 maskBitCount = 0, mask = 0;
u32 * maskBuffer, * maskPtr, * maskEndPtr;
// links buffer
u32 linkMaxSize = decodedSize;
u32 linkSize = 0;
u16 link = 0;
u16 linkOffset;
u16 * linkBuffer, * linkPtr, * linkEndPtr;
u16 maxOffset = 1 << OFSBITS;
u16 minCount = 3, maxCount = 273;
// chunks buffer
u32 chunkMaxSize = decodedSize;
u32 chunkSize = 0;
u8 chunk = 0;
u8 * chunkBuffer, * chunkPtr, * chunkEndPtr;
u8 *windowPtr;
int windowLen = 0, length, maxlen;
// Re-allocate memory needed
maskBuffer = (u32 *) SZP_ALLOC(maskMaxSize);
linkBuffer = (u16 *) SZP_ALLOC(linkMaxSize);
chunkBuffer = (u8 *) SZP_ALLOC(chunkMaxSize);
if (maskBuffer == NULL || linkBuffer == NULL || chunkBuffer == NULL) {
SZP_FREE(maskBuffer);
SZP_FREE(linkBuffer);
SZP_FREE(chunkBuffer);
return;
}
memset(maskBuffer, 0, maskMaxSize);
//memset(linkBuffer, 0, linkMaxSize); // Unnecessary.
//memset(chunkBuffer, 0, chunkMaxSize);
//set pointers
decPtr = decodedBuffer;
decEndPtr = decPtr + decodedSize;
maskPtr = maskBuffer;
maskEndPtr = (u32 *)((u8 *)maskPtr + maskMaxSize);
linkPtr = linkBuffer;
linkEndPtr = (u16 *)((u8 *)linkPtr + linkMaxSize);
chunkPtr = chunkBuffer;
chunkEndPtr = chunkPtr + chunkMaxSize;
windowPtr = decPtr;
// start enconding
while (decPtr < decEndPtr) {
// check buffers pointers
#if 0
ASSERTMSG (!(maskPtr >= maskEndPtr), "SZP Encode: Not enough memory for mask buffer");
ASSERTMSG (!(linkPtr >= linkEndPtr), "SZP Encode: Not enough memory for links buffer");
ASSERTMSG (!(chunkPtr >= chunkEndPtr), "SZP Encode: Not enough memory for chunks buffer");
#endif
if(windowLen >= (1 << OFSBITS))
{
windowLen = windowLen - (1 << OFSBITS);
windowPtr = decPtr - windowLen;
}
if((decEndPtr - decPtr) < maxCount) maxCount = (decEndPtr - decPtr);
// Scan through the window.
maxlen = 0;
for(int i=0; i<windowLen; i++)
{
for(length=0; length<(windowLen-i) && length<maxCount; length++)
{
if(decPtr[length] != windowPtr[length+i]) break;
}
if(length > maxlen)
{
maxlen = length;
linkOffset = windowLen - i;
}
}
length = maxlen;
mask <<= 1;
if(length >= minCount) // Add Link
{
link = (linkOffset - 1) & 0x0FFF;
if (length < 18) {
link |= ((length - 2) << 12) ;
}
else{
// store current count as a chunk.
*chunkPtr++ = (u8)(length - 18);
}
*linkPtr++ = Swap16(link);
decPtr += length;
windowLen += length;
}
else // Add single byte, increase Window.
{
*chunkPtr++ = *decPtr++;
windowLen++;
mask |= 1;
}
maskBitCount++;
if (maskBitCount == 32) {
// store current mask
*maskPtr = Swap32(mask);
maskPtr++;
maskBitCount = 0;
}
}
//flush mask
if (maskBitCount > 0) {
mask <<= (32 - maskBitCount);
// store current mask
*maskPtr = Swap32(mask);
maskPtr++;
maskBitCount = 0;
}
// now join all pieces
maskSize = (u32)((u8 *)maskPtr - (u8 *)maskBuffer);
linkSize = (u32)((u8 *)linkPtr - (u8 *)linkBuffer);
chunkSize = (u32)((u8 *)chunkPtr - (u8 *)chunkBuffer);
*encodedSize = sizeof(SZP_S) + maskSize + linkSize + chunkSize;
header = (SZP_S *)encodedBuffer;
// swap arch. dependent data
header->check = SZP_MAGIC;
header->decodedSize = Swap32(decodedSize);
header->linkOffset = Swap32(sizeof(SZP_S) + maskSize);
header->chunkOffset = Swap32(sizeof(SZP_S) + maskSize + linkSize);
// copy all buffer to final buffer
memcpy((u8 *)header + sizeof(SZP_S), maskBuffer, maskSize);
memcpy((u8 *)header + sizeof(SZP_S) + maskSize, linkBuffer, linkSize);
memcpy((u8 *)header + sizeof(SZP_S) + maskSize + linkSize, chunkBuffer, chunkSize);
SZP_FREE(maskBuffer);
SZP_FREE(linkBuffer);
SZP_FREE(chunkBuffer);
}
// Decodes an SZP block. Decoding is incredibly fast!
// In: Memory mapped compressed buffer and its length in bytes.
// Out: Pre-allocated destination buffer (length can be obtained from buffer's
// header), decompressed buffer length in bytes.
void SZP_Decompress(void *dest, int *destLen, void *src, int srcLen)
{
u8 * encodedBuffer = (u8 *)src;
SZP_S * header;
u8 * decodedBuffer;
u32 decodedBytes, decodedSize, linkOffset, chunkOffset;
u32 mask, maskBitsLeft, maskOffset;
u32 aux;
header = (SZP_S *)encodedBuffer;
decodedSize = Swap32(header->decodedSize); // size of decoded data
linkOffset = Swap32(header->linkOffset); // link table
chunkOffset = Swap32(header->chunkOffset); // byte chunks and count modifiers
decodedBytes = 0; // current offset in dest buffer
maskBitsLeft = 0; // mask bit counter
maskOffset = 16; // current offset in mask table
decodedBuffer = (u8 *)dest;
//memset(decodedBuffer, 0, decodedSize);
*destLen = decodedSize;
do
{
// if all bits are done, get next mask
if(maskBitsLeft == 0)
{
// read word from mask data block
mask = Swap32(*(u32 *)(encodedBuffer + maskOffset));
maskOffset += 4;
maskBitsLeft = 32; // bit counter
}
// if next bit is set, chunk is non-linked
if(mask & 0x80000000)
{
// get next byte
*(u8 *)(decodedBuffer + decodedBytes) = *(u8 *)(encodedBuffer + chunkOffset);
chunkOffset++, decodedBytes++;
}
// do copy, otherwise
else
{
u16 link;
u32 count;
u8 * pointer;
// read 16-bit from link table
link = Swap16(*(u16 *)(encodedBuffer + linkOffset));
linkOffset += 2;
// 'offset'
pointer = decodedBuffer + decodedBytes - ( (link & 0xfff) + 1);
// 'count'
count = link >> 12;
if(count == 0)
{
// get 'count' from chunks table
count = *(u8 *)(encodedBuffer + chunkOffset) + 18;
chunkOffset++;
}
else count += 2;
// do block copy
for(aux=0; aux<count; aux++)
{
*(u8 *)(decodedBuffer + decodedBytes) = *pointer;
decodedBytes++, pointer++;
}
}
// next bit in mask
mask <<= 1;
maskBitsLeft--;
} while(decodedBytes < decodedSize);
}
void ZLIB_Compress(void *dest, int *destLen, void *src, int srcLen)
{
compress((Byte *)dest, (uLong *)destLen, (Byte *)src, (uLong)srcLen);
}
void ZLIB_Decompress(void *dest, int *destLen, void *src, int srcLen)
{
uncompress((Byte *)dest, (uLong *)destLen, (Byte *)src, (uLong)srcLen);
}
// --- Quick File Compress/Decompress (memory mapping used! So do not apply on huge files)
bool FileCompress(char *to, char *from)
{
void *buf, *filedata;
u32 filesize = 0, packedSize = 0;
filedata = FileLoad(from, &filesize);
if(filedata == NULL) return false;
buf = SZP_ALLOC(filesize*2);
if(buf == NULL)
{
Free(filedata);
return false;
}
SZP_Compress(buf, (int *)&packedSize, filedata, (int)filesize);
bool result = FileSave(to, buf, packedSize);
Free(filedata);
SZP_FREE(buf);
return result;
}
bool FileDecompress(char *to, char *from)
{
u32 filesize, unpackedSize;
void *filedata = FileLoad(from, &filesize);
if(filedata == NULL) return false;
SZP_S *header = (SZP_S *)filedata;
if(header->check != SZP_MAGIC)
{
Free(filedata);
return false;
}
void *buf = SZP_ALLOC(Swap32(header->decodedSize));
if(buf == NULL)
{
Free(filedata);
return false;
}
SZP_Decompress(buf, (int *)&unpackedSize, filedata, (int)filesize);
bool result = FileSave(to, buf, unpackedSize);
Free(filedata);
SZP_FREE(buf);
return result;
}