SDK Problem

The Great Apple

Big Cheese
Joined
Mar 24, 2002
Messages
3,361
Location
Oxford, England
Currently, when Civ 4 crashes I use the dump file to attempt to find the root of the crash (I can't seem to manage to get the debug config to compile). I'm assuming this is a valid method. It gives you a nice little stack trace which allows you to find where in the code

However, I've rather reached my wits end with this problem - I can't for the life of me work out what could be causing it, so I though I'd post here to see if anybody had any suggestions.

What I'm attempting to do is to have the game restart after one player has won, so many games can be played without user intervention. This is all good, and I can get the game to restart fine, however, whenever I try to reinitialize the player data it causes a strage crash to be caused the second time the player AI tries to find the civic yield modifier for civic 0 (GC.getCivicInfo(eCivic).getYieldModifier(iI)). It only happens the second time, not the first (the first call happens shortly before it).

I have no idea. I'm completely stumped. I haven't even slightly changed anything to do with civics or XML loading... or anything even remotely in that area. The data that it's failing to find has been successfully found very shortly beforehand, and well after my code has finished.

I can post the source & .dll if somebody wants to take a look at it. I would really appricaite some help, as this is one of the last few things stopping me from lauching into the AI head first. (The other is the two turn '0's thing).
 
The Great Apple said:
Currently, when Civ 4 crashes I use the dump file to attempt to find the root of the crash (I can't seem to manage to get the debug config to compile). I'm assuming this is a valid method. It gives you a nice little stack trace which allows you to find where in the code

However, I've rather reached my wits end with this problem - I can't for the life of me work out what could be causing it, so I though I'd post here to see if anybody had any suggestions.

What I'm attempting to do is to have the game restart after one player has won, so many games can be played without user intervention. This is all good, and I can get the game to restart fine, however, whenever I try to reinitialize the player data it causes a strage crash to be caused the second time the player AI tries to find the civic yield modifier for civic 0 (GC.getCivicInfo(eCivic).getYieldModifier(iI)). It only happens the second time, not the first (the first call happens shortly before it).

I have no idea. I'm completely stumped. I haven't even slightly changed anything to do with civics or XML loading... or anything even remotely in that area. The data that it's failing to find has been successfully found very shortly beforehand, and well after my code has finished.

I can post the source & .dll if somebody wants to take a look at it. I would really appricaite some help, as this is one of the last few things stopping me from lauching into the AI head first. (The other is the two turn '0's thing).

I think I'm most interested in seeing the actual code used to "restart" the game. If it's one clump you might be able to just put it in a reply, otherwise post up the code.
 
Gerikes said:
I think I'm most interested in seeing the actual code used to "restart" the game. If it's one clump you might be able to just put it in a reply, otherwise post up the code.
I've a feeling it quite be naive, but ok! ;)

Code:
void CvGame::newGame()
{
	HandicapTypes eHandicap;

	eHandicap = GC.getInitCore().getHandicap( ( PlayerTypes ) 0 );

	for (int iI = 0; iI < MAX_TEAMS; iI++)
	{
		GET_TEAM((TeamTypes)iI).init((TeamTypes)iI);
	}
	
	for (int iI = 0; iI < MAX_PLAYERS; iI++)
	{
		GET_PLAYER((PlayerTypes)iI).setAlive(0);
		GET_PLAYER((PlayerTypes)iI).reinit((PlayerTypes)iI, GET_PLAYER((PlayerTypes)iI).getLeaderType(), true);
	}


	setGameTurn(0);

	regenerateMap();
	logMsg("Game init");

    init(eHandicap);
    logMsg("Map init");
	
	for (int iI = 0; iI < MAX_PLAYERS; iI++)
	{
		GET_PLAYER((PlayerTypes)iI).setAlive(1);
	}
	
	logMsg("newGame() completed");
The logMsg function just prints out the message to a file, CvPlayer::reinit is very similar to CvPlayer::init, (it is a function taken from jdog500), however it removes the previous leaderhead's traits before applying the new ones... which init doens't.

It's really not that complicated... and I fear it may have to be a bit more so - I just can't think of anything else to stick in.
 
however, whenever I try to reinitialize the player data it causes a strage crash to be caused the second time the player AI tries to find the civic yield modifier for civic 0 (GC.getCivicInfo(eCivic).getYieldModifier(iI)). It only happens the second time, not the first (the first call happens shortly before it).

I don't know if I understand what you say by this. Does this crash happen during the newGame function, or during the game, after the newGame function completes? And if after, what function does it show?
 
Gerikes said:
I don't know if I understand what you say by this. Does this crash happen during the newGame function, or during the game, after the newGame function completes? And if after, what function does it show?
Afterward. The trace given by the dubugger is as follows:

Code:
CvGameCoreDLL.dll!CvCivicInfo::getYieldModifier()  + 0x21 bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_civicValue()  + 0x7e4 bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_bestTech()  + 0x174e bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_chooseResearch()  + 0x19a bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_doResearch()  + 0x1f bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_doTurnPre()  + 0x28 bytes
CvGameCoreDLL.dll!CvPlayer::doTurn()  + 0x6d bytes
CvGameCoreDLL.dll!CvPlayer::setTurnActive()  + 0x854 bytes
CvGameCoreDLL.dll!CvPlayer::setAutoMoves()  + 0x66 bytes
CvGameCoreDLL.dll!CvGame::updateMoves()  + 0x166 bytes
CvGameCoreDLL.dll!CvGame::update()  + 0x10b bytes

With the disassembly showing this for getYieldModifier():

Code:
CvCivicInfo::getYieldModifier:
01FBB8F0  push        ebp  
01FBB8F1  mov         ebp,esp 
01FBB8F3  sub         esp,8 
01FBB8F6  mov         dword ptr [ebp-4],ecx 
01FBB8F9  mov         eax,dword ptr [ebp-4] 
01FBB8FC  cmp         dword ptr [eax+0E4h],0 
01FBB903  je          CvCivicInfo::getYieldModifier+29h (1FBB919h) 
01FBB905  mov         ecx,dword ptr [ebp-4] 
01FBB908  mov         edx,dword ptr [ecx+0E4h] 
01FBB90E  mov         eax,dword ptr [ebp+8] 
01FBB911  mov         ecx,dword ptr [edx+eax*4]
(Last line is the one that causes the crash, if I read this correctly)

By printing debug messages I have found that it runs though CvPlayerAI::AI_civicValue twice in quick succession, and the second time is the time that causes it to fail. The first time runs fine. Here is my debug file.

Here is the sectionthat is calling the one that causes the error, as well as my debug log file:

Code:
	for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
	{
		iTempValue = 0;

		logMsg("iI Civic stuff, %d. Player, %d",iI, getID());

		iTempValue += ((GC.getCivicInfo(eCivic).getYieldModifier(iI) * getNumCities()) / 2);
Code:
[291248.125] iI Civic stuff, 0. Player, 1
[291248.125] iI Civic stuff, 1. Player, 1
[291248.125] iI Civic stuff, 2. Player, 1
[291248.125] iI Civic stuff, 0. Player, 1
[291248.125] iI Civic stuff, 1. Player, 1
[291248.125] iI Civic stuff, 2. Player, 1
[291248.219] iI Civic stuff, 0. Player, 18
[291248.219] iI Civic stuff, 1. Player, 18
[291248.219] iI Civic stuff, 2. Player, 18
[291248.219] iI Civic stuff, 0. Player, 18
[291248.219] iI Civic stuff, 1. Player, 18
[291248.219] iI Civic stuff, 2. Player, 18
[291285.906] Game init
[291285.906] Map init
[291285.938] newGame() completed
[291286.000] iI Civic stuff, 0. Player, 1
[291286.000] iI Civic stuff, 1. Player, 1
[291286.000] iI Civic stuff, 2. Player, 1
[291286.000] iI Civic stuff, 0. Player, 1
 
The Great Apple said:
Afterward. The trace given by the dubugger is as follows:

Code:
CvGameCoreDLL.dll!CvCivicInfo::getYieldModifier()  + 0x21 bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_civicValue()  + 0x7e4 bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_bestTech()  + 0x174e bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_chooseResearch()  + 0x19a bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_doResearch()  + 0x1f bytes
CvGameCoreDLL.dll!CvPlayerAI::AI_doTurnPre()  + 0x28 bytes
CvGameCoreDLL.dll!CvPlayer::doTurn()  + 0x6d bytes
CvGameCoreDLL.dll!CvPlayer::setTurnActive()  + 0x854 bytes
CvGameCoreDLL.dll!CvPlayer::setAutoMoves()  + 0x66 bytes
CvGameCoreDLL.dll!CvGame::updateMoves()  + 0x166 bytes
CvGameCoreDLL.dll!CvGame::update()  + 0x10b bytes

With the disassembly showing this for getYieldModifier():

Code:
CvCivicInfo::getYieldModifier:
01FBB8F0  push        ebp  
01FBB8F1  mov         ebp,esp 
01FBB8F3  sub         esp,8 
01FBB8F6  mov         dword ptr [ebp-4],ecx 
01FBB8F9  mov         eax,dword ptr [ebp-4] 
01FBB8FC  cmp         dword ptr [eax+0E4h],0 
01FBB903  je          CvCivicInfo::getYieldModifier+29h (1FBB919h) 
01FBB905  mov         ecx,dword ptr [ebp-4] 
01FBB908  mov         edx,dword ptr [ecx+0E4h] 
01FBB90E  mov         eax,dword ptr [ebp+8] 
01FBB911  mov         ecx,dword ptr [edx+eax*4]
(Last line is the one that causes the crash, if I read this correctly)

That's very weird. If that is where it crashes, I would think that it means that it's going out of bounds retrieving the value of the m_piYieldModifier. However, in order to do that, either the address for m_piYieldModifier must have somehow been modified, or the value i is greater than the number of entries in m_piYieldModifier. The latter would make more sense, but AI_civicValue puts it into a for loop where i cannot become bigger than NUM_YIELD_TYPES, so I wouldn't think it's that either.

If you want, you can post the entire source, and I'll try making a debug build of it to step through (debug builds are a lifesaver).
 
Gerikes said:
That's very weird. If that is where it crashes, I would think that it means that it's going out of bounds retrieving the value of the m_piYieldModifier. However, in order to do that, either the address for m_piYieldModifier must have somehow been modified, or the value i is greater than the number of entries in m_piYieldModifier. The latter would make more sense, but AI_civicValue puts it into a for loop where i cannot become bigger than NUM_YIELD_TYPES, so I wouldn't think it's that either.
. And what's even weirder is that it's just been called successfully...

Attached are the modified sources. There are one or two functions I don't use (related to the AutoPlay mod), but I tried using to see if they fixed the problem. As they aren't called, they shouldn't be a problem.

Thanks!
 
The Great Apple said:
Currently, when Civ 4 crashes I use the dump file to attempt to find the root of the crash (I can't seem to manage to get the debug config to compile). I'm assuming this is a valid method. It gives you a nice little stack trace which allows you to find where in the code

Are you using using VS2003 to compile the code? If you are you might want to try this:
1) Start the game using your compiled DLL in "Final Release" mod
2) Switch to your VS2003 instance and press CTRL-ALT-P
3) Select the Civilization4.exe from the available processes list and press the "Attach" button.
4) Select "Common Runtime Library" and "Native" and press the "OK" button
5) Set break points in the suspected code.
6) Start playing

