lfnetwork.com mark read register faq members calendar

Thread: Q3 Engine Game Bugs / JA bugs
Thread Tools Display Modes
Post a new thread. Add a reply to this thread. Indicate all threads in this forum as read. Subscribe to this forum. RSS feed: this forum RSS feed: all forums
Old 01-23-2008, 12:47 PM   #201
ensiform
The Stig
 
ensiform's Avatar
 
Join Date: Nov 2004
Location: Sawtooth Cauldron
Posts: 1,241
Current Game: Borderlands 2
Means if you try to check any of the other force power values, you get 0 regardless of what it would be on server.


iojamp project lead / coder
ensiform is offline   you may: quote & reply,
Old 02-12-2008, 06:34 PM   #202
ensiform
The Stig
 
ensiform's Avatar
 
Join Date: Nov 2004
Location: Sawtooth Cauldron
Posts: 1,241
Current Game: Borderlands 2
Angry New Q3 Engine exploit

It appears any cvar that is part of userinfo is susceptible to being too large and thus making the userinfo string bigger than 1024 (which would likely cause the IP string and others too may not then be retrieved with trap_GetUserinfo(...) as its not there because it was chopped off).

This can be a hazard because then ban checks cannot be performed. >.<

Fix: Well Luigi has a Windows only patch for it but I haven't heard it tested with q3 and its not supported by Linux.

Other possibilities: Enlarge the buffers in ClientConnect/ClientUserinfoChanged and check that its actual length is not greater than 1024. Check that there is indeed \ip\ in the string (You wouldn't want to also check for the value I guess because the value is lost after first connect.)


iojamp project lead / coder
ensiform is offline   you may: quote & reply,
Old 02-20-2008, 01:25 PM   #203
ensiform
The Stig
 
ensiform's Avatar
 
Join Date: Nov 2004
Location: Sawtooth Cauldron
Posts: 1,241
Current Game: Borderlands 2
Info_ValueForKey function calls are quite expensive as they must parse the entire string every time it is called.

Improvement: Use a string hashing method and compare the tokens using Info_NextPair. An example of this can be seen from lucel in the NoQuarter ET mod source.


iojamp project lead / coder
ensiform is offline   you may: quote & reply,
Old 02-24-2008, 07:52 PM   #204
JediDog
Rookie
 
JediDog's Avatar
 
Join Date: Jan 2008
Location: Florida
Posts: 16
forcepowers crash

Quote:
Originally Posted by =X=Master HeX
in w_force.c & g_client.c, look for:
Code:
	Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );
replace with:
Code:
	if (!(ent->r.svFlags & SVF_BOT))
	{
		char *s;
		s = Info_ValueForKey (userinfo, "forcepowers");
		FR_NormalizeForcePowers(s, strlen(s));
		strcpy( forcePowers, s );
	}
	else
	{
		Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );
	}
Why did you add it in both places?
You only need it in one, did it just in case? I believe if you do it in userinfo it prevents it from being changed to it and in w_force.c it changes to it but it doesn't process it. (i think)
I'm only curious.
JediDog is offline   you may: quote & reply,
Old 02-24-2008, 07:58 PM   #205
ensiform
The Stig
 
ensiform's Avatar
 
Join Date: Nov 2004
Location: Sawtooth Cauldron
Posts: 1,241
Current Game: Borderlands 2
I think you only need to do proper checks in BG_LegalizedForcePowers.


iojamp project lead / coder
ensiform is offline   you may: quote & reply,
Old 02-24-2008, 09:05 PM   #206
JediDog
Rookie
 
JediDog's Avatar
 
Join Date: Jan 2008
Location: Florida
Posts: 16
bg_misc.c\BG_LegalizedForcePowers is also a good place to put it but the other way is kind of easier in my opinion.

Strange... I just tried this force crash 'patch' in w.force.c first and it worked, it didn't crash. I try it in g_client.c\ClientUserInfoChanged and it doesn't prevent the crash. I did everything exactly right but instead of putting it in both places I put it only in userinfo and it isn't working, rawr!!
What would be the cause of this?
JediDog is offline   you may: quote & reply,
Old 02-24-2008, 09:28 PM   #207
ensiform
The Stig
 
ensiform's Avatar
 
