// ******************************************************************************** //
// **Creator:    Dazzle                Licence:      Diku Merc Rom Terms         ** //
// **Difficulty:    3                  Snippet:      Duplication Check           ** // 
// ******************************************************************************** //
// **                           Contact Information                              ** //
// **Yahoo:      ldevil.geo                 Msn:          ldevil@hotmail.com     ** //
// **Aim:        pocketweasle               Email:   sandstorm@patrickcooper.ca  ** //
// **Webpage:    http://sandstorm.arthmoor.com                                   ** //
// ******************************************************************************** //
// **Terms of Usage:                                                             ** //
// **Follow All the Licences listed above, also, if you have a snippet helpfile  ** //
// **Put my name in there, if not, leave my name in the source.                  ** //
// **Also, this code is given AS-IS, straight from my mud, so there will be some ** //
// **Effort required to make this work in your mud.                              ** //
// **Install at your own risk, if you have any comments or questions on this     ** //
// **Visit the website mentioned above, and enter the forum, post and bugs or    ** //
// **suggestions there.                                                          ** //
// ******************************************************************************** //


// ******************************************************************************** //
// **           Version 2.00, mudmagic has version 1.05, enjoy the update        ** //
// ** Reported bugs are fixed, aswell as updated for better quicker usage.       ** //
// **                               And more POWER!!!!                           ** //
// ******************************************************************************** //
// **             Warning.. this file contains vulgar language                   ** //
// ******************************************************************************** //


2.00 changes
	Destroyed all known bugs in system
	grab_timestamp function added
		-- Updated timestamp locations in keygen to use this instead
			of grab_time_log and grab_date log.. they are now used
			in grab_timestamp. (random for which one it pulls)
	dupecheck command now has options for searching individual online players.
		-- check_player function added
	dupe_update modified.
		-- Now does character checks, and every 1 in 100 times, it will
			do a complete obj_list check
		-- Recommendation using both install options to optimize your
			protection against dupes.
	Added our own MD5 Support!
		-- See cd_md5.c and cd_md5.h
		-- Other options like RSA and SHA256 were availabe
			but didn't have the string compression of md5
		-- Updated md5 message in keygen
	last_unique_key expanded.
		-- Now expands to 2bil, did this for kicks, no other reason :)
	Cleaned up the switch statements (uses less space now)
		-- They were just bloating the overall size of keygen the way they were
			so they are cleaned up now.
	New has_duped system designed... Ooooh, this will be fun :)
		-- Once an has been detected as 'duped' the system will
			flag the object, if it detects another dupe, it will
			remove both items from the game completely, this will
			screw people over.. normal it just purges the 'dupes'
			with this, it will purge the original aswell.
			Take that dupers!

1.06 changes
	Fixed ostat bug.. %ld.. what was i thinking.. should of been %s
	Fixed keygen bug with race/class/skill-spell, now has default to stop bug.
		-- That includes the ->name bug that were missing from the switch's.

1.05 changes
	Put in new options for install
	Removed need for dupe_update (your choice if you want it)
		-- Install choices! ARGH! as if snippets weren't hard enough ;)
	Updated manual start with security feature for confirmation.
		-- now requires you to 'confirm' the check, since this process
			can lag a mud big-time
	Updated keygen for better more unique results(expanded keygen possabilities)
		-- Now uses skills, classes, and races within the massive switch to
			add more depth to the chances
		-- Uses new advanced alphabet cycling, that also contains numbers.
			Perfect for making strings look more confusing.
	Fixed keygen issue.
		-- Keygen could infantly loop in 1.02, never any good
	Fixed memory leak with keygen
		-- Strdups and free_string's the unique_key variable now
			This will stop the stupid crash bug that has appeared
			in some muds that are using this system (though not all seem
				todo it)

	New Unique_key number that saves over reboot/copyover/shutdown
		-- Makes assigning keys to a string more interesting.. and unique.
			makes strings almost impossible to duplicate as it goes up
			to 1billion... ie, 9 digits! :)
	Added Extra security during dupe-check to check for immortal!
		-- immortals with dupes is never a good thing, usualy cheating imms
			trying to exploit for morts, atleast from my experience.
			Now it warns, and logs their name, aswell as stripping the objects

	Expanded 'a' variable in keygen, used to overflow, 1000 just wasn't enough for large
		-- generation


	Added options for making keygen contain more.. usless crap, ya.. i excel at this.
		-- This can GREATLY lengthen a string, beware, its repetative ;)

1.02 changes
	Fixed bugs with system
	Fixed perverbial pwipe bug.
	Made keygen str_dup the key.
	Fixed issue with MD5
	
1.00 features
	detects duplicate items.
	Removes duplicate items.
	Manual start of duplication check
	Automated duplication check on update
	Detects Duplicates on player-login
	Capable of MD5ing the password

**********************************************

Hey Everyone, this is my item-dupe check and cleaner system,  just everyone be forwarned.

There may be compile errors because this system was written in windows notepad and i haven't
had time to try it out myself.

However, i trust that it will work just fine when properly added into a system, mainly because
i have been working with Rom code for the better half of 10 years.


That being said, this function will check all items in the game every-single-time a item is created
loaded, whatever, to detect duplicates.