I have tried this method and I am able to step through my code without having to building using the debug build.
 
Couple of things I'm running into:

1.) You're probably going to run into some start location problems, as in CvGame.cpp, line 953-967:

Code:
iHumanSlot = range(((([b]countCivPlayersAlive()[/b] - 1) * GC.getHandicapInfo(getHandicapType()).getStartingLocationPercent()) / 100), 0, ([b]countCivPlayersAlive()[/b]- 1));

for (iI = 0; iI < iHumanSlot; iI++)
{
	if (GET_PLAYER((PlayerTypes)iI).isAlive())
	{
		if (!(GET_PLAYER((PlayerTypes)iI).isHuman()))
		{
			if (GET_PLAYER((PlayerTypes)iI).getStartingPlot() == NULL)
			{
				GET_PLAYER((PlayerTypes)iI).setStartingPlot(GET_PLAYER((PlayerTypes)iI).findStartingPlot(), true);
			}
		}
	}
}

In newGame() you've set all the players setAlive to 0, then countCivPlayersAlive() will return zero, resulting in the range arguments being (0, 0, -1), which will return -1. Thus, no players will have their starting plots mapped out if it's predefined on a scenario (which I'm sure you're probably going to use at some point).

Also, another thing I noticed, is that when you run newGame(), if setting the players to not alive is important (I'm not sure why it needs to be done, but then again I haven't really tried doing any of this), you should probably keep track of them, since you are setting them all to dead (say, players 0, 1, and 18), but then you set them afterward all to alive (players 0, 1, 2, 3, 4, 5.... 18).