Join Date: Nov 2004
Location: Sawtooth Cauldron
Posts: 1,241
Current Game: Borderlands 2
Code:
qboolean BG_LegalizedForcePowers(char *powerOut, int maxRank, qboolean freeSaber, int teamForce, int gametype, int fpDisabled)
{
	char powerBuf[128] = {0};
	char readBuf[128] = {0};
	qboolean maintainsValidity = qtrue;
	int powerLen = strlen(powerOut);
	int i = 0;
	int c = 0;
	int allowedPoints = 0;
	int usedPoints = 0;
	int countDown = 0;
	
	int final_Side;
	int final_Powers[NUM_FORCE_POWERS];

	//[BugFix36]
	//blank out the final_Powers array in case we get garbage in powerOut.
	memset(final_Powers, 0, sizeof(final_Powers));
	//[/BugFix36]

	if (powerLen >= 128)
	{ //This should not happen. If it does, this is obviously a bogus string.
		//They can have this string. Because I said so.
		Q_strncpyz(powerBuf, "7-1-032330000000001333", sizeof(powerBuf));
		maintainsValidity = qfalse;
	}
	else
	{
		Q_strncpyz(powerBuf, powerOut, sizeof(powerBuf)); //copy it as the original
	}

	//first of all, print the max rank into the string as the rank
	strcpy(powerOut, va("%i-", maxRank));

	while (i < 128 && powerBuf[i] && powerBuf[i] != '-')
	{
		i++;
	}
	i++;
	while (i < 128 && powerBuf[i] && powerBuf[i] != '-')
	{
		readBuf[c] = powerBuf[i];
		c++;
		i++;
	}
	readBuf[c] = 0;
	i++;
	//at this point, readBuf contains the intended side
	final_Side = Q_atoi(readBuf);

	if (final_Side != FORCE_LIGHTSIDE &&
		final_Side != FORCE_DARKSIDE)
	{ //Not a valid side. You will be dark. Because I said so. (this is something that should never actually happen unless you purposely feed in an invalid config)
		final_Side = FORCE_DARKSIDE;
		maintainsValidity = qfalse;
	}

	if (teamForce)
	{ //If we are under force-aligned teams, make sure we're on the right side.
		if (final_Side != teamForce)
		{
			final_Side = teamForce;
			//maintainsValidity = qfalse;
			//Not doing this, for now. Let them join the team with their filtered powers.
		}
	}

	//Now we have established a valid rank, and a valid side.
	//Read the force powers in, and cut them down based on the various rules supplied.
	c = 0;
	//[BugFix36]
	while (i < 128 && powerBuf[i] && powerBuf[i] != '\n' && powerBuf[i] != '\r'  //standard sanity checks
		&& powerBuf[i] >= '0' && powerBuf[i] <= '3' && c < NUM_FORCE_POWERS)
	//while (i < 128 && powerBuf[i] && powerBuf[i] != '\n' && c < NUM_FORCE_POWERS)
	//[/BugFix36]
	{
		readBuf[0] = powerBuf[i];
		readBuf[1] = 0;
		final_Powers[c] = Q_atoi(readBuf);
		c++;
		i++;
	}

	//final_Powers now contains all the stuff from the string
	//Set the maximum allowed points used based on the max rank level, and count the points actually used.
	allowedPoints = forceMasteryPoints[maxRank];

	i = 0;
	while (i < NUM_FORCE_POWERS)
	{ //if this power doesn't match the side we're on, then 0 it now.
		if (final_Powers[i] &&
			forcePowerDarkLight[i] &&
			forcePowerDarkLight[i] != final_Side)
		{
			final_Powers[i] = 0;
			//This is only likely to happen with g_forceBasedTeams. Let it slide.
		}

		if ( final_Powers[i] &&
			(fpDisabled & (1 << i)) )
		{ //if this power is disabled on the server via said server option, then we don't get it.
			final_Powers[i] = 0;
		}

		i++;
	}

	if (gametype < GT_TEAM)
	{ //don't bother with team powers then
		final_Powers[FP_TEAM_HEAL] = 0;
		final_Powers[FP_TEAM_FORCE] = 0;
	}

	usedPoints = 0;
	i = 0;
	while (i < NUM_FORCE_POWERS) {
		countDown = 0;

		countDown = final_Powers[i];
		//[MBQ3FILLFIX]
		if(countDown > 3) {
			return qfalse; //-1
		}
		//[/MBQ3FILLFIX]

		while (countDown > 0)
		{
			usedPoints += bgForcePowerCost[i][countDown]; //[fp index][fp level]
			//if this is jump, or we have a free saber and it's offense or defense, take the level back down on level 1
			if ( countDown == 1 &&
				((i == FP_LEVITATION) ||
				 (i == FP_SABER_OFFENSE && freeSaber) ||
				 (i == FP_SABER_DEFENSE && freeSaber)) )
			{
				usedPoints -= bgForcePowerCost[i][countDown];
			}
			countDown--;
		}

		i++;
	}

	if (usedPoints > allowedPoints)
	{ //Time to do the fancy stuff. (meaning, slowly cut parts off while taking a guess at what is most or least important in the config)
		int attemptedCycles = 0;
		int powerCycle = 2;
		int minPow = 0;
		
		if (freeSaber)
		{
			minPow = 1;
		}

		maintainsValidity = qfalse;

		while (usedPoints > allowedPoints)
		{
			c = 0;

			while (c < NUM_FORCE_POWERS && usedPoints > allowedPoints)
			{
				if (final_Powers[c] && final_Powers[c] < powerCycle)
				{ //kill in order of lowest powers, because the higher powers are probably more important
					if (c == FP_SABER_OFFENSE &&
						(final_Powers[FP_SABER_DEFENSE] > minPow || final_Powers[FP_SABERTHROW] > 0))
					{ //if we're on saber attack, only suck it down if we have no def or throw either
						int whichOne = FP_SABERTHROW; //first try throw

						if (!final_Powers[whichOne])
						{
							whichOne = FP_SABER_DEFENSE; //if no throw, drain defense
						}

						while (final_Powers[whichOne] > 0 && usedPoints > allowedPoints)
						{
							if ( final_Powers[whichOne] > 1 ||
								( (whichOne != FP_SABER_OFFENSE || !freeSaber) &&
								  (whichOne != FP_SABER_DEFENSE || !freeSaber) ) )
							{ //don't take attack or defend down on level 1 still, if it's free
								usedPoints -= bgForcePowerCost[whichOne][final_Powers[whichOne]];
								final_Powers[whichOne]--;
							}
							else
							{
								break;
							}
						}
					}
					else
					{
						while (final_Powers[c] > 0 && usedPoints > allowedPoints)
						{
							if ( final_Powers[c] > 1 ||
								((c != FP_LEVITATION) &&
								(c != FP_SABER_OFFENSE || !freeSaber) &&
								(c != FP_SABER_DEFENSE || !freeSaber)) )
							{
								usedPoints -= bgForcePowerCost[c][final_Powers[c]];
								final_Powers[c]--;
							}
							else
							{
								break;
							}
						}
					}
				}

				c++;
			}

			powerCycle++;
			attemptedCycles++;

			if (attemptedCycles > NUM_FORCE_POWERS)
			{ //I think this should be impossible. But just in case.
				break;
			}
		}

		if (usedPoints > allowedPoints)
		{ //Still? Fine then.. we will kill all of your powers, except the freebies.
			i = 0;

			while (i < NUM_FORCE_POWERS)
			{
				final_Powers[i] = 0;
				if (i == FP_LEVITATION ||
					(i == FP_SABER_OFFENSE && freeSaber) ||
					(i == FP_SABER_DEFENSE && freeSaber))
				{
					final_Powers[i] = 1;
				}
				i++;
			}
			usedPoints = 0;
		}
	}

	if (freeSaber)
	{
		if (final_Powers[FP_SABER_OFFENSE] < 1)
		{
			final_Powers[FP_SABER_OFFENSE] = 1;
		}
		if (final_Powers[FP_SABER_DEFENSE] < 1)
		{
			final_Powers[FP_SABER_DEFENSE] = 1;
		}
	}
	if (final_Powers[FP_LEVITATION] < 1)
	{
		final_Powers[FP_LEVITATION] = 1;
	}

	i = 0;
	while (i < NUM_FORCE_POWERS)
	{
		if (final_Powers[i] > FORCE_LEVEL_3)
		{
			final_Powers[i] = FORCE_LEVEL_3;
		}
		i++;
	}

	if (fpDisabled)
	{ //If we specifically have attack or def disabled, force them up to level 3. It's the way
	  //things work for the case of all powers disabled.
	  //If jump is disabled, down-cap it to level 1. Otherwise don't do a thing.
		if (fpDisabled & (1 << FP_LEVITATION))
		{
			final_Powers[FP_LEVITATION] = 1;
		}
		if (fpDisabled & (1 << FP_SABER_OFFENSE))
		{
			final_Powers[FP_SABER_OFFENSE] = 3;
		}
		if (fpDisabled & (1 << FP_SABER_DEFENSE))
		{
			final_Powers[FP_SABER_DEFENSE] = 3;
		}
	}

	if (final_Powers[FP_SABER_OFFENSE] < 1)
	{
		final_Powers[FP_SABER_DEFENSE] = 0;
		final_Powers[FP_SABERTHROW] = 0;
	}

	//We finally have all the force powers legalized and stored locally.
	//Put them all into the string and return the result. We already have
	//the rank there, so print the side and the powers now.
	Q_strcat(powerOut, 128, va("%i-", final_Side));

	i = strlen(powerOut);
	c = 0;
	while (c < NUM_FORCE_POWERS)
	{
		Q_strncpyz(readBuf, va("%i", final_Powers[c]), sizeof(readBuf));
		powerOut[i] = readBuf[0];
		c++;
		i++;
	}
	powerOut[i] = 0;

	return maintainsValidity;
}
Thats all I use and seems fine.