To ensure that all items are not loaded in the same order each time, I have created a unique item-key
generating system, aswell as assigning a time-stamp to insure that no duplicate items exist.


**********************************************
How Dupe Bugs work:  Dupe bugs take the exact memory key of an existing object, ie, an object from your
pfile while loading, and puts all the data onto the object, and then, to duplicate it,  you do something
funky like drop all your equipment, while another character is loaded at the password prompt (same character)
and the pfile has loaded again from the data-file, so all of that dropped equipment, will be there, aswell as
all your non-dropped equipment from the newly loaded character.

So first off..  We address the main issue, these bugs exist, and just when you think you've got them all, someone
will find another way todo it, but the end result is the same... it still has to run through create_object
and it still has to load the data from somewhere.

So we assign every object a creation time-stamp, and a unique key.

When an object is loaded from a pfile, it will assign the old-time-stamp and unique key.

It is virtualy impossible to duplicate the time-stamp and unique-key on the same object.

The more objects your game has, the less likely it will be that your objects would end up
with the same keys.


**********************************************

Compatability Issues:  Okay, i wrote this for Sandstorm, however, i tried to make it more compatable to stock
rom, so i left out all the C++ from my mud, and went from memory to make this work in stock rom.

You may be required to change some variable names to match that of rom, or if you have changed your base at all.


********************************************************************************************
					Q and A

********************************************************************************************

Answering the question:  Does the keygen actauly help?  Isn't a timestamp good enough for you?

The Answer?   A timestamp is more then efficient, however, the unique_key is just another level
of security.  Many people have asked me, why so secure, as most of my snippets you can see
that security is a prime piece for me.. The answer, i came from a mud that wasn't all that 'secure'
aswell as the fact that i was raised by a security professional.  And am one myself.

I feel that adding to the level of security will greatly help the mud develop past its original
potentials, and thats exactly what the keygen does, sure, it can be long and bulky, but it is less
likely to be duplicated through normal means, making it even more difficult for naturaly existing
duplicates.

**********************************************

Answering the question:  Why make this system instead of fixing the dupe-bugs?

The Answer?  Actauly, this is just to help the battle against duplicates by stopping th em
in their tracks.  If someone wants to dupe, they have to defeat the dupe detection system.

By stopping thier attempts, you have prevented this issue, sure, if there is a dupe-bug, fix it.
By all means, a bug is a bug, and all bugs must be fixed, but if you cannot find the dupe-bug, block
it instead.  Make it so that when you try to exploit a bug, or your players try, they cannot, it detects
removes, and logs the actions.  This should make it easier to track down dupe-bugs

**********************************************

Answering the question:  Why a revision, and why so fast?

The answer?  This got allot of good feedback the very first day of downloads, it got some bad feedback
aswell, i address both issues with my revision, not only is the system expanded, but it functions better
less possible bugs (if any) and as far as i know, it works perfectly.  3 mud admins emailed me day one
and told me that it saved their mud, because their muds had big dupe issue's.  That means that i did something
right, and i want to continue the fight against dupes, by expanding the system for the future generations of
mudders.

Even if your mud doesn't have dupe bugs, it never hurts to have a monitor, just incase.


**********************************************

Answering the question:  Whats the purpose of last_unique_key?  and why bother?

The Answer?  Its pretty simple, last_unique_key is a number, from 0 to 1billion, it increments everytime
keygen is called, and attaches itself to the very end of your unique_key, if a unique_key is duplicated, its
less likely to have the same ending key as another object.  1 in a billion chance to duplicate the last number.

I mean, the more active a mud, with the more objects repopping, the more chance of it wrapping the last_unique_key
back to 0, but even when it does, it still offsets the line, so that the same objects don't get the same end-key
attached, its pretty much impossible.

**********************************************

Answering the question:  Why use race, skills/spells, and classes within the keygen?

The answer?  Why not?  1 in 27 chance to land on that option, then 1 in 3 to land on any of the specific options
listed above.  Creates a new level of random-ness, enhances the key, the more races, classes, and skills/spells that
your mud has, the more possabilities for unique-keys, think of it, as a way to expand your unique-key generation
without modifying keygen();  Add more races, add more classes, add more skills/spells, and you'll get more
unique keys, more unique then the ones before.

**********************************************

Answering the question:   If dupe_check on create_object doesnt' work out as planned, why do it?