Aside from that, when I run newGame() I keep on being defeated. I'm assuming because once again I'm running from the console, not from the scripts. Because I'm deafeated the AI code won't run to get to that crash you're getting to.
 
I get a crash earlier if I don't kill off the Civs first (although I haven't a clue why that one is cause either).

I'll try keeping track of the players, and only setting the dead ones back alive. I've no idea why that would cause this error, but it's worth a try. (Maybe it causes the error because it's checking for a dead player, despite the logs... however I don't see that the player matters)

I get no problems with being defeated.

Thanks for the tips TL - will check it out.

EDIT: No joy. Still crashes in the same place with player tracking.

New code:
Code:
void CvGame::newGame()
{
	HandicapTypes eHandicap;
    bool alivePlayers[MAX_PLAYERS];
    int iI;

    for (iI = 0; iI < MAX_PLAYERS; iI++)
    {
        alivePlayers[iI] = false;
    }

	eHandicap = GC.getInitCore().getHandicap( ( PlayerTypes ) 0 );

	for (iI = 0; iI < MAX_PLAYERS; iI++)
	{
        if (GET_PLAYER((PlayerTypes)iI).isAlive())
        {
            GET_PLAYER((PlayerTypes)iI).setAlive(0);
            alivePlayers[iI] = true;
            GET_PLAYER((PlayerTypes)iI).reinit((PlayerTypes)iI, GET_PLAYER((PlayerTypes)iI).getLeaderType(), true);
        }
	}

	for (iI = 0; iI < MAX_TEAMS; iI++)
	{
		GET_TEAM((TeamTypes)iI).init((TeamTypes)iI);
	}

	for (iI = 0; iI < MAX_PLAYERS; iI++)
	{
        if (alivePlayers[iI])
        {
            GET_PLAYER((PlayerTypes)iI).setAlive(1);
        }
	}

	logMsg("Players init");

	setGameTurn(0);

	regenerateMap();
	logMsg("Map init");

    init(eHandicap);
    logMsg("Game init");

	logMsg("newGame() completed");

	// gDLL->getEventReporterIFace()->GameStart();
}