iojamp project lead / coder
ensiform is offline   you may: quote & reply,
Old 04-08-2008, 01:05 AM   #208
ensiform
The Stig
 
ensiform's Avatar
 
Join Date: Nov 2004
Location: Sawtooth Cauldron
Posts: 1,241
Current Game: Borderlands 2
There seems to be an issue with using the cgs.scores1/2 for the team score as it uses data from a ConfigString which appears to be somewhat unreliable during map changes on the client side. For instance: Server running a map that ends and starts changing to new map but you started connecting while old map was still running and then you get to Awaiting Snapshot and start the new map load... You will notice that one or both scores on mini-scoreboard are not quite like they should be (see scoreboard for real score).


iojamp project lead / coder
ensiform is offline   you may: quote & reply,
Old 09-26-2009, 03:15 PM   #209
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Screw letting the dead rest in peace, I want to bring this part of the forum alive again.

Basically, the Q3/JKA memory management is poor - Do not follow their examples!
More at this thread (Old JA+ exploit)

"When you assign dynamic memory as a buffer for anything, be sure to free the damn memory when you are done with it!"
Otherwise, if that function is called enough, the memory pool will overflow and crash the server - Not a good thing at all!
For the most part, it's not a problem..but if us mod authors are allocating memory for whatever reason, free it!

-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-26-2009, 03:44 PM   #210
acdcfanbill
..the wonders I have seen
 
acdcfanbill's Avatar
 
Status: Super Moderator
Join Date: Jun 2001
Location: Random Hell Wholes around the
Posts: 5,698
Imperialist Meatbags Guild Member The Walking Carpets Guild Member LFN Staff Member 10 year veteran! 
Quote:
Originally Posted by -=*Raz0r*=- View Post
Screw letting the dead rest in peace, I want to bring this part of the forum alive again.

Basically, the Q3/JKA memory management is poor - Do not follow their examples!
More at this thread (Old JA+ exploit)

"When you assign dynamic memory as a buffer for anything, be sure to free the damn memory when you are done with it!"
Otherwise, if that function is called enough, the memory pool will overflow and crash the server - Not a good thing at all!
For the most part, it's not a problem..but if us mod authors are allocating memory for whatever reason, free it!
I don't see why it's that poor. It's just lazy modders who are used to languages with memory management systems. Being in C, Q3/JKA figure you are going to clean up your own messes

acdcfanbill is offline   you may: quote & reply,
Old 09-26-2009, 04:08 PM   #211
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Nothing calling G_Alloc is freeing that memory - that's rather poor to me.
I myself just use malloc and free it - and after all, why shouldn't we =]?

-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-27-2009, 10:54 AM   #212
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
There's a potential infinite loop in G_RadiusDamage

Look for..
Code:
		if ( dist >= radius ) {
			continue;
		}
After it, add..
Code:
		if(ent->health <= 0)
			continue;
AFAIK this shouldn't be a problem unless you've added an entity that deals out radius damage and can be destroyed itself.

