FLHash Tool version 2.00
-
Hi all,
Most of you should be familiar with the FLHash command line tool that was created long ago, based on the hashing algorithm decoded by Sherlog in 2003. While very useful, FLHash unfortunately cannot hash Freelancer faction IDs and Freelancer Location IDs (both used in the save game files). There are other programs out there that can do this, but there are still those of us who are using the old tool…
Therefore, I’ve taken the liberty of re-writing the code from ground up and extending the functionality of the tool so that it can now process all three types of hashes. For instructions, simply enter the program name by itself or enter it with the ‘-h’ option. Once again, I’ve included the source code with the executable so that people can know it’s safe. Credits go to Sherlog for figuring out the original algorithm.
I’ll upload the tool as soon as the Downloads section on this site has stabilized.
Cheers,
– The Haen.
-
For the curious, here’s the source code, written in (hopefully) ANSI C. I probably could have coded several things more efficiently, but then again, I’m no coder. Feel free to modify as you see fit.
/* Name: FLHash, version 2.00 Author: Haenlomal Date: November 2009 Purpose: Returns hashcodes for various Freelancer objects All credit goes to Sherlog for discovering the initial Freelancer hash algorithm as it was implemented in the original FLHash.exe. This is free software. Permission to copy, store and use granted as long as this copyright note remains intact. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define _VERSION_STAMP 2.00.00 // The hash table used for generic entities and locations is a modified // CRC-16-IBM Lookup Table using the reversed little-endian polynomial // of 0xA001, extended logically from its original 16-bit specification // to a 32-bit implementation. #define CRC16IBM_POLYNOMIAL 0xA001 // The hash table used for factions is the standard CRC-16-CCITT Lookup Table // using the standard big-endian polynomial of 0x1021. #define CRC16CCITT_POLYNOMIAL 0x1021 // Constants for both LUTs #define NUM_BITS 8 #define HASH_TABLE_SIZE (1 << NUM_BITS) // Error code defines #define SUCCESSFUL_RUN 0 #define WRONG_NUMBER_ARGUMENT 1 #define INVALID_ARGUMENT 2 #define MALLOC_FAILED 3 #define UNKNOWN_ERROR 4 // Global LUT declarations unsigned short crc_16_ccitt_table[HASH_TABLE_SIZE]; unsigned long modded_crc_16_ibm_table[HASH_TABLE_SIZE]; void usage( const char* prog, int i ) { FILE* s; s = i ? stdout : stderr; fprintf( s, "Usage: %s [-e | -f | -h | -l] [nicknames] ...\r\n", prog ); fprintf( s, "\r\n" ); fprintf( s, "%s will print the hash for the supplied nickname(s) in unsigned, signed\r\n", prog ); fprintf( s, "and hex format (comma-delimited list), hashed according to the given flag(s).\r\n" ); fprintf( s, "Flags and nicknames are not case sensitive, and specifying two hash flags in\r\n" ); fprintf( s, "a row will result in the first flag being silently ignored.\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "Flags:\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "-e\tIndicates that all nicknames following the flag are normal Freelancer\r\n" ); fprintf( s, "\tobjects, unless overridden by another flag further down the argument\r\n" ); fprintf( s, "\tchain. %s will hash the nicknames using the default algorithm. If\r\n", prog ); fprintf( s, "\tno flags are specified, this is the default mode.\r\n" ); fprintf( s, "-f\tIndicates that all nicknames following the flag are Freelancer\r\n" ); fprintf( s, "\tfaction IDs, unless overridden by another flag further down the\r\n" ); fprintf( s, "\targument chain. %s will hash the nicknames using the Faction ID\r\n", prog ); fprintf( s, "\thash algorithm.\r\n" ); fprintf( s, "-h\tBrings up usage instructions and exits %s.\r\n", prog ); fprintf( s, "-l\tIndicates that all nicknames following the flag are Freelancer base\r\n" ); fprintf( s, "\tlocations, unless overridden by another flag further down the argument\r\n" ); fprintf( s, "\tchain. The nicknames MUST be in the following format:\r\n" ); fprintf( s, "\t\"<nickname_of_base>,<nickname_of_room>\", or %s will return an\r\n", prog ); fprintf( s, "\terror. %s will hash the nicknames using the Location ID hash\r\n", prog ); fprintf( s, "\talgorithm.\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "Examples:\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "\"%s -e Li01 Li02\"\r\n", prog ); fprintf( s, "\tThis would return the hashcodes for \"Li01\" and \"Li02\", using the\r\n" ); fprintf( s, "\tdefault hash algorithm.\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "\"%s Li01 Li02\"\r\n", prog ); fprintf( s, "\tThis would return the same result as in the first example, since the\r\n" ); fprintf( s, "\t-e flag is optional.\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "\"%s -f li_p_grp gd_bh_grp\"\r\n", prog ); fprintf( s, "\tThis would return the hascodes for \"li_p_grp\" and \"gd_bh_grp\", using\r\n" ); fprintf( s, "\tthe Faction ID hash algorithm.\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "\"%s -l Li01_01_Base,Cityscape Li03_02_Base,Deck\"\r\n", prog ); fprintf( s, "\tThis would return the hashcodes for the \"Cityscape\" room in base\r\n" ); fprintf( s, "\t\"Li01_01_Base\" and the \"Deck\" room in base \"Li03_02_Base\", using the\r\n" ); fprintf( s, "\tLocation ID hash algorithm.\r\n" ); fprintf( s, "\r\n" ); fprintf( s, "\"%s Li01 Li02 -f li_p_grp gd_bh_grp -l Li01_01_Base,Cityscape -e Ku01\"\r\n", prog ); fprintf( s, "\tThis would return the default hashcodes for \"Li01\", \"Li02\", and \"Ku01\",\r\n" ); fprintf( s, "\treturn the Faction ID hashcodes for \"li_p_grp\" and \"gd_bh_grp\", and\r\n" ); fprintf( s, "\tfinally the Location hashcode for the \"Cityscape\" room in base\r\n" ); fprintf( s, "\t\"Li01_01_Base\". Note how each option specified changed how the\r\n" ); fprintf( s, "\tnicknames following them would be interpreted.\r\n" ); } void Initialize_CRC_16_CCITT_Table() { int i, j; unsigned short x; for ( i = 0 ; i < HASH_TABLE_SIZE ; i++ ) { x = i << ( 16 - NUM_BITS ); for ( j = 0; j < NUM_BITS ; j++) { x = ( x & 0x8000 ) ? ( x << 1 ) ^ CRC16CCITT_POLYNOMIAL : ( x << 1 ); } crc_16_ccitt_table[i] = x; } } void Initialize_Modded_CRC_16_IBM_Table() { int i, j; unsigned long x; for ( i = 0; i < HASH_TABLE_SIZE ; i++ ) { x = i; for (j = 0;j < 16 - NUM_BITS ; j++ ) { x = ((x & 1) == 1) ? (x >> 1) ^ (CRC16IBM_POLYNOMIAL << 14) : x >> 1; } modded_crc_16_ibm_table[i] = x; } } char* HexToString( unsigned long l ) { unsigned long i, j, k; char* p; char* q; // Determine length of hex string. // Since input is unsigned long, // the max length of the hex // string is 8\. So force a stop // if we reach 8. i = 0; j = 1; while ( ( l > j ) && ( i < 8 ) ) { i += 1; j *= 16; } // Malloc length + 1\. Freeing memory is // entirely the responsibility of the // caller! p = malloc( i + 1 ); if ( p == NULL ) { fprintf( stderr, "Error: malloc() failed.\r\n" ); exit( MALLOC_FAILED ); } q = p; for ( k = 1 ; k <= i ; k++ ) { j = ( l >> ( ( i - k ) * 4 ) ) & 0xF; j = ( j < 10 ) ? j + 0x30 : j + 0x57; *q =(char)j; q +=1 ; } // terminate the text string with NULL *q = '\0'; return p; } unsigned short MakeID( char* id, int l ) { unsigned short hash; int i; hash = 0xFFFF; for ( i = 0 ; i < l ; i++ ) { hash = ( ( hash & 0xFF00 ) >> NUM_BITS ) ^ ( crc_16_ccitt_table[( hash & 0x00FF ) ^ tolower( id[i] )] ); } return hash; } unsigned long CreateID( char* id, int l ) { unsigned long hash; int i; hash = 0; for ( i = 0; i < l; i++ ) { hash = ( hash >> NUM_BITS ) ^ modded_crc_16_ibm_table[( hash & 0x000000FF ) ^ tolower( id[i] )]; } // Special processing to come up with the finalized hashcode hash = ( hash >> 24 ) | ( ( hash >> 8 ) & 0x0000FF00 ) | ( ( hash << 8 ) & 0x00FF0000 ) | ( hash << 24 ); hash = ( hash >> 2 ) | 0x80000000; return hash; } unsigned long MakeLocationID( char* id, char* room, int l ) { unsigned long hash; char* p; char* q; // Hash the base name hash = CreateID( id, l ); // Change the hex of the hash into a string p = HexToString( hash ); // Concatenate base hash string with "_<room nickname="">" q = malloc( strlen( p ) + strlen( room ) + 2 ); // +2 for "_" and NULL if ( q == NULL ) { fprintf( stderr, "Error: malloc() failed.\r\n" ); exit( MALLOC_FAILED ); } strcpy( q, p ); strcat( q, "_" ); strcat( q, room ); // Hash the newly created string hash = CreateID( q, strlen( q ) ); free( p ); free( q ); return hash; } int main( int argc, char* argv[] ) { int i; unsigned int j, k, crc16ccitt_init, crc16ibm_init, hashmode, expecting_argument; char* p; char* q; char* r; unsigned short x; unsigned long y; if ( argc < 2 ) { fprintf( stderr, "Error: wrong number of arguments.\r\n" ); usage( argv[0], 0 ); exit( WRONG_NUMBER_ARGUMENT ); } // LUTs not initialized yet crc16ibm_init = 0; crc16ccitt_init = 0; // assume hashing normal freelancer entities hashmode = 0; expecting_argument = 0; for ( i = 1 ; i < argc ; i++ ) { p = argv[i]; if ( *p == '-' ) { p += 1; switch ( *p ) { case 'E': case 'e': if ( !crc16ibm_init ) { Initialize_Modded_CRC_16_IBM_Table(); crc16ibm_init = 1; } hashmode = 0; break; case 'F': case 'f': if ( !crc16ccitt_init ) { Initialize_CRC_16_CCITT_Table(); crc16ccitt_init = 1; } hashmode = 1; break; case 'H': case 'h': fprintf( stdout, "%s help:\r\n", argv[0] ); usage( argv[0], 1 ); return ( SUCCESSFUL_RUN ); break; case 'L': case 'l': if ( !crc16ibm_init ) { Initialize_Modded_CRC_16_IBM_Table(); crc16ibm_init = 1; } hashmode = 2; break; default: fprintf( stderr, "Error: invalid flag \"%s\".\r\n", argv[i] ); usage( argv[0], 0 ); exit( INVALID_ARGUMENT ); break; } // Flag processed -- should now expect an argument expecting_argument = 1; } else { switch ( hashmode ) { case 0: // default hash algorithm (decoded by Sherlog) if ( !crc16ibm_init ) // needed in just case no flags specified { Initialize_Modded_CRC_16_IBM_Table(); crc16ibm_init = 1; } y = CreateID( p, strlen( p ) ); fprintf( stdout, "\"%s\", %u, %d, 0x%.8X\r\n", p, y, y, y ); break; case 1: // Faction ID hash algorithm x = MakeID( p, strlen( p ) ); fprintf( stdout, "\"%s\", %u, %d, 0x%.4X\r\n", p, x, x, x ); break; case 2: // Location ID hash algorithm (really just the default called twice) // Two strings separated by a comma. Split them up then // send them to MakeLocationID // Assume one comma, and locate it in argument j = strcspn( p , "," ); if ( j == strlen( p ) ) { fprintf( stderr, "Error: invalid argument.\r\n" ); usage( argv[0], 0 ); exit( INVALID_ARGUMENT ); } // Parse out the 1st argument q = malloc( j + 1 ); if ( q == NULL ) { fprintf( stderr, "Error: malloc() failed.\r\n" ); exit( MALLOC_FAILED ); } for ( x = 0 ; x < j ; x++ ) { q[x] = p[x]; } q[j] = '\0'; // Parse out the 2nd argument k = strlen( p ) - j - 1; r = malloc( k + 1 ); if ( r == NULL ) { fprintf( stderr, "Error: malloc() failed.\r\n" ); free( q ); exit( MALLOC_FAILED ); } for ( x = 0 ; x < k ; x++ ) { r[x] = p[j + x + 1]; } r[k] = '\0'; // Arguments parsed. Now invoke the function // and return the results. y = MakeLocationID(q, r, strlen ( q ) ); fprintf( stdout, "\"%s\", %u, %d, 0x%.8X\r\n", p, y, y, y ); free ( q ); free ( r ); break; default: fprintf( stderr, "Error: unknown error." ); exit( UNKNOWN_ERROR ); break; } // No longer expecting an argument, though there // is possibly more. expecting_argument = 0; } } // If we are still expecting an argument, this means user did // not supply text after specifying a flag. In other words, the // wrong number of arguments was given. if ( expecting_argument ) { fprintf( stderr, "Error: wrong number of arguments.\r\n" ); usage( argv[0], 0 ); exit( WRONG_NUMBER_ARGUMENT ); } return ( SUCCESSFUL_RUN ); } Edit: Forgot the code formatting brackets...silly me. Thanks, M0tah[/i][/i][/i][/i]</room></nickname_of_room></nickname_of_base></errno.h></string.h></stdlib.h></stdio.h>
-
M0tah wrote:
For not being a coder that’s excellent code; great work and thanks for sharing!One suggestion for future posts: enclosing the code in```
tags preserves the formatting of the text so indentation isn’t lost.Oops…can’t believe I forgot about the formatting. This has now been fixed. Thanks, M0tah.
– The Haen.
-
Well, what can I say?
You guys are overwhelming!!
-
May not be the right place here to ask this, but not having any experience with C I’m itching to know your secrets guys…
With source code like this one, how would I go about compiling it, what do I need, and what would it give me, an .exe file?
-
First you will need a C compiler. A nice free one is GCC. Instructions on how to install the Windows port can be found here. Once you have GCC installed you can compile the program. Save the source code to a .c file, such as flhash.c. Then open a command prompt and cd to the directory containing flhash.c. Enter the command
gcc -o flhash.exe flhash.c
This will compile the source code in flhash.c to an executable file called flhash.exe. You should be able to run flhash and it will print out the help.```flhash
Error: wrong number of arguments.
Usage: flhash [-e | -f | -h | -l] [nicknames] …
… -
Thank you!!
-
Uploads seem to be working again, so I’ve uploaded the file onto the site. It should be there soon pending admin approval.
– The Haen.
-
Duhhhh……
OK, I give up!
Where is it??!!
-
StarTrader wrote:
Duhhhh……OK, I give up!
Where is it??!!
Huh? It’s right there under the Downloads section…2nd item under the Freelancer Critical Downloads section as of the posting of this post.
– The Haen.
-
Ah!
Oh yes, got it thanks…
blind as usual!