Maybe I should try reloading the XML every time ;)
 
The Great Apple said:
I get a crash earlier if I don't kill off the Civs first (although I haven't a clue why that one is cause either).

I'll try keeping track of the players, and only setting the dead ones back alive. I've no idea why that would cause this error, but it's worth a try. (Maybe it causes the error because it's checking for a dead player, despite the logs... however I don't see that the player matters)

I get no problems with being defeated.

Thanks for the tips TL - will check it out.

EDIT: No joy. Still crashes in the same place with player tracking.

New code:
Code:
void CvGame::newGame()
{
	HandicapTypes eHandicap;
    bool alivePlayers[MAX_PLAYERS];
    int iI;

    for (iI = 0; iI < MAX_PLAYERS; iI++)
    {
        alivePlayers[iI] = false;
    }

	eHandicap = GC.getInitCore().getHandicap( ( PlayerTypes ) 0 );

	for (iI = 0; iI < MAX_PLAYERS; iI++)
	{
        if (GET_PLAYER((PlayerTypes)iI).isAlive())
        {
            GET_PLAYER((PlayerTypes)iI).setAlive(0);
            alivePlayers[iI] = true;
            GET_PLAYER((PlayerTypes)iI).reinit((PlayerTypes)iI, GET_PLAYER((PlayerTypes)iI).getLeaderType(), true);
        }
	}

	for (iI = 0; iI < MAX_TEAMS; iI++)
	{
		GET_TEAM((TeamTypes)iI).init((TeamTypes)iI);
	}

	for (iI = 0; iI < MAX_PLAYERS; iI++)
	{
        if (alivePlayers[iI])
        {
            GET_PLAYER((PlayerTypes)iI).setAlive(1);
        }
	}

	logMsg("Players init");

	setGameTurn(0);

	regenerateMap();
	logMsg("Map init");

    init(eHandicap);
    logMsg("Game init");

	logMsg("newGame() completed");

	// gDLL->getEventReporterIFace()->GameStart();
}


Maybe I should try reloading the XML every time ;)

I didn't anticipate that being the problem, just something that you might want to know about. I wasn't able to recreate the crash you were getting, because whenever I used the newGame() function, my game would just end, probably because there are other things in python that you do besides call newGame() that I don't have.
 
Odd.. not much I'm doing in python:
Code:
	def onBeginPlayerTurn(self, argsList):
		iGameTurn, iPlayer = argsList
		
		if iGameTurn == 5:
			game.newGame()
Where game = CyGame()
 
Ok, I think I figured it out.

The AI player for some reason hasn't been reiniting. The reason is because of a safegaurd put into the reinit function...

Code:
//--------------------------------
// Init other game data
//FAssert(getTeam() != NO_TEAM);
//GET_TEAM(getTeam()).changeNumMembers(1);