-=*Raz0r*=- is offline   you may: quote & reply,
Old 10-02-2009, 07:07 PM   #213
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
SKIP TO BOTTOM...
Quote:
Originally Posted by THIS DOESN'T WORK
Because of the way chat strings are handled and sent out to each client, players can prepend something to the start of their name to make their chat text appear in the alert area (You know, where it says 'x was y by z' in the top-left)

Easy way to test this is doing '/name .*Blah' and saying anything.

You have a few choices on what you can do..
You can check on every change of their userinfo string, or do something simple and nicer such as this..

In G_SayTo (g_cmds.c) just add in this check before the trap_SendServerCommand call..
Code:
	for (i=0; i<strlen(name); i++)
	{
		if (name[i] == '.')
			continue;
		if (name[i] == '*' && name[i-1] == '.')
			return;
		break;
	}
That will successfully stop them from saying anything if they're trying to use this (minor) exploit.
Other things you can do is alter 'name[i]' and remove that character so they can chat but it won't appear in the alert area.
You could also warn them, change their name, kick them, silence them...whatever you want.
I suppose you can adapt that check to work in ClientUserinfoChanged (g_client.c) if you want.

Happy coding. =]
It appears you can also use '/name **Blah' or whatever, so this 'fix' is useless.
I'll patch this another way some day, unless someone would like to try...