The Answer?  Shut up!  :P  yeah, it doesn't work the way it was original intended, however, it does have
some merit.  By some off-chance another object is created with all the same data, timestamp, and unique-key
it will strip the one from the game, think of this as a bug-prevention system for your own code, it should
be impossible to create 2 objects at the same time, with the same key, and timestamp, but create_object
could be buggeg, create 2 objects, link 1, check the other (i've seen it happen, spent nearly 2 weeks fixing
a guys mud... he gave up coding shortly there after, he just couldn't get it..  but thats beside the point)

Why not, if you dont' want it, remove it, just make-sure that you do enough checks during update proceedures
or on dropping/giving of objects, force the mud to check often, to strip away potential threats.

**********************************************

Answering the question:  Okay, options for the update process?  which one is better?

The Answer?  That depends on what you want, the obj_update checks every object for duplicate, every update.

Where-as the dupe_update() checks every 1 in 10 times that it is run.  It depends on how paranoid you want to be.

Personaly, i use obj_update and dupe_check, but my dupe_check has been offset greatly to check once every hour.

**********************************************

Now... Onto the code!


// ****************************************************************************************************************** //
// **                                                     Code                                                     ** //
// ****************************************************************************************************************** //

Add cd_md5.c to your makefile
And makesure that cd_md5.h is in your directory with your headers.


int merc.h

add with externs

extern long last_unique_key;

in your defines, add the following if you want to use some crazy additions
to the keygen system

#define CRAZY_KEYGEN 1

If you don't want to use it.. then do this..

#define CRAZY_KEYGEN 0


In your defines section.. add, you may want to place this with your other data files.
or else this will just save into your area directory with everything else.

#define UNIQUE_KEY_FILE  "unique.key"


in struct obj_data, add 

time_t timestamp;
char * unique_key;
bool   has_duped;

In your prototypes section, add the following

void read_unique_key(void);
void write_unique_key(void);
char *keygen(void);
int dupe_check(obj_data *obj);
void dupe_update(void);
char *grab_timestamp(void);
int check_player(CHAR_DATA *ch, CHAR_DATA *victim);

In your copyover, shutdown, reboot functions (assuming you have them) add the following..

write_unique_key();

This will force the saving of the unique_key data file.


**********************************************
recycle.c should have the next stuff, if not, grep and find it yourself :P

in new_obj add

obj->timestamp = current_time;
obj->unique_key = keygen();
obj->has_duped = false;

in free_obj add

free_string(obj->unique_key);	// Destroy the unique_key

**********************************************
Optional, the following updates are optional
and recommended to imms who want to beable to view this data.


in do_ostat add

// String MD5'd in this version, making, making it easier to read
snprintf(buf, MSL, "Unique Key: %s\n\r", obj->unique_key);	
send_to_char(ch, buf);


// This requires my grab_time_log function
snprintf(buf, MSL, "TimeStamp: %s\n\r", grab_time_log(obj->timestamp));
send_to_char(ch, buf);


**********************************************
in db.c

at the top with the global declarations, add

long last_unique_key;


in boot_db, prior to the loading of objects, add this.

read_unique_key();


at the end of boot_db add the following

write_unique_key();	// Save the key at the end of bootup


in create_object, yes, create_object calls new_obj, why duplicate it?  Because some
admins have this little thing where they skip using create_object and just use new_obj
and after having reports of it, i decided to update it :P so deal with it :P

And some copies of create_object use alloc_perm/alloc_mem instead of using new_obj..
So to save on problems, here was the solution for everyone :)



Add this..

obj->timestamp = current_time;

// *Ensure a new keygen* //
if(!IS_NULLSTR(obj->unique_key))
	free_string(obj->unique_key);	// Free if it isn't clear.
obj->unique_key = keygen();
obj->has_duped = false;

at the VERY bottom of create_object, after all the info has been set to the object and it has been added
to the linked list..

Add this..


// Proven to not do anything but check against current timestamps and strings in the game
// To ensure that this is the only string and timestamp on said vnum
dupe_check(obj);


**********************************************

*** PARANOID CHECKING ***

This here is recommended for paranoid imms, this really makes life more interesting
if you ask me, i haven't noticed any lag from it, but it does make sure that
all every chance a person 'could' have to dupe, is gone.

Warning, doing this will stop so-called 'drop'and relog dupe possabilities, by forcing
the check everytime the object changes location :)


handler.c  obj_to_char

At the bottom of obj_to_char, put..

dupe_check(obj);

This is because that in most places of the mud after create_object
is called, it sends the object to a player, check for duplicates here


In obj_from_char..

at the bottom, put..

dupe_check(obj);


In obj_to_room

at the bottom put...

dupe_check(obj);

**********************************************

If you want to be REALLY paranoid about object duplication, you can always
go through your code, and in every instance of players logging in, out, 
disconnection, linkdead states, the works, and run dupe-check against them.


************************************************

in save.c, search for the create_object call.

Now, depending how your loading of equipment works, this will work diffently.

At the end of the loading of a object, add a 'dupe_check(obj);' call, at this
point in time, your character has the object loaded onto them..

in fwrite_object, you need to add..

fprintf(fp, "Timestamp %ld\n", obj->timestamp);
fprintf(fp, "unique_key %s~\n", obj->unique_key);
fprintf(fp, "has_duped %d\n", obj->has_duped);

in fread_object, you will need to add, put these under the right case heading.


KEY("Timestamp", obj->timestamp, fread_number(fp));
KEY("has_duped", obj->has_duped, fread_number(fp));

KEYS("unique_key", obj->unique_key, fread_string(fp));



If you are using a locker system that has fread_locker and fwrite_locker, make sure
that it has the proper data to read the timestamp and unique_key properly.

**********************************************

Update.c has some options for how you wish to handle the dupe_check updates.
Choose which one you want.

We now recommend that both options be installed as dupe_update will now check
all characters in the char_list, and on random, check the entire obj_list.

And since option2 is a parser of the obj_list, we recommend that it be put in
aswell, so that it is always checking, and an off-chance, of double-checking
inbetween, but ultimatly, it is up to you.