[b]if ((GC.getInitCore().getSlotStatus(getID()) == SS_TAKEN) || (GC.getInitCore().getSlotStatus(getID()) == SS_COMPUTER))[/b]
{
	[b] ------- Snip basically all the player initting, including... -------[/b]

	// sets up civics to what's in XML
	for (iI = 0; iI < GC.getNumCivicOptionInfos(); iI++)
	{
		setCivics(((CivicOptionTypes)iI), ((CivicTypes)(GC.getCivilizationInfo(getCivilizationType()).getCivilizationInitialCivics(iI))));
	}
}

The emboldened code is the way that jdog5000 uses to make sure that the function doesn't init players that weren't init'd before the game started. In other words, only slots that were taken (human and I believe barbarian) or computer slots were reinit'ed.

(I didn't notice this when I was looking at the newGame code. However, even with the knowledge that this safeguard was put in by jdog, I get a hell of a lot less assert throws when the new newGame function code is used)

In theory, the slot check should pass for players 0 (the human), 1 (the ai), and 18 (the barbarians). However, because just before calling reinit you make player 1 not alive (by using the setAlive(false) in newGame), the slot for player 1 closes, and thus the guard used by jdog5000 doesn't allow player 1 to reinitialize.

The barbarians don't suffer the same fate, because apparently calling to close the barbarian slot will not actually close the slot.
 
I got it working! :woohoo:

The suggestion you made was correct. Following that there were several other things I had to go through and fix. Next job is allowing the changing of settings.

Working code:
Code:
void CvGame::newGame()
{
	HandicapTypes eHandicap;
	int iI;
	bool bHumanPlayer = false;

	setGameTurn(0);

	eHandicap = GC.getInitCore().getHandicap( ( PlayerTypes ) 0 );

	for (iI = 0; iI < MAX_TEAMS; iI++)
	{
		if (GET_TEAM((TeamTypes)iI).isAlive())
		{
			GET_TEAM((TeamTypes)iI).init((TeamTypes)iI);
		}
	}

	for (iI = 0; iI < MAX_PLAYERS; iI++)
	{
		if (GET_PLAYER((PlayerTypes)iI).isAlive())
		{
			if (GET_PLAYER((PlayerTypes)iI).isHuman())
			{
				bHumanPlayer = true;
			}
			else
			{
				bHumanPlayer = false;
			}

			GET_PLAYER((PlayerTypes)iI).clearResearchQueue();
			GET_PLAYER((PlayerTypes)iI).killUnits();
			GET_PLAYER((PlayerTypes)iI).killCities();
			GET_PLAYER((PlayerTypes)iI).killAllDeals();

			GET_PLAYER((PlayerTypes)iI).init((PlayerTypes)iI);

			if (bHumanPlayer)
			{
				GC.getInitCore().setSlotStatus((PlayerTypes)iI, SS_TAKEN);
			}
			else
			{
				GC.getInitCore().setSlotStatus((PlayerTypes)iI, SS_COMPUTER);
			}

			GET_PLAYER((PlayerTypes)iI).setTurnActive(false);
		}
	}

	regenerateMap();
	init(eHandicap);
	GET_PLAYER((PlayerTypes)0).setTurnActive(true);
}

Thanks for all the help!
 
Good job Gerikes I am eager to see what TGA produces, AI autoplaying sounds like a realy slick means of testing not only the AI but virtualy every other major aspect of the game.
 
In the .ini
Code:
; Create a dump file if the application crashes
GenerateCrashDumps = 1
This will cause a promt when you crash which allows you to save a dump file. Load up that dump file, and it'll give you some quite useful infromation about the crash (a stack trace, most usefully).
 
Impaler[WrG] said:
Good job Gerikes I am eager to see what TGA produces, AI autoplaying sounds like a realy slick means of testing not only the AI but virtualy every other major aspect of the game.

Yup. I'm definetly interested because I may want to see what AI-testing tools the community can come up with before I have to rewrite the AI code for Civcraft :P
 
Right. Next job was to allow the function to change the game variables, so that the game could be tested under many conditions. In doing this I've run into another problem - I can't seem to get the game to succesfully initialize a map of a different size to the original. The method I'm using will work fine for maps the same size, but crashes for smaller or larger maps. Quite frustrating. I can't seem to figure out why - I think I'm crashing a function in the .exe, so it's not giving me alot of info! It seems to be in the CvPlot::getX() function...

I just realised that I don't have the code on this computer, but I'll post it up tomorrow when I get it in the hopes that somebody can see what I'm doing wrong.
 
Back
Top Bottom