Last edited by -=*Raz0r*=-; 10-31-2009 at 04:55 AM.
-=*Raz0r*=- is offline   you may: quote & reply,
Old 10-02-2009, 08:50 PM   #214
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Quote:
Originally Posted by ensiform View Post
It appears any cvar that is part of userinfo is susceptible to being too large and thus making the userinfo string bigger than 1024 (which would likely cause the IP string and others too may not then be retrieved with trap_GetUserinfo(...) as it's not there because it was chopped off).

This can be a hazard because then ban checks cannot be performed. >.<
As mentioned after, we can check that the value does exist, and ban if not.


Quote:
Originally Posted by ensiform View Post
Fix: Well Luigi has a Windows only patch for it but I haven't heard it tested with q3 and its not supported by Linux.
I'm remember hearing there's a side-effect to that 'fix'


Quote:
Originally Posted by ensiform View Post
Check that there is indeed \ip\ in the string (You wouldn't want to also check for the value I guess because the value is lost after first connect.)
For those wondering how to do this, it's rather simple...
Head over to ClientConnect in g_client.c

Declare a variable like so:
Code:
char TmpIP[32] = {0};
Adapt some code early on in the function so it looks like this:
Code:
	// check to see if they are on the banned IP list
	value = Info_ValueForKey (userinfo, "ip");
	if (!isBot)
		Q_strncpyz( TmpIP, value, sizeof(TmpIP) ); // Used later
	if ( G_FilterPacket( value ) ) {
		return "Banned";
	}
Then after the G_ReadSessionData call, chuck in:
Code:
	if (firstTime && !isBot)
	{
		if(!TmpIP[0])
		{// No IP sent when connecting, probably an unban hack attempt
			client->pers.connected = CON_DISCONNECTED;
			return "Invalid userinfo detected";
		}
		Q_strncpyz(client->sess.IP, TmpIP, sizeof(client->sess.IP));
	}
You can then use client->sess.IP anywhere in the gameside code for whatever reason.


Another way to prevent q3infoboom would be to patch the engine.
I'm not allowed to 'release' the fix, but it involves hooking SV_ConnectionlessPacket and checking lengths..


Last edited by -=*Raz0r*=-; 10-31-2009 at 05:08 AM.
-=*Raz0r*=- is offline   you may: quote & reply,
Old 10-10-2009, 01:02 PM   #215
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
5 in a row!

Some of you know of the 'JA Haxxor Toolkit' and its features..
Well, one of these features is a multi-lined name (You can also make it look like someone else said something)

So, an effective way to combat this? Simple.
Adapt your Info_Validate to look like this...
Code:
static const char badChars[] = { '\n', '\r', '\"', ';' };
qboolean Info_Validate( const char *s ) {
	int i = 0;
	for (i=0; i<sizeof(badChars); i++)
		if ( strchr( s, badChars[i] ) )
			return qfalse;
	return qtrue;
}
That should effectively remove carriage returns, line breaks, semicolons and quotation marks from any field in the userinfo string (Client names are kept in their userinfo string)

EDIT: Silly me, you should also perform this check in the say function (G_Say or something in g_cmds.c)

EDIT: I suppose the semi-logical thing would be to remove all instances of those characters in the string, and afterwards check if there are any characters remaining in the string (To prevent a free method of getting a blank name/etc)


Last edited by -=*Raz0r*=-; 11-13-2009 at 02:28 PM.
-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-11-2011, 12:19 AM   #216
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
There's a missing "firing" animation for the concussion rifle.

In bg_misc.c

Code:
int WeaponAttackAnim[WP_NUM_WEAPONS] =
{
	BOTH_ATTACK1,//WP_NONE, //(shouldn't happen)

	BOTH_ATTACK3,//WP_STUN_BATON,
	BOTH_ATTACK3,//WP_MELEE,
	BOTH_STAND2,//WP_SABER, //(has its own handling)
	BOTH_ATTACK2,//WP_BRYAR_PISTOL,
	BOTH_ATTACK3,//WP_BLASTER,
	BOTH_ATTACK3,//BOTH_ATTACK4,//WP_DISRUPTOR,
	BOTH_ATTACK3,//BOTH_ATTACK5,//WP_BOWCASTER,
	BOTH_ATTACK3,//BOTH_ATTACK6,//WP_REPEATER,
	BOTH_ATTACK3,//BOTH_ATTACK7,//WP_DEMP2,
	BOTH_ATTACK3,//BOTH_ATTACK8,//WP_FLECHETTE,
	BOTH_ATTACK3,//BOTH_ATTACK9,//WP_ROCKET_LAUNCHER,
	BOTH_THERMAL_THROW,//WP_THERMAL,
	BOTH_ATTACK3,//BOTH_ATTACK11,//WP_TRIP_MINE,
	BOTH_ATTACK3,//BOTH_ATTACK12,//WP_DET_PACK,
	BOTH_ATTACK2,//WP_BRYAR_OLD,

	//NOT VALID (e.g. should never really be used):
	BOTH_STAND1,//WP_EMPLACED_GUN,


	BOTH_ATTACK1//WP_TURRET,
};
Replace with

Code:
int WeaponAttackAnim[WP_NUM_WEAPONS] =
{
	BOTH_ATTACK1,//WP_NONE, //(shouldn't happen)

	BOTH_ATTACK3,//WP_STUN_BATON,
	BOTH_ATTACK3,//WP_MELEE,
	BOTH_STAND2,//WP_SABER, //(has its own handling)
	BOTH_ATTACK2,//WP_BRYAR_PISTOL,
	BOTH_ATTACK3,//WP_BLASTER,
	BOTH_ATTACK3,//BOTH_ATTACK4,//WP_DISRUPTOR,
	BOTH_ATTACK3,//BOTH_ATTACK5,//WP_BOWCASTER,
	BOTH_ATTACK3,//BOTH_ATTACK6,//WP_REPEATER,
	BOTH_ATTACK3,//BOTH_ATTACK7,//WP_DEMP2,
	BOTH_ATTACK3,//BOTH_ATTACK8,//WP_FLECHETTE,
	BOTH_ATTACK3,//BOTH_ATTACK9,//WP_ROCKET_LAUNCHER,
	BOTH_THERMAL_THROW,//WP_THERMAL,
	BOTH_ATTACK3,//BOTH_ATTACK11,//WP_TRIP_MINE,
	BOTH_ATTACK3,//BOTH_ATTACK12,//WP_DET_PACK,
	BOTH_ATTACK3,//WP_CONCUSSION, //Raz: Fixed bryar pistol animation
	BOTH_ATTACK2,//WP_BRYAR_OLD,

	//NOT VALID (e.g. should never really be used):
	BOTH_STAND1,//WP_EMPLACED_GUN,


	BOTH_ATTACK1//WP_TURRET,
};
This array is shared by the server and client, so depending on your use case scenario, you may want to override this "fix" to avoid prediction errors (i.e the server "correcting" your animation halfway through the sequence)

In my case, I am developing a cross-compatible server-side and client-side mod (separately) as an alternative to JA+
I check the serverinfo for "gamename" inside CG_ParseServerinfo, and work out which mod the server is running.

After that, modify PM_Weapon to look like this

Code:
		#ifndef QAGAME
			//Raz: Hacky fix here
			int weapon = pm->ps->weapon;
			if ( cg.mod != SMOD_JAPP && (pm->ps->weapon == WP_CONCUSSION || pm->ps->weapon == WP_BRYAR_OLD) )
				weapon++;
			PM_StartTorsoAnim( WeaponAttackAnim[weapon] );
		#else
			PM_StartTorsoAnim( WeaponAttackAnim[pm->ps->weapon] );
		#endif
I have not yet written the code to set the "correct" animation depending on the client's mod. Plugin sniffing is an ugly area.


Last edited by -=*Raz0r*=-; 09-12-2011 at 03:51 AM.
-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-11-2011, 12:38 AM   #217
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
There is a bug in jamp.exe where connecting to an invalid hostname or IP whilst ingame will shove you out to a black screen, unable to do anything (including open your console). A very ugly situation.

The function in question is CL_Connect_f (0x41D990)
The code in question is:
Code:
	//Taken from q3
	if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) {
		Com_Printf ("Bad server address\n");
		cls.state = CA_DISCONNECTED;
		return;
	}
At the very least, it should be setting your connection state to CA_CONNECTING

Ideally, you would rewrite this function to have a different code path if you attempt to connect to a bad hostname or IP whilst ingame.
Another solution, is to patch the opcode setting cls.state to CA_DISCONNECTED

You will have to unlock the code page with VirtualProtect or mprotect
I suggest writing a wrapper.

Code:
UnlockMemory( 0x41DACB, 1 );
*(unsigned char *)0x41DACB = (unsigned char)0x03;
LockMemory( 0x41DACB, 1 );
At the moment this fix is only for Windows, but it is possible to fix on Mac with the right addresses.

With this fix, you will be sent to the "Connecting to someinvalidhostname...1" screen and things will carry on as normal. Not ideal, but it works.

-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-11-2011, 12:47 AM   #218
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Another bug in jamp.exe where you can't use the Alt + Enter combination to toggle fullscreen. This is intended behaviour, but Raven(?) used the wrong connection state again.

The function in question is MainWndProc (0x454880)
The code in question is:
Code:
    if ( com_r_fullscreen
      && cl_allowAltEnter
      && (cls_state == CA_DISCONNECTED || cls_state == CA_CONNECTED)
      && cl_allowAltEnter.integer) )
    {
      Cvar_SetValue( "r_fullscreen", (com_r_fullscreen.integer == 0) );
      Cbuf_AddText("vid_restart\n");
    }
CA_CONNECTED should actually be CA_ACTIVE ("game views should be displayed")
My fix, however, is slightly hacky but will allow the Alt + Enter combination on all connection states.
It still relies on cl_allowAltEnter being 1

Code:
UnlockMemory( 0x454B5A, 2 );
*(unsigned char *)0x454B5A = (unsigned char)0x90; //NOP opcode, skip over the instruction
*(unsigned char *)0x454B5B = (unsigned char)0x90; //NOP opcode, skip over the instruction
LockMemory( 0x454B5A, 2 );
No Mac fix as of yet. I am not sure if this is even applicable for Mac. I don't own one.