Update.c Option 1

	in update.c  in update_handler, under pulse_update section.
	With all your 'mobile update's and stuff like that, add

		dupe_update();


Update.c Option 2
	In update.c in obj_update, at the beginning of the for-loop that parses
	through the obj_list, it'll look something like that..

	for(obj = obj_list; obj; obj = next_obj)
	{
		next_obj = obj->next;

	Or something like that..

	Anyways, under the next_obj = obj->next;
	put this..

	// *Let handle the duplication check now!* //
	if(dupe_check(obj) > 0)	// Uh oh, duplicates found, extract, and continue on the list.
		continue;
	

**********************************************
in interp.h add this.

DECLARE_DO_FUN(do_dupecheck);


**********************************************
in interp.c..

Add

{"dupecheck", do_dupecheck, POS_DEAD, ML, 1),


**********************************************

// ********************************************* //
// ** Sandstorms Item Dupe Detection System   ** //
// ********************************************* //

// *Dupecheck system by Dazzle(David Simmerson)* //

// * Generate a new unique-key, wicked awesome eh? * //
// * Updated to house new last_unique_key number variable * //
// * aswell as new strings, such as skill names, race names * //
// * and other misc data into a string.* //
// * Yet another revision has been done * //
char *keygen(void)
{
	static char a[10000];	// Yea, it can get this big
	char last_unique_key_buffer[20];	// never exceeds 20 unless you expand how high the key can get

	int x = 0, y = number_range(7, 30);	// up to 30 rotations of the loop, this could get big..

	// *Nullified eh?  Take that!* //
	a[0] = '\0';

	// *Another nullified piece of data* //
	last_unique_key_buffer[0] = '\0';
	snprintf(last_unique_key_buffer, 20, "l_key_%ld",last_unique_key);

	// *Over 1billion!?  reset please* //
	if(++last_unique_key > 2000000000 )	// Yes, thats 2billion.. woweee
	{
		log_string(LOG_SECURITY, "%s: last_unique_key reached 2bil, resetting to 0.", __PRETTY_FUNCTION__ );
		last_unique_key = 0;
	}

	// *1 in 4 chance to save the unique_key, solong as the bootDb is false, only save after boots* //
	if(number_range(0, 3) == 0 && fBootDb == FALSE)
	{
		write_unique_key();
	}


	// Start off the unique-key, tis fun, Now contains special greetings to certain names in
	// the mudding community that have in some, way, shape or form helped or hindered me in
	// my growth as a coder, also just random crap, this is used to start off each unique
	// string, strcpy's, ie, sets the memory, so not strcating goes on prior to this.
	switch(number_range(0, 16))
	{
		case 0:	strcpy(a, "sstm_");	break;		case 1:	strcpy(a, "unique_");	break;
		case 2:	strcpy(a, "key_");	break;		case 3:	strcpy(a, "elvis_");	break;
		case 4:	strcpy(a, "lick_me_");	break;  	case 5:	strcpy(a, "sandstorm_"); break;
		case 6: strcpy(a, "mages_");     break;		case 7: strcpy(a, "sanctuary_"); break;
		case 8: strcpy(a, "mindeye_sux_"); break; 	case 9: strcpy(a, "hello_runter_"); break;
		case 10: strcpy(a, "morning_samson_"); break; 	case 11: strcpy(a, "evening_davion_"); break;
		case 12: strcpy(a, "afternoon_kyndig_"); break; case 13: strcpy(a, "crap_"); break;
		case 14: strcpy(a, "mudmagic_"); break;		case 15: strcpy(a, "dupe_this_"); break;
		case 16: strcpy(a, "terminator_"); break;

		// *SHOULD NEVER HAPPEN!* //
		default: strcpy(a, "unique_sstm_key_");	break;
	}

	// *Yes, with crazy installed, you will get bigger loops.* //
	if(CRAZY_KEYGEN == 1)
	{
		y = number_range(20, 45);	// 45... this is HUGE if you use this!
	}

	// *Parse the generation list, expanded for new fun possabilities* //
	for(x = 0; x < y; ++x)
	{
		// *Start the switching* //
		switch(number_range(0, 27)
		{
			case 1:			case 2:
			case 3:			case 4:
			// Alphabet, lowercase
			switch(number_range(0, 24))
			{
				case 1: strcat(a, "a"); break;		case 2: strcat(a, "b"); break;
				case 3: strcat(a, "c"); break;		case 4: strcat(a, "d"); break;
				case 5: strcat(a, "e"); break;		case 6: strcat(a, "f"); break;
				case 7: strcat(a, "g"); break;		case 8: strcat(a, "h"); break;
				case 9: strcat(a, "i"); break;		case 10: strcat(a, "j"); break;
				case 11: strcat(a, "k"); break;		case 12: strcat(a, "l"); break;
				case 13: strcat(a, "m"); break;		case 14: strcat(a, "n"); break;
				case 15: strcat(a, "o"); break;		case 16: strcat(a, "p"); break;
				case 17: strcat(a, "q"); break;		case 18: strcat(a, "r"); break;
				case 19: strcat(a, "s"); break;		case 20: strcat(a, "t"); break;
				case 21: strcat(a, "u"); break;		case 22: strcat(a, "v"); break;
				case 23: strcat(a, "w"); break;		case 24: strcat(a, "x"); break;
				case 25: strcat(a, "y"); break;		default: strcat(a, "z"); break;
			}
			break;

			case 5:			case 6:
			case 7:
				char uick[3];

				// *Add a number to the list* //
				snprintf(uick, 3, "%d", number_range(0, 9);
				strcat(a, uick);
				break;

			case 8:			case 9:
			case 10:		case 11:
			case 12:
				char uick[3];

				// *Add a number to the list, larger numbers here.* //
				snprintf(uick, 3, "%d", number_range(0, 99);
				strcat(a, uick);
				break;

			case 13:		case 14:
			case 15:		case 16:
			case 17:		case 18:

			// *Afew ascii characters to make it more fun, no ~ cuz of string handling ;)*//
			switch(number_range(0, 17))
			{
				case 1: strcat(a, "!"); break;		case 2: strcat(a, "@"); break;
				case 3: strcat(a, "#"); break;		case 4: strcat(a, "$"); break;
				case 5: strcat(a, "^"); break;		case 6: strcat(a, "&"); break;
				case 7: strcat(a, "*"); break;		case 8: strcat(a, "("); break;
				case 9: strcat(a, ")"); break;		case 0: strcat(a, "|"); break;
				case 10: strcat(a, "["); break;		case 11: strcat(a, "]"); break;
				case 12: strcat(a, ";"); break;		case 13: strcat(a, "?"); break;
				case 14: strcat(a, "_"); break;		case 15: strcat(a, "+"); break;
				case 16: strcat(a, "="); break;		case 17: strcat(a, "`"); break;
				default: strcat(a, "-"); break;
			}
			break;

			case 19:		case 20:
			case 21:		case 22:
			// Alphabet, Upper Case
			switch(number_range(0, 24))
			{
				case 1: strcat(a, "A"); break;		case 2: strcat(a, "B"); break;
				case 3: strcat(a, "C"); break;		case 4: strcat(a, "D"); break;
				case 5: strcat(a, "E"); break;		case 6: strcat(a, "F"); break;
				case 7: strcat(a, "G"); break;		case 8: strcat(a, "H"); break;
				case 9: strcat(a, "I"); break;		case 10: strcat(a, "J"); break;
				case 11: strcat(a, "K"); break;		case 12: strcat(a, "L"); break;
				case 13: strcat(a, "M"); break;		case 14: strcat(a, "N"); break;
				case 15: strcat(a, "O"); break;		case 16: strcat(a, "P"); break;
				case 17: strcat(a, "Q"); break;		case 18: strcat(a, "R"); break;
				case 19: strcat(a, "S"); break;		case 20: strcat(a, "T"); break;
				case 21: strcat(a, "U"); break;		case 22: strcat(a, "V"); break;
				case 23: strcat(a, "W"); break;		case 24: strcat(a, "X"); break;
				case 25: strcat(a, "Y"); break;		default: strcat(a, "Z"); break;
			}
			break;

			case 23:		case 24:
			switch(number_range(0, 3))
			{
				case 0: strcat(a, "_dave_"); break;	case 1:	strcat(a, "_loves_"); break;
				case 2:	strcat(a, "_courtney_"); break;	case 3: strcat(a, "_allot_"); break;

				// Default if something explodes.
				default: strcat(a, "_dave_loves_courtney_"); break;
			}
			break;

			// Case 25....  This is where each mud's unique keys will be really
			// Different based on skills/spells, races, and classes.
			case 25:
			int poo = 0;

			// Okay, LET THE FUN BEGIN!
			switch(number_range(0, 3))
			{
				case 1:
					poo = number_range(1, MAX_SKILL-1);

					// Null Name.??  Make it the number then'
					if(IS_NULLSTR(skill_table[poo].name))
					{
						char b[MAX_SKILL];
						b[0] = '\0';
						snprintf(b, MAX_SKILL, "%d", poo);
						strcat(a, b);
					}
					else	// Not null... Grab the skill name
					{
						char b[3];
						b[0] = '\0';
						// Grab the first 3 letters of the skill name, interesting ;)
						snprintf(b, 3, "%3s", skill_table[poo].name);
						strcat(a, b);
					}
					break;
				case 2:
					poo = number_range(1, MAX_RACE-1);
					if(IS_NULLSTR(race_table[poo].name))
					{
						char b[MAX_RACE];
						b[0] ='\0';
						snprintf(b, MAX_RACE], "%d", poo);
						strcat(a, b);
					}
					else
					{
						char b[3];
						b[0] = '\0';
						snprintf(b, 3, "%3s", race_table[poo].name);
						strcat(a, b);
					}
					break;
				case 3:
					poo = number_range(1, MAX_CLASS-1);
					if(IS_NULLSTR(class_table[poo].name))
					{
						char b[MAX_CLASS];
						b[0] ='\0';
						snprintf(b, MAX_CLASS, "%d", poo);
						strcat(a, b);
					}
					else
					{
						char b[3];
						b[0] = '\0';
						snprintf(b, 3, "%3s", class_table[poo].name);
						strcat(a, b);
					}
					break;
				default:	// Socials.. MUAHAHA
					poo = number_range(1, MAX_SOCIAL-1);
					if(IS_NULLSTR(social_table[poo].name))
					{
						char b[MAX_SOCIAL];
						b[0] ='\0';
						snprintf(b, MAX_SOCIAL, "%d", poo);
						strcat(a, b);
					}
					else
					{
						char b[3];
						b[0] = '\0';
						snprintf(b, 3, "%3s", social_table[poo].name);
						strcat(a, b);
					}
					break;
					break;
			}
			break;

			case 26:
			int hud = number_range(0, 9);
			char huj[3];
			huj[0] = '\0';

			// Alphabet, Upper Case
			switch(number_range(0, 24))
			{
				case 1: snprintf(huj, 3, "A%d",hud); break;	case 2: snprintf(huj, 3, "B%d",hud); break;
				case 3: snprintf(huj, 3, "C%d",hud); break;	case 4: snprintf(huj, 3, "D%d",hud); break;
				case 5: snprintf(huj, 3, "E%d",hud); break;	case 6: snprintf(huj, 3, "F%d",hud); break;
				case 7: snprintf(huj, 3, "G%d",hud); break;	case 8: snprintf(huj, 3, "H%d",hud); break;
				case 9: snprintf(huj, 3, "I%d",hud); break;	case 10: snprintf(huj, 3,"J%d",hud); break;
				case 11: snprintf(huj, 3,"K%d",hud); break;	case 12: snprintf(huj, 3,"L%d",hud); break;
				case 13: snprintf(huj, 3,"M%d",hud); break;	case 14: snprintf(huj, 3,"N%d",hud); break;
				case 15: snprintf(huj, 3,"O%d",hud); break;	case 16: snprintf(huj, 3,"P%d",hud); break;
				case 17: snprintf(huj, 3,"Q%d",hud); break;	case 18: snprintf(huj, 3,"R%d",hud); break;
				case 19: snprintf(huj, 3,"S%d",hud); break;	case 20: snprintf(huj, 3,"T%d",hud); break;
				case 21: snprintf(huj, 3,"U%d",hud); break;	case 22: snprintf(huj, 3,"V%d",hud); break;
				case 23: snprintf(huj, 3,"W%d",hud); break;	case 24: snprintf(huj, 3,"X%d",hud); break;
				case 25: snprintf(huj, 3,"Y%d",hud); break;	default: snprintf(huj, 3,"Z%d",hud); break;
			}

			strcat(a, huj);
			break;

			case 27:
			int hud = number_range(0, 9);
			char huj[3];
			huj[0] = '\0';

			// Alphabet, LowerCase
			switch(number_range(0, 24))
			{
				case 1: snprintf(huj, 3, "a%d",hud); break;	case 2: snprintf(huj, 3, "b%d",hud); break;
				case 3: snprintf(huj, 3, "c%d",hud); break;	case 4: snprintf(huj, 3, "d%d",hud); break;
				case 5: snprintf(huj, 3, "e%d",hud); break;	case 6: snprintf(huj, 3, "f%d",hud); break;
				case 7: snprintf(huj, 3, "g%d",hud); break;	case 8: snprintf(huj, 3, "h%d",hud); break;
				case 9: snprintf(huj, 3, "i%d",hud); break;	case 10: snprintf(huj, 3,"j%d",hud); break;
				case 11: snprintf(huj, 3,"k%d",hud); break;	case 12: snprintf(huj, 3,"l%d",hud); break;
				case 13: snprintf(huj, 3,"m%d",hud); break;	case 14: snprintf(huj, 3,"n%d",hud); break;
				case 15: snprintf(huj, 3,"o%d",hud); break;	case 16: snprintf(huj, 3,"p%d",hud); break;
				case 17: snprintf(huj, 3,"q%d",hud); break;	case 18: snprintf(huj, 3,"r%d",hud); break;
				case 19: snprintf(huj, 3,"s%d",hud); break;	case 20: snprintf(huj, 3,"t%d",hud); break;
				case 21: snprintf(huj, 3,"u%d",hud); break;	case 22: snprintf(huj, 3,"v%d",hud); break;
				case 23: snprintf(huj, 3,"w%d",hud); break;	case 24: snprintf(huj, 3,"x%d",hud); break;
				case 25: snprintf(huj, 3,"y%d",hud); break;	default: snprintf(huj, 3,"z%d",hud); break;
			}

			strcat(a, huj);
			break;

	}

	// Let the crazies begin, this will REALLY expand a generated key greatly!
	if(CRAZY_KEYGEN == 1)
	{
		// *This part of the keygen is really retarded, there is no point* //
		// *in doing this, but at the same time, it makes keygens more   * //
		// *random, which is fine, considering it gets MD5'd now         * //
		if(number_range(0,1) == 1)		strcat(a, grab_timestamp());
		if(number_range(0,1) == 1)		strcat(a, "aeiou-sometimes-y");
		if(number_range(0,1) == 1)		strcat(a, "another-brick-in-the-wall");
		if(number_range(0,1) == 1)		strcat(a, "time...time...");
		if(number_range(0,1) == 1)		strcat(a, "elvis_loves_you");
		if(number_range(0,1) == 1)		strcat(a, "thankyou...thankyou.very.much.");
		if(number_range(0,1) == 1)		strcat(a, "windows-xp");
		if(number_range(0,1) == 1)		strcat(a, grab_timestamp());
	}


	// *Cannot forget the last_unique_key_buffer, tis abit required!* //
	strcat(a, last_unique_key_buffer);

	// Nullify the end of the data, essentaily stop leaks.
	a[strlen(a)] = '\0';


	// *********************************************************************** //
	// * MD5 the string if it is longer then 15 characters(which it should be* //
	// * md5 is now a localy powered system, unlike the samson md5, this     * //
	// * doesn't have a max size for the md5 to use :)                       * //
	// * If the string is less then 15, it won't md5, but the chances of that* //
	// * happening are far, and few between.  so enjoy                       * //
        // *********************************************************************** //
	if(strlen(a) > 15)
	{
	 a = handle_md5(a);
	} // End MD5


	// Strdup's the value, requires disposal, (ie, free_string(blah) )
	return str_dup(a);
}

// *time-stamp!* //
char *grab_timestamp(void)
{
	if(number_range(0, 1) == 1)
		return grab_time_log(current_time);

	return grab_date_log(current_time);
}


// Check the main OBJ against all the objects in the obj_list
int dupe_check(obj_data *obj)
{
	obj_data *list, *list_next;
	int count = 0;
	bool remove_object = false;

	// *Object list manipulation begins... Now* //
	for(list = obj_list; list; list = list_next)
	{
		list_next = list->next;

		// Skip this one cuz frankly, it will be 100% match :P silly people..
		if(list == obj)
			continue;

		// not the same vnum, then its not a duplicate!
		if(list->vnum != obj->vnum)
			continue;

		// *check the timestamp* //
		if(list->timestamp == obj->timestamp)
		{
			// We roped us a duplicate, CASE SENSETIVE!
			if(strcasecmp(list->unique_key, obj->unique_key))
			{
				// Lets log it, and extract the duplicate!
				// Uses my released log system, comment out if you don't have my system
				if(obj->in_room)
					log_string(LOG_SECURITY, "Duplicate found!  %s->%s, removing duplicate!", obj->in_room->name, obj->short_descr);
				else if(obj->carried_by)
				{
					if(IS_IMMORTAL(obj->carried_by))
						log_string(LOG_SECURITY, "Duplicate found!  %s->%s, removing duplicate!(Immortal with duped items! Check logs!)", obj->carried_by->name, obj->short_descr);
					else
						log_string(LOG_SECURITY, "Duplicate found!  %s->%s, removing duplicate!", obj->carried_by->name, obj->short_descr);
				}
				else
					log_string(LOG_SECURITY, "Duplicate found!  %s, removing duplicate!",  obj->short_descr);

				// Strip the object from wherever it is.
				extract_obj(list);
				// *Evil cheating bastards... dupe me once, shame on me, dupe me twice.. Fuck you!* //
				if(obj->has_duped = true)
					remove_object = true;
				obj->has_duped = true;
				++count;
				continue;
			}
		}

		// *Same check again, just go through its contains list.* //
		if(list->contains)
		{
			obj_data *cont_list, *cont_next;

			// *Search for duplicates within containers* //
			for(cont_list = list->contains; cont_list; cont_list = cont_next)
			{
				cont_next = cont_list->next_content;

				// *check the timestamp* //
				if(cont_list->timestamp == obj->timestamp)
				{
					// We roped us a duplicate, CASE SENSETIVE!
					if(strcasecmp(cont_list->unique_key, obj->unique_key))
					{
						// Lets log it, and extract the duplicate!
						// Uses my released log system, comment out if you don't have my system
						if(list->carried_by)	// Just checking
						{
							if(IS_IMMORTAL(list->carried_by))
								log_string(LOG_SECURITY, "Duplicate found!  %s->%s, removing duplicate!(Duped item in object found on immortal: %s)", list->short_descr, cont_list->short_descr, list->carried_by->name);
							else
								log_string(LOG_SECURITY, "Duplicate found!  %s->%s, removing duplicate!", list->short_descr, cont_list->short_descr);
						}
						else
							log_string(LOG_SECURITY, "Duplicate found!  %s->%s, removing duplicate!", list->short_descr, cont_list->short_descr);
		
						// Strip the object from wherever it is.
						extract_obj(cont_list);
						// *Evil cheating bastards... dupe me once, shame on me, dupe me twice.. Fuck you!* //
						if(obj->has_duped = true)
							remove_object = true;
						obj->has_duped = true;
						++count;
						continue;
					}
				}

			}
		}

	}

	// *Take that you evil cheating bastards!* //
	if(remove_object)
		extract_obj(obj);

	// Exit function... sad that i commented this..
	return count;
}


// *Oh yeah!  List checking fun!* //
// *Only use this if you are using option 1* //
// *Updated* //
void dupe_update(void)
{
	obj_data *list, *next_list;
	int count = 0;

	// *Because this can be a long process, we will only do it at random.* //
	if(number_range(0, 100) == 0)	// Small chance, but when it happens, its good cleaning :)
	{
		// Uses my time-of-day system and my log system, remove if you don't use.
		log_string(LOG_SECURITY, "Duplication Check started at:  %s", grab_time_log(current_time));

		// *Parse through the list for duplicates, just-incase...* //
		for(list = obj_list; list; list = next_list)
		{
			int x = 0;
			next_list = list->next;
			x = dupe_check(list);	// Compare to the rest of the lists

			// Ick, fixed this, much better now.
			count =+ x;
		}

		// *Take that Boyo!* //
		if(count > 0)
		{
			log_string(LOG_SECURITY, "Dupe_check cleared %d duplicate items!", count);
		}
	}
	else	// *Yes, we now parse the player-list, less checks then the above object_list.. we hope..* //
	{
		CHAR_DATA *ick, *next_ick;

		// *Parse through the list of characters next* //
		for(ick = char_list; ick; ick = next_ick)
		{
			next_ick = ick->next;
			// *Ooooooooooh!* //
			count =+ check_player(NULL, ick);

		}

		if(count > 0)
		{
			log_string(LOG_SECURITY, "Dupe_check cleared %d duplicate items!", count);
		}

	}
	return;
}

// Uh oh!?  Indevidual player-checking!
int check_player(CHAR_DATA *ch, CHAR_DATA *victim)
{
	obj_data *list, *next_list;
	int count = 0;

	for (list = victim->carrying; list; list = next_list)
	{
		next_list = list->next;

		count =+ dupe_check(list);
	}

	if(ch)
		printf_to_char(ch, "%s had %d dupes.", count);

	// *Take this... Asshole!* //
	if(count > 0)
	{
			log_string(LOG_SECURITY, "Dupe_check cleared %d duplicate items from %s", count, victim->name);
	}

	return count;

}

// Force a dupecheck to start, updated for confirmation.
// Uses printf_to_char snippet by erwin, download if you don't have something similar.
void do_dupecheck(CHAR_DATA *ch, char *argument)
{
	char arg[MAX_INPUT_LENGTH];
	int count = 0;
	obj_data *list, *next_list;
	#define DUPE_KEYWORD "confirm"	// Bad Security attempt here!

	argument = one_argument(argument, arg);

	if(IS_NULLSTR(argument))
	{
		printf_to_char(ch, "You must confirm the dupecheck.  Ie  dupecheck <victim/all> %s\n\r", DUPE_KEYWORD);
		return;
	}

	if(!is_exact_name(argument, DUPE_KEYWORD))
	{
		send_to_char("Please confirm with the proper keyword!\n\r",ch);
		return;
	}


	if(is_exact_name(arg, "all"))
	{
		CHAR_DATA *ick, *next_ick;

		// Uses my time-of-day system and my log system, remove if you don't use.
		log_string(LOG_SECURITY, "Duplication Check started at:  %s", grab_time_log(current_time));

		// *Parse through the list for duplicates, just-incase...* //
		for(list = obj_list; list; list = next_list)
		{
			int x = 0;
			next_list = list->next;
			x = dupe_check(list);
			count = count + x;
		}

		// *Parse through the list of characters next* //
		for(ick = char_list; ick; ick = next_ick)
		{
			next_ick = ick->next;
			// *Ooooooooooh!* //
			count =+ check_player(NULL, ick);

		}

		// *Take that Boyo!* //
		if(count > 0)
		{
			log_string(LOG_SECURITY, "Dupe_check cleared %d duplicate items!", count);
		}
	}
	else
	{
		CHAR_DATA *victim;
		if((victim = get_char_world(ch, arg)) == NULL)
		{
			send_to_char("They are not here!\n\r",ch);
			return;
		}

		// Booya!
		check_player(ch, victim);
		return;
	}
	return;
}


void write_unique_key(void)
{
	FILE *fp;

	if((fp = fopen(UNIQUE_KEY_FILE, "w")) == NULL)
	{
		log_string(LOG_BUG, "Uh oh! couldn't write " UNIQUE_KEY_FILE "for writing");
		return;
	}

	// Simple as pie
	fprintf(fp, "%ld\n",last_unique_key);
	fclose(fp);
	return;
}

void read_unique_key(void)
{
	FILE * fp;

	if((fp = fopen(UNIQUE_KEY_FILE, "r")) == NULL)
	{
		log_string(LOG_BUG, "Uh oh! couldn't write " UNIQUE_KEY_FILE "for reading");
		return;
	}

	// Simple as pie!
	last_unique_key = fread_number(fp);
	fclose(fp);
	return;

}





// ********************************************************************************************* //
// **                              Tips on Dupe Fixing and tracking                           ** // 
// ********************************************************************************************* //

Tip 1, on drop, get, give, take, put, do a save_char_obj

Tip 2, Make sure that when someone tries to bring on a character that is already in the game, that
it boots the active player in the game, and assigns that data to them, instead of loading the pfile
all over again.

Tip 3, Monitor suspicion player activity (like constant logoff's logons with players alts)

Tip 4, IP block-timer, use a 3 minute block-timer after someone quits off of the mud.

Tip 5, players who go linkdead to avoid block-timer, ie, go linkdead, and logon with a different character
	get disconnected and blocked, aswell as having their characters unloaded and saved with any changes
	that may of happened to them (this is evil)

Tip 6, If you cannot find the dupe-bug, make a mortal character, and track down someone who knows how to dupe..
	And watch them do it, get them to show you, and then fix problem in your code.

Tip 7, Use autosnoop code to track known dupers, you may find more then you know happening.

Thats all folks
