1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

An Idiots Guide to Editing the DLL

Discussion in 'Civ4 - Modding Tutorials & Reference' started by xienwolf, Mar 16, 2009.

  1. xienwolf

    xienwolf Deity

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
  2. phungus420

    phungus420 Deity

    Joined:
    Mar 1, 2003
    Messages:
    6,296
    Repeating the same request for a tutorial on how to use the save game flags so that if we add a new XML tag, we can keep things save game compatible. I've added 4 new tags to the RevDCM core, and want to incorporate these in LoR without breaking save game compatibility. Interestingly enough 3 of the tags apparently don't break save game (only the array does), but davidlallen stated that the data is probably screwed up because of the new tags even if the save will load; and this seems like a good simple and useful tutorial to add.
     
  3. xienwolf

    xienwolf Deity

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    Yeah, sorry. Meant to write it up a couple times over the last few days, but I am stuck in a fairly endless loop of looking up citations of citations trying to track down some specifics on a fabrication technique for my lab. After banging my head against the wall for a few hours on that my ability to type coherent guidelines for simple processes is a bit limited (and my sense of humor is pretty harsh), so I wind up preventing myself from attempting to write anything out.

    The big tricky bit of trying to maintain your savegame compatibility is to have a backup method of acquiring the information which wasn't available previously. This means picking it up out of the Cv___Infos functions (unfortunately you cannot be PERFECTLY savegame compatible if you added things to the XML and they had Lock Modified Assets enabled. But it is their fault for using that strange gameoption and wanting to update midgame)

    Basically how it works is like this:
    You added a new tag to the XML, so in CvInfos you would look at your ::read(stream) function if you have one and you would find this line early on:
    Code:
    	uint uiFlag=0;
    	stream->Read(&uiFlag);	// flags for expansion
    
    And of course in ::write(Stream) you'll find the matching element:
    Code:
    	uint uiFlag=0;
    	stream->Write(uiFlag);		// flag for expansion
    
    When the DLL was first fashioned, every ::read and ::write in the entire DLL contained these lines at their opening. Wherever the value is no longer 0, that is where Firaxis has manipulated it to maintain some savegame compatibility. The only ones I see right now is in CvGame they have uiFlag=1 and in CvUnit they have uiFlag=2. Though honestly the CvUnit one could be


    In CvGame, they used it in ::read as follows:
    Code:
    	if (uiFlag < 1)
    	{
    		int iEndTurnMessagesSent;
    		pStream->Read(&iEndTurnMessagesSent);
    	}
    .
    .
    .
    .
    	if (uiFlag < 1)
    	{
    		std::vector<int> aiEndTurnMessagesReceived(MAX_PLAYERS);
    		pStream->Read(MAX_PLAYERS, &aiEndTurnMessagesReceived[0]);
    	}
    
    This tells us that before the flag was set to 1 (I am pretty sure that this is the one they did in 3.19 which let me catch on to how you do this) there was an m_iEndTurnMessagesSent variable in the code. After 3.19, this variable didn't exist, so it wasn't required to be written/read. But if your savegame was from before that patch, your stream has this spare INT. Thus if the flag was a 0 (meaning a pre-3.19 save) it would create a dummy variable to read the information into. As soon as the IF statement finishes, the variable is discarded since it was a local. Thus we read that spare INT and did nothing with it. The second chunk is the same thing, but with a vector instead of a single INT.

    This is the EASY problem to fix, they removed something. So you read it in a dummy variable just to "jump ahead" in the stream and get back to what really exists.

    In CvUnit, it is the same deal
    Code:
    	if (uiFlag < 2)
    	{
    		int iCombatDamage;
    		pStream->Read(&iCombatDamage);
    	}
    
    So apparently we no longer track the combat damage specifically. Instead we just track their health as I recall it.

    Further down, they have a sample where something was added though.
    Code:
    	if (uiFlag > 0)
    	{
    		pStream->Read(&m_bAirCombat);
    	}
    
    Far as I can tell, this flag is just used for inteception of air units, so apparently that wasn't a possibility in the initial launch of Civ 4, or it just wasn't handled as efficiently. Not entirely sure. In this case, if you come before this patch (probably this one had moved them to uiFlag 1, while the removal of CombatDamage moved them to 2) then you LACK a boolean variable, so you need to skip reading this.

    Now, this time we ADDED something, so we have to ensure that the default values are acceptable, or that proper values are loaded. This particular flag is only ever set DURING combat, and since you cannot save mid-combat, it seems that it would be false for all units at the time of a save anyway. But even lacking that, it flags an ability which hadn't existed prior, so a false on all units is a safe bet.


    I would post where/how I used this, but looking at it now, I am pretty sure that I got things backwards and wrote the ::read in the manner I needed for the ::write and vice-versa.


    Anyway, where things get tricky are where you are adding information to the game for which a default is NOT going to work. Like if you added a new array to PromotionInfos, and used that array for some information on a pre-existing promotion, like Combat I. If you had only used it on a completely NEW promotion, then no unit would have that promotion yet, so the default of NO INFORMATION LOADED in CvUnit, would be perfect. But if it is on a promotion that some unit may already have, then you have to load the new data on to the unit as well (this would be true of course for any change to a pre-existing promotion, sure you might SEEM savegame compat, but when you play for a while you'll notice problems where unit stats don't match up with what they should be based on promotions held, because they lack the additional stats of the promotion they gained pre-patch, or even have those stats REMOVED from themselves if they have gained the promotion pre-patch, and lost it post-patch).


    In cases like that, within your IF check to verify that you are loading a pre-change savegame, you have to set things up to find the proper data and modify your gamestate to match. In this example case of modification to Combat I, you would check if the unit had Combat I (well, any promotion which utilizes the new fields honestly) and if they do have such a promotion, load up the new information just like as if you were running ::setHasPromotion for just the new tags. Obviously this has to be done AFTER loading promotions onto the unit, as well as setting any other state information for the unit which you may be changing (otherwise you just write over your careful update).



    Yeah.... not so easy to read/understand possibly. But it might help to get you a bit further along for some more pointed questions, or till I have the time to figure out a nice way to write it up as part of the main tutorials. Though my current example code is all fairly simple to make savegame compat, so wouldn't show off the more difficult possibilities too well.


    EDIT: Also, nobody has noticed the change to first post? Awww :(
     
  4. phungus420

    phungus420 Deity

    Joined:
    Mar 1, 2003
    Messages:
    6,296
    Could you show an example? Ie, could you take the tutorial you did for adding an array, then expand it showing how to make that new Array XML tag be save game compatible? This is definitely one of those things I need to see done, trying to follow the above post leaves me still lost as to where I am supposed to start.
     
  5. xienwolf

    xienwolf Deity

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    For the parts in CvInfos: There is nothing needed/possible. These read/writes are only utilized for Lock Modified Assets, which any patch WILL break savegames for. So that portion of the tutorial remains unchanged. And since it seems I only half finished the array tutorial, that means I have nothing to modify and show with that particular example.



    Maybe a non-code illustration could help?

    Let's pretend that on initial release, you had a count of even numbers.

    Code:
    ::write(stream)
    {
        int uiFlag=0;
        ->write(uiFlag);
    
        ->write(m_eDataType);
        ->write(2);
        ->write(4);
        ->write(6);
        ->write(8);
        ->write(10);
        ->write(12);
        ->write(14);
    }
    
    Code:
    ::read(stream)
    {
        int uiFlag=0;
        ->read(uiFlag);
    
        ->read(m_eDataType);
        ->read(2);
        ->read(4);
        ->read(6);
        ->read(8);
        ->read(10);
        ->read(12);
        ->read(14);
    }
    
    Now, if you wanted to add odd numbers in a future patch, you would change it as follows:


    Code:
    ::write(stream)
    {
        int uiFlag=[COLOR="Lime"]1[/COLOR];
        ->write(uiFlag);
    
        ->write(m_eDataType);
    [COLOR="Lime"]    ->write(1);[/COLOR]
        ->write(2);
    [COLOR="Lime"]    ->write(3);[/COLOR]
        ->write(4);
    [COLOR="Lime"]    ->write(5);[/COLOR]
        ->write(6);
    [COLOR="Lime"]    ->write(7);[/COLOR]
        ->write(8);
    [COLOR="Lime"]    ->write(9);[/COLOR]
        ->write(10);
    [COLOR="Lime"]    ->write(11);[/COLOR]
        ->write(12);
    [COLOR="Lime"]    ->write(13);[/COLOR]
        ->write(14);
    }
    
    Code:
    ::read(stream)
    {
        int uiFlag=0;
        ->read(uiFlag);
    
    
        ->read(m_eDataType);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(1);
        }[/COLOR]
        ->read(2);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(3);
        }[/COLOR]
        ->read(4);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(5);
        }[/COLOR]
        ->read(6);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(7);
        }[/COLOR]
        ->read(8);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(9);
        }[/COLOR]
        ->read(10);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(11);
        }[/COLOR]
        ->read(12);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(13);
        }[/COLOR]
        ->read(14);
    }
    
    This is assuming that I have default values for the odd numbers in my code during initialization. If I did not have such, or those were insufficient, I would have to set them up here.

    Code:
    ::read(stream)
    {
        int uiFlag=0;
        ->read(uiFlag);
    
    
        ->read(m_eDataType);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(1);
        }
        else
        {
            1 = m_eDataType.get1();
        }[/COLOR]
        ->read(2);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(3);
        else
        {
            3 = m_eDataType.get3();
        }[/COLOR]
        ->read(4);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(5);
        else
        {
            5 = m_eDataType.get5();
        }[/COLOR]
        ->read(6);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(7);
        else
        {
            7 = m_eDataType.get7();
        }[/COLOR]
        ->read(8);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(9);
        else
        {
            9 = m_eDataType.get9();
        }[/COLOR]
        ->read(10);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(11);
        else
        {
            11 = m_eDataType.get11();
        }[/COLOR]
        ->read(12);
    [COLOR="Lime"]    if (uiFlag>0)
        {
            ->read(13);
        else
        {
            13 = m_eDataType.get13();
        }[/COLOR]
        ->read(14);
    }
    
    If I were able to just say 1=1; 3=3... for the values, that would have easily been set up through my defaults in initialization, which would have allowed me to use the first version. This one instead accesses the CvDataInfos:: functions over in CvInfo to pick up what is required for this particular object to be accurate (maybe in one of my CvData types stores raw numbers as numbers, but another stores letters as numbers, so 1=a; 3=c... If that were the case then strict defaults wouldn't work as it isn't always the same. But accessing the CvDataInfo to pick up what it SHOULD have been does work)

    And then in a patch after that, you decide to remove multiples of 3:

    Code:
    ::write(stream)
    {
        int uiFlag=[COLOR="Lime"]2[/COLOR];
        ->write(uiFlag);
    
        ->write(m_eDataType);
        ->write(1);
        ->write(2);
        ->write(4);
        ->write(5);
        ->write(7);
        ->write(8);
        ->write(10);
        ->write(11);
        ->write(13);
        ->write(14);
    }
    
    Code:
    ::read(stream)
    {
        int uiFlag=0;
        ->read(uiFlag);
    
    
        ->read(m_eDataType);
        if (uiFlag>0)
        {
            ->read(1);
        }
        ->read(2);
        if (uiFlag>0)
        {
    [COLOR="Lime"]        if (uiFlag<2)
            {
                int trash=0;
                ->read(trash);
            }[/COLOR]
        }
        ->read(4);
        if (uiFlag>0)
        {
            ->read(5);
        }
    [COLOR="Lime"]    if (uiFlag<2)
        {
            int trash=0;
            ->read(trash);
        }[/COLOR]
        if (uiFlag>0)
        {
            ->read(7);
        }
        ->read(8);
        if (uiFlag>0)
        {
    [COLOR="Lime"]        if (uiFlag<2)
            {
                int trash=0;
                ->read(trash);
            }[/COLOR]
        }
        ->read(10);
        if (uiFlag>0)
        {
            ->read(11);
        }
    [COLOR="Lime"]    if (uiFlag<2)
        {
            int trash=0;
            ->read(trash);
        }[/COLOR]
        if (uiFlag>0)
        {
            ->read(13);
        }
        ->read(14);
    }
    
    Here we loaded the extra information in older save versions into a trash dummy variable just to get it out of our way since the data isn't required any longer.
     
  6. phungus420

    phungus420 Deity

    Joined:
    Mar 1, 2003
    Messages:
    6,296
    So for added tags that only have a read/write stream in CvInfos I don't need to do this? I only need to take into account tags that have a read/write stream portions in other files? This seems odd, because most tags that are added only have a read/write stream in CvInfos.
     
  7. Shiggs713

    Shiggs713 Immortal

    Joined:
    Mar 11, 2007
    Messages:
    2,361
    Location:
    Indianapolis
    well this is quite a gem! thanks for posting xienwolf
     
  8. xienwolf

    xienwolf Deity

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    Mostly, it is just that for tags which read/write in CvInfos you CANNOT do this. There is no other way to get the information which is needed.

    You can do the modification for when you REMOVE a tag, that is easy enough. But if you ADD a tag, then doing this means that for a pre-patch save you simply don't wind up having any data at all for those variables. You COULD set it up this way still, but you would have default values for all of those variables until you save under the new patch and then load the game once again.

    But it shouldn't matter in CvInfos. I am 90% certain that those ::read and ::write functions are ONLY used when you have Lock Modified Assets enabled so that it can test that your game data hasn't been altered.
     
  9. phungus420

    phungus420 Deity

    Joined:
    Mar 1, 2003
    Messages:
    6,296
    I'm a bit stumped by a recent code change I would like to implement. I have a boolean array created by afforess in BuildingInfos: PrereqBuildingClasses. I pass in a value (corresponding to a buildingclass), it tells me true or false. Works great. I'd like to add in a second paramater: TechOverride, where if the player has researched this tech it overrides the requirement. How is the best way to do this? I've added it into the schema, but am stuck on how to load it in CvInfos.

    This is the function currently in UnitInfos:

    bool getPrereqBuildingClass(int i)

    I suppose I could add in a second argument, the tech, but then I run into two problems. First, I want it to override the "true" value, if it's there, but not if nothing is defined (or if the wrong tech is passed in), but I also wouldn't want it to override a false. I'm not exactly sure how to set this up. The second problem is a bigger concern, and that's that this is for RevDCM, which is used as a modding base for multiple mods. If I add in a second argument other mods are going to suddenly have their code break because the'll get a "function takes two arguments, one given" error.

    My other issue I'm not really seeing is how I can get the tech that overrides, I'm not sure how to create a function to ask for an int value inside a boolean array.

    I've been staring at the screen for a couple hours now, trying to wrap my head around these issues, but haven't made any progress, so figured I'd post in here for help.


    Edit:
    I'm not sure if this is the right way, but I followed YieldTechChanges as a template in ImprovementInfos. Following this setup I left well enough alone with the original getPrereqBuildingClass function and created a new one, along with a multidimensional array, where the m_ppbPrereqBuildingAndTechs was the number of buildinclasses in the part and as big as TechInfos in m_ppbPrereqBuildingClass[j]; or in other words:

    I_size = GC.getNumBuildingInfos();
    J_size = GC.getNumTechInfos();

    bool m_ppbPrereqBuildingAndTechs[I_size][J_size];

    Of course since one cannot set up array sizes as non constants, I'm using what i believe is the proper method, as I've followed the TechYieldChanges tag in ImprovementsInfos as a template. Now everything was going smoothly, and I figured I'd have a nice multidimesional array to call weather a unit has a PrereqBuildingClass with a corresponding override tech or not, but then I got to where CvInfos actually reads things from the XML. The problem is the tag I am following has an extra level of depth, as it looks inside the YieldTypes array, in the XML it looks like this:

    Code:
    <TechYieldChanges>
    	<TechYieldChange>
    		<PrereqTech>TECH_AQUACULTURE</PrereqTech>
    		<TechYields>
    			<iYield>0</iYield>
    			<iYield>0</iYield>
    			<iYield>1</iYield>
    		</TechYields>
    	</TechYieldChange>
    </TechYieldChanges>
    What I've got, or rather what makes the most sense to me is this:
    Code:
    <PrereqBuildingClasses>
    	<PrereqBuildingClass>
    		<BuildingClassType>NONE</BuildingClassType>
    		<bPrereq>1</bPrereq>
    		<TechOverride>NONE</TechOverride>
    	</PrereqBuildingClass>
    </PrereqBuildingClasses>
    It's different, and the method CvInfos reads TechYieldChanges doesn't correspond correctly. Here is the code it uses:
    Code:
    	// initialize the boolean list to the correct size and all the booleans to false
    	FAssertMsg((GC.getNumTechInfos() > 0) && (NUM_YIELD_TYPES) > 0,"either the number of tech infos is zero or less or the number of yield types is zero or less");
    	pXML->Init2DIntList(&m_ppiTechYieldChanges, GC.getNumTechInfos(), NUM_YIELD_TYPES);
    	if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TechYieldChanges"))
    	{
    		if (pXML->SkipToNextVal())
    		{
    			iNumSibs = gDLL->getXMLIFace()->GetNumChildren(pXML->GetXML());
    			if (gDLL->getXMLIFace()->SetToChild(pXML->GetXML()))
    			{
    				if (0 < iNumSibs)
    				{
    					for (j=0;j<iNumSibs;j++)
    					{
    						pXML->GetChildXmlValByName(szTextVal, "PrereqTech");
    						iIndex = pXML->FindInInfoClass(szTextVal);
    
    						if (iIndex > -1)
    						{
    							// delete the array since it will be reallocated
    							SAFE_DELETE_ARRAY(m_ppiTechYieldChanges[iIndex]);
    							// if we can set the current xml node to it's next sibling
    							if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TechYields"))
    							{
    								// call the function that sets the yield change variable
    								pXML->SetYields(&m_ppiTechYieldChanges[iIndex]);
    								gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
    							}
    							else
    							{
    								pXML->InitList(&m_ppiTechYieldChanges[iIndex], NUM_YIELD_TYPES);
    							}
    						}
    
    						if (!gDLL->getXMLIFace()->NextSibling(pXML->GetXML()))
    						{
    							break;
    						}
    					}
    				}
    
    				gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
    			}
    		}
    
    		gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
    	}
    I am not familiar with how the XML is read by, and I have always simply copied this stuff before. Right now though I cannot see how to get the XML properly read. Hoping for some help or direction here.
     
  10. phungus420

    phungus420 Deity

    Joined:
    Mar 1, 2003
    Messages:
    6,296
    Well it apears things are getting sorted out in the python/SDK forums. I aparently was not on the right track going with a multidmensional array.
     
  11. xienwolf

    xienwolf Deity

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    Cool. I get email updates from this thread, though I don't check my email too often either, so if you need help with it post here again with a link to the other thread and I'll try to take a look. Been lax on the forum checking lately. A tad busy with other activities :)
     
  12. phungus420

    phungus420 Deity

    Joined:
    Mar 1, 2003
    Messages:
    6,296
  13. Magma_Dragoon

    Magma_Dragoon Reploid

    Joined:
    May 10, 2008
    Messages:
    2,354
    :goodjob:Thanks for the guide on what all the .cpp files are for. I've decided to mod civ4 and get a few more games worth of fun out of it since civ 5 seems kinda "feh".
     
  14. Imp. Knoedel

    Imp. Knoedel Properly Paranoid Proletarian

    Joined:
    Nov 11, 2011
    Messages:
    8,723
    Location:
    The cooler Germany
    How come the wiki link is the most outdated guide of them all then, referring to Visual Studio 2003 of all things?
     
  15. isenchine

    isenchine Empress

    Joined:
    Oct 18, 2010
    Messages:
    1,774
    Location:
    Brussels, Belgium
    We still need to use Microsoft Visual C++ Toolkit 2003 because (I quote Asaf here):
     

Share This Page