-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-11-2011, 12:51 AM   #219
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
A well-known bug, where charged shots cause a dynamic light bug on players.
Easy fix, worth posting.

cg_ents.c, CG_EntityEffects

Adjust the if statement near the end to match the following:
Code:
	// constant light glow
	if ( cent->currentState.constantLight && cent->currentState.eType != ET_PLAYER && cent->currentState.eType != ET_BODY ) {

-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-11-2011, 12:59 AM   #220
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Credit goes to Didz for finding/fixing this.

In MP, misc_model_static entities' model bounds loading code is incorrect leading to disappearing cliffs and stuff on maps such as t1_surprise and hoth2.

In cg_main.c, search for void CG_CreateModelFromSpawnEnt(cgSpawnEnt_t *ent)

About 44 lines after that inside the function, find:
Code:
	VectorScaleVector(mins, ent->scale, mins);
	VectorScaleVector(maxs, ent->scale, maxs);
Replace these lines with:
Code:
	//[Invalid Model Bounds Fix]
	//VectorScaleVector(mins, ent->scale, mins);
	//VectorScaleVector(maxs, ent->scale, maxs);
	VectorScaleVector(mins, RefEnt->modelScale, mins);
	VectorScaleVector(maxs, RefEnt->modelScale, maxs);
	//[/Invalid Model Bounds Fix]

-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-11-2011, 01:01 AM   #221
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Credit goes to Xycaleth for finding/fixing this.


In JKA, players can jump-crouch through some patches, where it's an angle. This fixes that problem.

In bg_pmove.c, find:
Code:
static void PM_CheckDuck (void)
Above that, add:
Code:
static qboolean PM_CanStand ( void )
{
    qboolean canStand = qtrue;
    float x, y;
    trace_t trace;

    const vec3_t lineMins = { -5.0f, -5.0f, -2.5f };
    const vec3_t lineMaxs = { 5.0f, 5.0f, 0.0f };

    for ( x = pm->mins[0] + 5.0f; canStand && x <= (pm->maxs[0] - 5.0f); x += 10.0f )
    {
        for ( y = pm->mins[1] + 5.0f; y <= (pm->maxs[1] - 5.0f); y += 10.0f )
        {
            vec3_t start = { x, y, pm->maxs[2] };
            vec3_t end = { x, y, pm->ps->standheight };

            VectorAdd (start, pm->ps->origin, start);
            VectorAdd (end, pm->ps->origin, end);

            pm->trace (&trace, start, lineMins, lineMaxs, end, pm->ps->clientNum, pm->tracemask);
		    if ( trace.allsolid || trace.fraction < 1.0f )
		    {
			    canStand = qfalse;
			    break;
		    }
        }
    }
	
    return canStand;
}
In the PM_CheckDuck function, find:
Code:
		else if (pm->ps->pm_flags & PMF_ROLLING)
		{
			// try to stand up
			pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2;
			pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
			if (!trace.allsolid)
				pm->ps->pm_flags &= ~PMF_ROLLING;
		}
Replace with:
Code:
		else if (pm->ps->pm_flags & PMF_ROLLING)
		{
            if ( PM_CanStand() )
            {
                pm->maxs[2] = pm->ps->standheight;
                pm->ps->pm_flags &= ~PMF_ROLLING;
            }
		}
Find:
Code:
		else
		{	// stand up if possible 
			if (pm->ps->pm_flags & PMF_DUCKED)
			{
				// try to stand up
				pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2;
				pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
				if (!trace.allsolid)
					pm->ps->pm_flags &= ~PMF_DUCKED;
			}
		}
Replace with:
Code:
		else
		{	// stand up if possible 
			if (pm->ps->pm_flags & PMF_DUCKED)
			{
                if ( PM_CanStand() )
	            {
		            pm->maxs[2] = pm->ps->standheight;
		            pm->ps->pm_flags &= ~PMF_DUCKED;
	            }
			}
		}
And...that should be it.

-=*Raz0r*=- is offline   you may: quote & reply,
Old 09-12-2011, 11:00 AM   #222
Tinny
 
Tinny's Avatar
 
Join Date: Apr 2002
Posts: 1,188
oh wow, thanks so much!


Redeemed!

An old school mod for jedi academy.
Tinny is offline   you may: quote & reply,
Old 10-14-2011, 09:46 AM   #223
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
ITEM_TYPE_EDITFIELD elements will leave insert/overstrike mode on in various occasions.

ui_shared.c -> Item_TextField_HandleKey

Replace
Code:
		if ( key == A_ENTER || key == A_KP_ENTER || key == A_ESCAPE)  {
			return qfalse;
		}
With
Code:
		if ( key == A_ENTER || key == A_KP_ENTER || key == A_ESCAPE || (key == A_MOUSE1 && !Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) ))  {
			DC->setOverstrikeMode( qfalse );
			return qfalse;
		}

-=*Raz0r*=- is offline   you may: quote & reply,
Old 10-14-2011, 09:50 AM   #224
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
ITEM_TYPE_LISTBOX elements (Server browser, hilt selection, etc) can't be scrolled.
Whether you consider this a bug or not is totally your call. I like to scroll through lists

ui_shared.c -> Item_ListBox_HandleKey

After
Code:
			if ( key == A_CURSOR_DOWN || key == A_KP_2 ) 
			{
				if (!listPtr->notselectable) {
					listPtr->cursorPos++;
					if (listPtr->cursorPos < listPtr->startPos) {
						listPtr->startPos = listPtr->cursorPos;
//JLF
#ifndef _XBOX
						return qfalse;
#endif
					}
					if (listPtr->cursorPos >= count) {
						listPtr->cursorPos = count-1;
						return qfalse;
					}
					if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
						listPtr->startPos = listPtr->cursorPos - viewmax + 1;
					}
					item->cursorPos = listPtr->cursorPos;
					DC->feederSelection(item->special, item->cursorPos, NULL);
				}
				else {
					listPtr->startPos++;
					if (listPtr->startPos > max)
						listPtr->startPos = max;
				}
				return qtrue;
			}
Add
Code:
			if ( key == A_MWHEELUP ) 
			{
				listPtr->startPos -= ((int)item->special == FEEDER_Q3HEADS) ? viewmax : 1;
				if (listPtr->startPos < 0)
				{
					listPtr->startPos = 0;
					Display_MouseMove(NULL, DC->cursorx, DC->cursory);
					return qfalse;
				}
				Display_MouseMove(NULL, DC->cursorx, DC->cursory);
				return qtrue;
			}
			if ( key == A_MWHEELDOWN ) 
			{
				listPtr->startPos += ((int)item->special == FEEDER_Q3HEADS) ? viewmax : 1;
				if (listPtr->startPos > max)
				{
					listPtr->startPos = max;
					Display_MouseMove(NULL, DC->cursorx, DC->cursory);
					return qfalse;
				}
				Display_MouseMove(NULL, DC->cursorx, DC->cursory);
				return qtrue;
			}

Updated: Fixed for FEEDER_Q3HEADS to skip an entire row (16th October 2011)
Updated: return qfalse if there's no more to scroll, to prevent the sound from playing (10th November 2011)
Updated: Forcefully update the mouse position when scrolling so the proper listbox entry has focus (12th November 2011)


Last edited by -=*Raz0r*=-; 11-12-2011 at 06:07 AM.
-=*Raz0r*=- is offline   you may: quote & reply,
Old 10-14-2011, 10:17 AM   #225
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
The name field in the profile customisation screen will not allow more than 26 characters, despite the actual limit being 36 characters.
Furthermore, overflowing this then changing your name results in some...odd behaviour.

The first part of this fix is in the ui/jamp/ingame_player.menu
Adjust this part
Code:
		itemDef 
		{
			name 				namefield
			type 				ITEM_TYPE_EDITFIELD
			style 				0
			text 				@MENUS_NAME1
			cvar 				"ui_Name"
			maxchars 			26
To match
Code:
		itemDef 
		{
			name 				namefield
			type 				ITEM_TYPE_EDITFIELD
			style 				0
			text 				@MENUS_NAME1
			cvar 				"ui_Name"
			maxchars 			35
36-1 characters to account for the null-terminator, if I am correct.


ui_main.c -> UI_Update

Replace
Code:
 	if (Q_stricmp(name, "ui_SetName") == 0) {
		trap_Cvar_Set( "name", UI_Cvar_VariableString("ui_Name"));
 	} else if (Q_stricmp(name, "ui_setRate") == 0) {
With
Code:
 	if ( !Q_stricmp( name, "ui_SetName" ) )
	{
		char buf[36] = { 0 };
		Q_strncpyz( buf, UI_Cvar_VariableString( "ui_Name" ), sizeof( buf ) );
		trap_Cvar_Set( "name", buf );
 	}
	else if (Q_stricmp(name, "ui_setRate") == 0) {
Replace
Code:
	else if (Q_stricmp(name, "ui_GetName") == 0) 
	{
		trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name"));
	}
With
Code:
	else if ( !Q_stricmp( name, "ui_GetName" ) ) 
	{
		char buf[36] = { 0 };
		Q_strncpyz( buf, UI_Cvar_VariableString( "name" ), sizeof( buf ) );
		trap_Cvar_Set( "ui_Name", buf );
	}

ui_main.c -> _UI_Init

Replace
Code:
	trap_Cvar_Register(NULL, "ui_name", UI_Cvar_VariableString("name"), CVAR_INTERNAL );	//get this now, jic the menus change again trying to setName before getName
With
Code:
	{
		char buf[36] = { 0 };
		Q_strncpyz( buf, UI_Cvar_VariableString( "name" ), sizeof( buf ) );
		trap_Cvar_Register( NULL, "ui_Name", buf, CVAR_INTERNAL );
	}

-=*Raz0r*=- is offline   you may: quote & reply,
Old 11-12-2011, 06:16 AM   #226
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
There's an infinite loop in Cmd_FollowCycle_f (g_cmds.c) that can easily be used to attack an unpatched server.

To reproduce the infinite loop:
  • Join a server (Preferrably FFA?) where there are no in-game players (Spectators are fine)
  • /team follow1
  • Click to cycle through clients

Replace it with this version, also containing this fix

Code:
void Cmd_FollowCycle_f( gentity_t *ent, int dir ) {
	int		clientnum;
	int		original;
	qboolean	looped = qfalse;

	// if they are playing a tournement game, count as a loss
	if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
		&& ent->client->sess.sessionTeam == TEAM_FREE ) {\
		//WTF???
		ent->client->sess.losses++;
	}
	// first set them to spectator
	if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) {
		SetTeam( ent, "spectator" );
	}

	if ( dir != 1 && dir != -1 ) {
		G_Error( "Cmd_FollowCycle_f: bad dir %i", dir );
	}

	clientnum = ent->client->sess.spectatorClient;
	original = clientnum;

	do {
		clientnum += dir;
		if ( clientnum >= level.maxclients )
		{
			//Raz: Avoid /team follow1 crash
			if ( looped )
			{
				clientnum = original;
				break;
			}
			else
			{
				clientnum = 0;
				looped = qtrue;
			}
		}
		if ( clientnum < 0 ) {
			if ( looped )
			{
				clientnum = original;
				break;
			}
			else
			{
				clientnum = level.maxclients - 1;
				looped = qtrue;
			}
		}

		// can only follow connected clients
		if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) {
			continue;
		}

		// can't follow another spectator
		if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) {
			continue;
		}

		//ensiform's fix
		// can't follow another spectator
		if ( level.clients[ clientnum ].tempSpectate >= level.time ) {
			return;
		}

		// this is good, we can use it
		ent->client->sess.spectatorClient = clientnum;
		ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
		return;
	} while ( clientnum != original );

	// leave it where it was
}
I'm sure the loop could be written more elegantly, but this will suffice.

-=*Raz0r*=- is offline   you may: quote & reply,
Old 11-15-2011, 02:03 AM   #227
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Prediction error after spectating somebody who is on an ET_MOVER then switching to roaming mode.

Modify the start of CG_AdjustPositionForMover (cg_ents.c) to match:

Code:
void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) {
	centity_t	*cent;
	vec3_t	oldOrigin, origin, deltaOrigin;
	vec3_t	oldAngles, angles, deltaAngles;

	if ( cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR )
	{//Don't bother if we're a spectator
		VectorCopy( in, out );
		return;
	}

-=*Raz0r*=- is offline   you may: quote & reply,
Old 07-02-2012, 12:23 PM   #228
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
Weird behaviour in UI code causes onOpen {} events to be triggered on the parent item of the item you're closing (via out-of-bounds click)
Example: Ingame menu -> click "About" -> Click "Setup" -> the onOpen event of the "ingame" menu would be fired.
This is only an issue if you have your own menus that use these events. Personally I'm using transitions in all my menus, which were being triggered at the wrong time.

In ui_shared.c -> Menus_HandleOOBClick

Comment out "Menus_Activate(&Menus[i]);" in the for loop.

Code:
		for (i = 0; i < menuCount; i++) {
			if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) {
				Menu_RunCloseScript(menu);
				menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
			//	Menus_Activate(&Menus[i]);
				Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory);
				Menu_HandleKey(&Menus[i], key, down);
			}
		}

-=*Raz0r*=- is offline   you may: quote & reply,
Old 07-02-2012, 12:27 PM   #229
-=*Raz0r*=-
Rookie
 
-=*Raz0r*=-'s Avatar
 
Join Date: Oct 2006
Location: Australia
Posts: 243
JA's font width calculation is horrible broken, especially when using abnormal (anything but 1.0) scales.
The issue is in the engine, RE_Font_StrLenPixels uses integer precision for the return value, causing visible data loss.

The way I'm currently working around this is by replacing trap_R_Font_StrLenPixels with:
Code:
float trap_R_Font_StrLenPixels(const char *text, const int iFontIndex, const float scale)
{
	//Raz: HACK! RE_Font_StrLenPixels only works semi-correctly with 1.0f scale
	float width = (float)syscall( CG_R_FONT_STRLENPIXELS, text, iFontIndex, PASSFLOAT(1.0f));
	return width * scale;
}
Note I'm using a float return value - you'll have to adjust function prototypes and usage all around the code to use floats for best results.
I also use a large custom font to help with scaling.

This is not a fix, but it does help tremendously when trying to center-align text in both cgame and ui

Theoretically you could replace all text rendering with your own OpenGL code and use the bmfont library. If you wanted to D:


Last edited by -=*Raz0r*=-; 07-02-2012 at 03:16 PM.
-=*Raz0r*=- is offline   you may: quote & reply,
Old 10-31-2012, 11:30 PM   #230
ensiform
The Stig
 
ensiform's Avatar
 
Join Date: Nov 2004
Location: Sawtooth Cauldron
Posts: 1,241
Current Game: Borderlands 2
Quote:
Originally Posted by -=*Raz0r*=- View Post
There is a bug in jamp.exe where connecting to an invalid hostname or IP whilst ingame will shove you out to a black screen, unable to do anything (including open your console). A very ugly situation.

The function in question is CL_Connect_f (0x41D990)
The code in question is:
Code:
	//Taken from q3
	if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) {
		Com_Printf ("Bad server address\n");
		cls.state = CA_DISCONNECTED;
		return;
	}
At the very least, it should be setting your connection state to CA_CONNECTING

Ideally, you would rewrite this function to have a different code path if you attempt to connect to a bad hostname or IP whilst ingame.
Another solution, is to patch the opcode setting cls.state to CA_DISCONNECTED

You will have to unlock the code page with VirtualProtect or mprotect
I suggest writing a wrapper.

Code:
UnlockMemory( 0x41DACB, 1 );
*(unsigned char *)0x41DACB = (unsigned char)0x03;
LockMemory( 0x41DACB, 1 );
At the moment this fix is only for Windows, but it is possible to fix on Mac with the right addresses.

With this fix, you will be sent to the "Connecting to someinvalidhostname...1" screen and things will carry on as normal. Not ideal, but it works.
This fix is actually wrong.

The problem is raven fail coding in the UI:

(Main menu is never actually loaded because you were in the server last which will tell UI_Init to be (qtrue) on ingameload so it will only load ingame related menus. (This is fine however)

When it goes to try to revert back to main menu with setactivemenu UIMENU_MAIN:


Code:
			if (uiInfo.inGameLoad) 
			{
//				UI_LoadNonIngame();
			}
because of this, it fails to actually "activate" main.


iojamp project lead / coder
ensiform is offline   you may: quote & reply,
Post a new thread. Add a reply to this thread. Indicate all threads in this forum as read. Subscribe to this forum. RSS feed: this forum RSS feed: all forums
Go Back   LucasForums > Network > JediKnight Series > Editing Central > JA Coding > Q3 Engine Game Bugs / JA bugs

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 08:41 PM.

LFNetwork, LLC ©2002-2011 - All rights reserved.
Powered by vBulletin®
Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.