Author Topic: Unit Limit  (Read 7264 times)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« on: December 24, 2006, 03:44:06 AM »
Well it seems a lot of people have asked about increasing the unit limit in OP2, so here is a little info on the subject. Not an answer, just some basic info. If you know a little assembly, you can probably figure out how to patch it yourself. But I'll leave that for someone eager enough to learn to be pushed over the edge and try it out themselves. This is written with the assumption that you will follow along with a debugger, but probably take a bit of time to explore on your own at each part.


First of all, I'd like to discuss the hard unit limit. If you look at the map file format, posted somewhere on the forums, you'll see that every tile has 11 bits reserved for a unit index. If the game ever needs to know if there is a unit on any given tile, it can just check this field to see what unit. If this field is 0, then no unit occupies this tile. That means for the correct operation of the game, unit indexes must fit within 11 bits. There is just too much code relying on this fact to change it all, and even if you could, all the other bits are used for important stuff anyways. That means there are 2^11 = 2048 possible unit indexes, and 0 represents no unit. This means the game has a hard limit of 2047 units before you'd need major code rewrites to increase the limit.


I'm sure many of you feel the actual limit imposed by OP2 is actually much lower than this value. I will refer to this limit as the soft limit (whatever the limit actually is), and this is what we would like to change.


I'll be using OllyDbg to take a look at the code, so if you think something looks like it was copied and pasted out of a debugger, it was, and it was done with OllyDbg. It's a free download, just google it if you're looking for it, and it has everything you'd really need in a debugger. There are other ones out there with many cool and useful features, but they generally cost something. I know IDA has some really nice code analysis features which can be really handy in identifying standard C/C++ functions and making sense of the code, although I haven't found that of too much use for most OP2 work. Also, I've added many labels and comments to the code. OllyDbg will save these as a seperate file. If you don't have that file, or you don't insert the comments or labels yourself, your output won't be quite the same. It will have raw hex memory addresses in place of labels, and no comments at all. I've also collapsed the hex representation of the machine instructions since it's not very relevant here. We will simply work with the assembly nmonics.


Ok, so where do we start? Well, I added most of my comments and labels a long time ago, so I can't really remember where I started with those. I guess I'll just jump in to some relevant code and assume we figured out how to find it easily enough without really exaplaining how. I'll point out some of the relevant sections and hopefully it won't be too cryptic. One way these functions can be found within Outpost2.exe, is to take a look at the exported functions, and their parameters (which were largely figured out by playing around with them to see what happened) and then tracing inwards to see where these parameters went, and what Outpost2 did with them. If you need info on the parameters to exported functions, check the header files in the latest release of the SDK. Just search the forums for it.

A good place to start for unit limits, is with unit creation. By tracing the exported CreateUnit function we can determine a little more about how units are created internally. The exported function is only used when the level DLL tries to create a unit, not when a unit is created internally by the game engine, such as when a player builds a vehicle at the vehicle factory.

The (decorated) exported name of the CreateUnit function looks like this:
Code: [Select]
CreateUnit@TethysGame@@SIHAAVUnit@@W4map_id@@ULOCATION@@H1H@Z
By using the names window, we can jump to the code address that this export points to. The first line of the function looks like this:
Code: [Select]
00478780 >/$ 8>SUB ESP,0C

I won't explain this function since it doesn't contain what we are looking for, but I'll give just a few lines showing access to some memory I've labelled. That way you can take a look at the code on your own and see if you can figure out a little bit of what it does. (It helps to know what the memory being referenced is actually used for). Note that you will have raw memory addresses instead of labels unless you have added the labels or are using a comment file that already has a label entered for that address. As a useful exercise, you might want to try and identify the lines that access the parameters of the function. Also, take a look how OllyDbg tries to identify functions, loops, and function calling sequences with the solid black lines. These are not always correct, and sometimes a few are missed, but they are still a very useful visual cue as to the program's structure.  

Code: [Select]
004787C6  |. A>MOV EAX,DWORD PTR DS:[<Map.tileWidth>]
...
004787EB  |. A>MOV EAX,DWORD PTR DS:[<Map.tileHeight>]
...
00478819  |. 8>MOV EBX,DWORD PTR DS:[EDI*4+<UnitTypeInfo*[]>]
...
0047886D  |> 8>MOV EAX,DWORD PTR SS:[ESP+28]                     ;  Load param4 (playerNum)
00478871  |. 8>MOV ECX,DWORD PTR SS:[ESP+24]                     ;  Load param3 (LOCATION.y)
00478875  |. 8>MOV EDX,DWORD PTR SS:[ESP+20]                     ;  Load param3 (LOCATION.x)
00478879  |. 5>PUSH EAX                                          ; /Arg4 - playerNum
0047887A  |. 5>PUSH ECX                                          ; |Arg3 - tileY
0047887B  |. 5>PUSH EDX                                          ; |Arg2 - tileX
0047887C  |. 5>PUSH EDI                                          ; |Arg1 - unitType (map_id enum)
0047887D  |. B>MOV ECX,OFFSET <Outpost2.Map>                     ; |Load "this" pointer (MapData)
00478882  |. E>CALL Outpost2.00436AE0                            ; \Outpost2.00436AE0


Eventually near the bottom of this function, we find that it calls another function. It turns out that this function is a sort of internal CreateUnit function that is called whenever a unit is created, even when it's done by a player ordering a vehicle factory to create a unit. (Although, the function isn't called when the player issues the order, but only when construction of the vehicle is complete). This brings us a little closer to info on the hard limit, since if a unit is created, it must be done so with this function. On the other hand, it doesn't lead directly to the soft limit, since if you've reached the unit limit, you can't even order a factory to produce a unit, which happens much earlier than this function would be called when a unit is compled. The function call line is the following:
Code: [Select]
00478906  |. E>CALL <Outpost2.???.CreateMapUnit(map_id unitType, >; \???.CreateMapUnit

Note that before the function is called, the parameters to that function must first be pushed onto the stack. Take a quick look at the preceeding lines and see if you can figure out what parameters are being passed to it. It is often useful to trace where the parameters being sent in come from, such as from the parameters to the current function. That is, by looking in the SDK header files and seeing what the parameters to the exported CreateUnit function are, we can look to see if these are passed in further to the internal create unit function. Take a look at the first parameter:
Code: [Select]
00478905  |. 5>PUSH EDI                                             ; |Arg1
By looking up through to code to find where the value of EDI is set, we eventually come to the line (right near the top):
Code: [Select]
00478791  |. 8>MOV EDI,EDX

Furthermore, EDX is not set before this. This means that this value is being set by the calling function (or in the case of a bug, it's an unitialized value  :P ). Now typically parameters are passed on the stack, and not in registers. But it so happened that the exported CreateUnit function using a "fastcall" calling convention. This means that the first two parameters to the function are passed in registers instead of on the stack. [roughly anyways, things are a little more complicated when passing objects since you may need a memory reference to them, thus forcing them onto the stack, plus it only considers the first two parameters that fit within 4 bytes as valid candidates. This is one of those detail things that you look up if and when you need it.] It also happens that the registers used by the fastcall calling convention are EDX and ECX. Thus we can see that EDX is one of the parameters.

Ok, so which parameter is it? Well, take a look at how the CreateUnit function is declared in the SDK.
Code: [Select]
	static int __fastcall CreateUnit(class Unit &returnedUnit, enum map_id unitType, 
   struct LOCATION location, int playerNum, enum map_id weaponCargoType, int rotation);
We should find the first parameter in ECX, the second in EDX, and the remaining parameters pushed onto the stack in a right to left order. This means that EDX is "enum map_id unitType". Thus the first parameter to the internal create unit function is the unit type that is being created.


Now let's take a look at the internal create unit function. Following the address used in the call, we find the first line of this function looks something like this:
Code: [Select]
004467C0 >/$ 8>MOV EAX,DWORD PTR SS:[ESP+1C]                        ;  ???.CreateMapUnit(map_id unitType, int pixelX, int pixelY, int creatorIndex, map_id cargoOrWeapon, int unitIndex, bool centerInTile)
Note that I added a comment giving the function some kind of name so I can remember what it does and a list of parameters used and their meaning and types.

Now this function is a little more complicated, but if you've added labels from the previous function, you should probably see that some of those memory locations are reused here.


Jumping to a rather important, but somewhat inconspicuous line, we have:
Code: [Select]
0044682D  |. F>CALL DWORD PTR DS:[EBX]                    ;  UnitTypeInfo.CreateUnit(int pixelX, int pixelY, int unitIndex)

What's going on here, is a call is being made to a function, whose address is stored in EBX. [Why it's doing this is to do with how the MSVC compiler handles object inheritance, and how it selects which function to call based on the object it's working with. The exact call address is not known until runtime, since the exact type of the object isn't know. To make the call correctly, the functions are declared as virtual and the call is made through a virtual function table. This allows objects of a different type to have pointers to different functions, allowing them to have different behavior when the corresponding function is called on each object type. This is sort of like how a laser weapon has a different affect than a sticky foam weapon when it is "fired", but they can both be "fired" in the same way. Using the virtual function table is a quick and easy way to get the compiler to handle which function gets called, and has fairly minimal impact on memory usage and runtime speed. That's not to say you don't need to know what you're doing to program something using this feature. ] The virtual function table is really just a table of function pointers. You should get used to seeing these as Outpost2 uses a lot of these.

These virtual function calls do pose a bit of a problem when analysing code however. Since the call address is not know until runtime, we don't know what code that call is being made to unless we actually run the program and stop it at that line. Also, even if we do know where it calls, that location can change depending on the object type that function is being called on. But not to worry, since once you know what one call destination does, they all essentially have to do the same thing, or rather similar.

It turns out that this line creates a unit of the given type. For each unit type, the virtual function pointer points to a different function, thus by making a "create" call, we can create a unit of any given type, just by calling the "create" function using a correct object. For each unit type, there is an object that creates units of that type. Hence the comment "UnitTypeInfo.CreateUnit". This is sort of like saying "Scout[UnitInfo].CreateUnit", or "Lynx[UnitInfo].CreateUnit", or "CommandCenter[UnitInfo].CreateUnit", although it would have been more like "genericUnitTypeInfo.CreateUnit" since we are using virtual functions. The "enum map_id unitType" parameter is used as an index into an array that contains pointers to all the "UnitInfo" for each unit type. Thus we can call the "create" function through the virtual function defined for that unit type to create the type of unit we want.

Take a look at the line:
Code: [Select]
00446815  |. 8>MOV EDI,DWORD PTR DS:[EAX*4+<UnitTypeInfo*[]>]  ;  Load UnitTypeInfo*[unitType]
This is where it loads the pointer to the object that creates units of the desired type. Here we see that EAX is used as an array index [pointers are 4 bytes long]. By looking up a bit:
Code: [Select]
0044680B  |> 8>MOV EAX,DWORD PTR SS:[ESP+24]                   ;  Load param1 (map_id unitType)
We can see that EAX is loaded with the first parameter, the unit type. This was used in the array lookup.



Ok, so where does this get us? Well by examing all these CreateUnit functions defined for each unit type, we soon start to see some similarities. And yes, there are better ways of finding all these functions than by setting runtime breakpoints. Looking back at the array lookup, you might notice that this array is all filled in nicely before we even start up the program, and it remains fixed. Thus we don't need to wait until runtime to know what all possible objects can have create unit called on them. We have a list of all their addresses. Following each address, we come to the memory layout of the class that stores all the info for units of that type (such as max hitpoints which is the same for all units of a given type, whereas the current hitpoints would be stored with each unit, since it will vary from unit to unit of the same type, and thus need more copies stored).

Slight note here: The memory for all these objects will be 0 until the program starts up and calls the constructors on each object. The constructors will fill in the virtual function table pointers allowing us to do the next part, even if not all the unit data gets read in yet. That might not happen until a level is loaded, but all we need for this is the virtual function tables. You don't need to pause the program or use any kind of breakpoints when inspecting this memory though. Just start up the program so the virtual function table pointers get filled in and you can inspect the data while the program is still running. (Idling at the main menu, or pause it manually yourself at any random code location - WITHIN Outpost2.exe! You don't want to be looking at code from Kernel32.dll really. It won't be a good use of your time. Make sure to check the title bar of the code window after pausing the app to make sure you've paused in the rigth module.)

Now, the first 4 bytes of any class with virtual functions should be the pointer to the virtual function table. If you follow the address stored in the first 4 bytes of the class you'll end up with the virtual function table pointer, which will be a list of pointers into the code section. The first of these functions is the create unit function. Follow this pointer to view the code for the create unit function for units of that type.

Note: Make sure you follow data references in the data window, and code references in the code window. Looking at code in the data window is not very useful. Keep in mind that the virtual function table is stored in the data section. The pointers however point into the code section.


After looking at a few of these functions, it should become obvious that they all call the same function. The call looks something like this:
Code: [Select]
00444F82   . E>CALL <Outpost2.GetNewUnitAddress(???, int pixelX, int pixelY, int unitIndex)>

If this function returns a non zero value, than that address is used as the "this" pointer to a unit object in a constructor call. In other words, if this function can return memory for the unit object, then the unit object is constructed at this address and returned.

Ok, so we follow this function in. The first line looks like:
Code: [Select]
00439A10 >/$ 5>PUSH EBX                                                                                     ;  Function: GetNewUnitAddress(???, int pixelX, int pixelY, int unitIndex)

Look down a few lines until you see:
Code: [Select]
00439A39  |> A>MOV EAX,DWORD PTR DS:[<GameMap.numUnits>]                                                    ;  Load GameMap.numUnits
00439A3E  |. 3>CMP EAX,3FF                                                                                  ;  Check if numUnits != 1023 (0x3FF)
00439A43  |. 7>JNZ SHORT Outpost2.00439A4E                                                                  ;  -> Continue

If that jump isn't taken, the function returns 0. That is, no memory could be allocated to create the unit. But look at the compare. If some number is not 1023, then the function continues on. One of the first things it does after it continues is increment that value. See below:
Code: [Select]
00439A4E  |> F>INC DWORD PTR DS:[<GameMap.numUnits>]                                                        ;  GameMap.numUnits++

Now I already have the label added, so it should be pretty obvious what's going on here. But after some quick experimentation, and looking at code that uses this memory reference, it seems fairly obvious its the current number of units. As long as it's not 1023, a new unit can be created, and the count is updated. But going back to the beginning, the hard limit seemed to be 2047, since 11 bits were used for the unit index. I can't imagine why this value is 1023, other than a possible off by 1 error that is so common in programming. It seems they used 2^10 - 1, instead of 2^11 - 1. If it is an error, it'd likely be hard to find since it won't cause a crash or anything. It just artificially lowers the unit limit to some safe value that is lower than it needs to be. And off by one error in the other direction would be much more noticable though, since crashes could occur if the safe unit limit was exceeded. I could be wrong though, maybe there is a good reason for this limit. But hey, why not try changing it and find out.


So what more is there to change? Well, if you've hit the unit limit, you can't order a vehicle factory to start producing, so we never get to the code we've just analysed. But, we know now where it stores the current number of units, and if it needs to check if a unit limit is exceeded, this might be a number it uses to do that. Don't get your hopes up though, since it'd be more fair to have a unit limit per player, thus preventing one player from building lots of units while someone else has very few. So what happens if we search for references to this memory address? Well, not too many are found, which means it's easy to check what they all do. Unfortunatly, they don't seem to perform max unit checks. The other references to this value that pop up decrement the value, which probably means you've found the code for when units are destroyed, or are somehow removed from game play (such as driving off the map). If it was a max unit check, you would expect some soft of compare instruction (probably CMP), followed by a conditional jump.

But don't give up hope yet, the reference finding abilities of OllyDbg are limited to static references (understandably!). There might still be a dynamic reference to that variable. So how do you find those? Well, set a (hardware) memory breakpoint on that data, and start the game up and see if it fires. Click on a vehicle factory, if it displays the build button when you select something, then it first had to check if it was buildable right now. Which means the code to perform the unit limit check had to run. If it referenced the memory you set a breakpoint at, then it'll halt at that code and let you examine it. If it doesn't halt, then it probably uses a different variable elsewhere, such as a per player unit count.

 One last note: If you set a memory breakpoint on that piece of memory, expect it to fire a lot when you start a level and all the unit and structures are created. It would be wise to start the program, load a level, and THEN set the breakpoint. Otherwise you might find yourself stopping at the same place a few hundred times in code we've just looked at. We want something new. I'd also recommend against using levels with an AI, or disasters if possible, since they might decide to create units on you, which would lead to breakpoint firing when you don't want them to. Also, since a compare require only an access and not a write, you need to set the hardware memory breakpoint on access, and not on write.


I'll stop here for now. This post is already huge and I'd like to see someone try something and reply before I post further.
 

Offline Eddy-B

  • Hero Member
  • *****
  • Posts: 1186
    • http://www.eddy-b.com
Unit Limit
« Reply #1 on: December 24, 2006, 05:19:06 PM »
Are you saying the unit limit is 1023 atm ?  Coz i never reached a unit limit yet.  Come to think of it: when playing with several players, you'd reach it much sooner, as all of you are creating units.

So if the theoretical MAX unit limit is 2^11 -1, then the per-player unitlimit should be 2047/numPlayers  or to be safe for ALL multiplayer games: 2047/8 = 255 per player. Now that is much easier to reach, and if the current limit is not 2047, but rather 1023 (only half), then a per-player limit would be 127 units.

So, in light of the above, i would say the unit limit is not being checked on a per-player basis, but is checked globally, which would still average out to 1023/numPlayers per player. So in a 4 player game, that would be 1023/4 = 255 units per person max atm.

But, then again, doubling the maximum unitcount won't harm the multiplayer community. But i do believe we should then also set a per-player maximum of 2047/numPlayers to keep it fair.
Rule #1:  Eddy is always right
Rule #2: If you think he's wrong, see rule #1
--------------------

Outpost : Renegades - Eddy-B.com - Electronics Pit[/siz

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #2 on: December 24, 2006, 10:35:30 PM »
I never really made any claims about the per person unit limit. That's the next part to be discussed. I only mentioned that there was a global limit of 1023 that really could be 2047 (probably). The part I duscussed is just one part of the problem to increasing the unit limit.

Also, keep in mind that all beacons, fumaroles, magma vents, and disasters take a unit record. That means the number of vehicles and structures each person can have will be a little lower than 1023 [or 2047] / numPlayers.

Of course reserving an equal number of slots for beacons, fumaroles, magma vents, and disasters seems like a waste of unit records. You don't need that many of them for these items. Plus, the max number tends to be fixed and depends on the DLL. It would be possible for a level designer to specify this (although hopefully automated). Which means if you really wanted to push the per player unit limit to a maximum safe value, you can calculate it per level.

Anyways, I'm still not certain how the unit cap is handled exactly. But I do know that once you have too many units, you can't build anything at a vehicle or structure factory. The build buttons don't appear.
 

Offline Eddy-B

  • Hero Member
  • *****
  • Posts: 1186
    • http://www.eddy-b.com
Unit Limit
« Reply #3 on: December 25, 2006, 09:34:13 AM »
Quote
Also, keep in mind that all beacons, fumaroles, magma vents, and disasters take a unit record.
and:
Quote
If the game ever needs to know if there is a unit on any given tile, it can just check this field to see what unit. If this field is 0, then no unit occupies this tile. That means for the correct operation of the game, unit indexes must fit within 11 bits.
Now these 2 findings contradict each other:  we all know that a unit can occupy a beacon tile. Therefore: your assuption that the game uses these 11 bits to determine if a unit is on a particular tile is incorrect!

So: once again.... what IS the hard limit ?  2047 or is it really 65535 ? (or even MAXINT-1)
Rule #1:  Eddy is always right
Rule #2: If you think he's wrong, see rule #1
--------------------

Outpost : Renegades - Eddy-B.com - Electronics Pit[/siz

Offline instigator

  • Jr. Member
  • **
  • Posts: 89
Unit Limit
« Reply #4 on: December 26, 2006, 02:43:01 PM »
Wow this is some deep stuff, keep it going guys! Once i learn assembly and other stuff i can come back and read it again so i know what ur talking about!!

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #5 on: December 29, 2006, 01:53:51 AM »
The game most certainly does use those 11 bits to index into the unit array. I've seen it in a number of places. If this can't hold, than all the code that relies on that assumption will be broken. Also keep in mind that the game doesn't necessarily find units in a certain area in a consistent manner.

I'd also like to point out that you can't target units on the dock of a building. Your units can still attack units on a dock, but they can't be ordered to attack them. This seems to be the most common case where two units occupy the same tile.


Btw, remember your invincible mine you built a while back? You placed two mines on top of each other right? If I remember correctly, they couldn't target the second mine once the first was destroyed. Explain how that happened.


As far as that hard limit goes, I'm sticking with 2047, since you'd need a lot of code changes to get things to work past that point. Simply for the reason that those 11 bits are used to index into the unit array, and those 11 bits are used in many independent locations that are not easily tracked down and fixed.
 

Offline BlackBox

  • Administrator
  • Hero Member
  • *****
  • Posts: 3093
Unit Limit
« Reply #6 on: January 05, 2007, 06:06:50 PM »
Interesting stuff. Perhaps it will spur other people to look inside the game and learn assembly :P

There are a couple "issues" I have with the game however. I recall that I might have looked at increasing this limit before, although there is the 1024 check in the unit creation functions, I believe I found some sort of memory limits elsewhere. (They might be easy to change to accomodate more units).

One definite place where there is a limit is the memory allocated for the unit array itself (the GameMap class keeps this). Although the unit list is technically a linked list the game can and does address directly into this array rather than traversing the linked list to find a unit (such as when the unit index lookup takes place from the map data, it uses the number stored in the 11 bits to go directly to the correct location in the unit array -- keeping in mind that the internal Unit class is 120 bytes long).

Luckily this is allocated off the heap instead of being static storage in the .data section, so we could change the call to malloc. I can't remember the address of the call at the moment.

However -- I want to say that there is some sort of statically-allocated buffers associated with units. It's been a while since I checked this out but I remember seeing some real show-stopper.

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #7 on: January 06, 2007, 11:41:37 AM »
Unit Array Memory
----------------------

Thanks.

Code: [Select]
00435660  |. 6>PUSH 1E078                              ;  Arg1 - uint numBytesToAlloc = 123000 (0x1E078) [1025 * 120]
00435665  |. E>CALL Outpost2.004C21F0                  ;  _malloc
0043566A  |. 8>ADD ESP,4                              ;  Cleanup parameters from stack (malloc)
0043566D  |. 8>MOV DWORD PTR DS:[EBX+50],EAX          ;  Map.unitArray* := EAX

The unit array is of course dynamically allocated, which is no surprise since the unit array is always accessed through a pointer to the array data. Here is the call that allocated the memory for the units. Just increase the amount of space it allocates.

There are also other memory allocations near that point, so if for some reason you need to increase the amount of memory available to those other areas, they can be changed too.


I would hope there is no static memory allocation problems that would hamper increasing the unit limit, but I guess I wouldn't be too surprised. I'll just remain hopeful for now, since the worst I have yet to see is hardcoded constants that we could just overwrite (and the index constraint, which is sort of a static memory allocation problem).



Edit: Also pay close attention to the code that follows the memory allocation. It initializes the array (to 0xEAEAEAEA), and a bit later, the linked list pointers, but again, the size of memory initialized is a hardcoded constant. These values would need to be changed as well.

 
« Last Edit: January 06, 2007, 11:51:11 AM by Hooman »

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #8 on: August 01, 2007, 09:46:19 PM »
Small update. I happened across some code, some of which I see in the post above, and I noticed something relevant.

The Map class allocates the memory for the units, which is where the code above is from. It allocates space for 1025 records (odd that it isn't 1024). But then when saved games are read or written, it always saves/loads 1023 units from the file. (Also odd that this isn't 1024). But, I suppose that's why in the CreateUnit function (see a few posts above), it checks against 1023, and won't create past that many.


So, if you wanted to up the unit limit, the saved game file format would also be an issue.
 

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #9 on: November 28, 2007, 10:57:04 PM »
I found the soft unit limit tables.

004E9908 VehicleLimitTable[0]
004E9920 BuildingLimitTable[0]

Each table has 6 entries in it, for the number of players in the game. Entry 0 being for a 1 player game.



Anyone for experimenting with increasing the units limits now?




 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Unit Limit
« Reply #10 on: November 29, 2007, 01:33:20 AM »
You bet!  :lol:  
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #11 on: April 23, 2008, 12:18:46 AM »
I spent some time playing with the hard limit today. Still no go, but perhaps some progress.

I made about 14 edits to various places in the exe trying to get the game engine to accept more units. That part seems to be working, but it seems there are a few other limitations. The mini map does some odd stuff using a static buffer, which can be overflowed now and cause crashes. I haven't really looked into that part of the mini map data structure, or too heavily at that code, so it may take a bit of time to figure out exactly what needs to be done there. I also haven't adjusted the saved game file loading and saving code. Once I do, it'll probably invalidate all the old saved game files. Currently, I expect problems if more then 1023 units are ever allocated at once, in which case saves will truncate data, and loads will leave data unitialized. I might be able to get old saved game files to load after the edits with some additional work, and maybe even save to the old format if the extra unit space is never used, but this isn't currently a priority.


In another note, I found what seems to be 2 bugs in the existing unit allocation code. Both of which can be somewhat like a memory leak and lead to resource exhaustion, but they are so infrequent that you'd probably never notice. One of which only seems to happen when the number of free unit records is low, and compounds the problem by artificially lowering the max number of units allowed. The other will occur after every 1023 freed unit records get reallocated, and with high probability lose 1 unit record, and with low probability will cause a possibly large chunk of unit records to be reused which were never freed, and thus corrupt existing units.

My guess is the second bug was caused by a typo in the source code. It looks like someone might have written something along the lines of:
Code: [Select]
index = (index + 1) % 1023;
instead of:
Code: [Select]
index = (index + 1) & 1023;
or equivalently:
Code: [Select]
index = (index + 1) % 1024;

Both bugs aren't too hard to fix.
« Last Edit: April 23, 2008, 12:20:38 AM by Hooman »

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #12 on: April 24, 2008, 12:46:44 AM »
Did some more work on this today, and now we're at "maybe".

The game seemed to play fine with regular levels, and even seemed to work fine with my initial stress test of around 1023 lynxes. Created, destroyed, and created again, and then created a few more one at a time so it went over the old limit. No odd behavior detected that time. I tried to up the test to about 2000 lynxes, and the game crashed mysteriously on me. But, I've traced the crash to somewhere within AIProc in the test DLL, which is where I've been creating and destroying units, so maybe I just screwed up in there somewhere. I'll hold off on posting the mod until I find out the reason for the crash.

ugh, some of this patching is very tedious.  :(
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Unit Limit
« Reply #13 on: April 24, 2008, 07:21:22 AM »
We know you can do it Hooman! Keep it up!
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #14 on: April 26, 2008, 03:38:42 PM »
Ok, I have something that seems to be working and is stable. I have yet to touch the saved game code though, so that part will still be broken.

Btw, display is a little slow when the screen is full of units.

Here's what 2041 Laser Lynxes looks like. (I decided not to go to the full 2047 limit cap since weapons fire and self destruct take unit records to work, so if you built that many units, they wouldn't be able to fire, or even self destruct to get rid of them).
 

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Unit Limit
« Reply #15 on: April 26, 2008, 03:58:17 PM »
But... But...
Those Lasers are on a Plymouth chassis!
Unacceptable!

Other than that, amazing work (as always) Hooman!
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Unit Limit
« Reply #16 on: April 26, 2008, 05:00:06 PM »
That's like... OMG! Big congratz on this one.

So... could this be used in multiplayer somehow? I'm guessing situations where you would attack with a full screen+ of units would be rather...rare :)
« Last Edit: April 26, 2008, 05:00:32 PM by Hidiot »
"Nothing from nowhere, I'm no one at all"

Offline Leviathan

  • Hero Member
  • *****
  • Posts: 4055
Unit Limit
« Reply #17 on: April 27, 2008, 01:08:48 AM »
Great work. Looks cool :)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #18 on: April 27, 2008, 01:01:05 PM »
Heh, I wasn't even paying attention to the chasis.

This patch would affect all game types, so yes, multiplayer could make use of this too.

I should mention the patch isn't finished yet though. Currently only DLLs can create that many. I have yet to update the soft limit, or even decided how I want to update the soft limit. Until I update that, the build button at factories will still dissapear long before you reach the hard limit. It's an easy table update when I get around to it though.

I also haven't looked into fixing the saved game code yet either. Save games will be broken with this patch until that's fixed. (Well, partially broken anyways. It might depend on the values of certain variables as to whether or not it works right).

I also have to apply the recent changes to an exe file. They're still done with in memory patching in OllyDbg. This patch is much larger then usual, so I'm gonna have to do it some automated way rather then hand patching the exe with a hex editor. I was going to figure out how to do this yesterday, but I got called away from my computer.
 

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Unit Limit
« Reply #19 on: April 27, 2008, 01:30:43 PM »
Well, saved games fixing shouldn't be such a priority now, should it?

No need to hurry.
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #20 on: April 28, 2008, 10:01:10 PM »
Test exe attached.


Ok, so I've completed the patch. The hard limit should now be 2047, and I've also updated the soft limit tables to double the number of units it will let you build. (I did more then double for 1 player games, but there are no 1 players games, since even colony games usually count as 2 players due to the AI. But if someone wants to build a sandbox mode, the limit is now 400 vehicles, and 950 buildings).

The saved game code has been updated to allow saving and loading of more unit records. This change is not compatible with the old saved game format. You will not be able to load old saved games with this copy, and anything you save with this copy can't be loaded by older copies.

There's also a slight change to how the mini map is updated, which may make it draw a little bit slower. Otherwise there should be no slowdown other then if you actually build past the old unit limit and the game has a ton of units to process for. I find it's a bit noticable for a screen full of units, but most people have a better computer than mine.


Anyways, go nuts.


Btw, let me know if the saved game issue is a particular problem for anyone. I could potentially do a bit more work to allow it to load older saved games, and maybe even save old style games when the number of units isn't too large, or I can make a standalone saved game converter. Either way, it'd be somewhat difficult to do for one reason or another, so if noone really cares, I wouldn't mind getting away with not handling this.


Edit: Btw, don't overwrite the exe for your multiplayer version. Make a seperate install or something. This patch isn't actually based off the current version people use for multiplayer, so expect a few minor differences.


Edit: Attachment removed. See attachment later in thread.
 
« Last Edit: August 17, 2008, 12:18:53 AM by Hooman »

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3237
Unit Limit
« Reply #21 on: April 29, 2008, 03:30:07 PM »
My advice: if you're going to bother with saved games, just do a standalone converter.
Now, on with the show!

Edit: Geez, you weren't kidding when you said it was an old version Hooman! It still needs Helphook.dll to run!
« Last Edit: April 29, 2008, 03:56:50 PM by Sirbomber »
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #22 on: April 29, 2008, 06:37:06 PM »
I'm pretty sure it was based on a clean CD install, with the 3 official updates applied. I then probably just hex edited a few patches just to make it easier to use a debugger with it (noCD, GDI draw), and then maybe whatever I was testing out, such as the current patch. Most patches were probably only done in memory though, so the copy is still probably pretty clean.

This patch was getting annoying doing that way thought due to the size, and the crashing. Each time it crashed, I had to restart the process, and then reapply all the code patches I was testing, and this change required a lot of individual code patches.


Anyways, I kept a change log so nobody would forget what changes were needed for this. Excuse the formatting of the file though, as it started to get a bit big, and I just sort of stopped caring about making it all perfectly uniform. I also didn't bother to supply both virtual addresses and file offsets for everything, but you can always figure out one from the other.

Offline Hidiot

  • Hero Member
  • *****
  • Posts: 1018
Unit Limit
« Reply #23 on: May 01, 2008, 07:56:53 AM »
Ok, so... excuse my ignorance and/or stupidity... but how do we make ti work then?
"Nothing from nowhere, I'm no one at all"

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4954
Unit Limit
« Reply #24 on: May 01, 2008, 04:23:07 PM »
I've basically have a CD install with a patched exe. That will make it work.

I'm not actually sure what all the changes to the exe are, so I can't really help you with getting it to work with the current version people use. I just tried it with the current version and I get a "Could Not Initialize Game" error message whenever I try to start a level. I had no trouble with helphook.dll though, as Sirbomber mentioned. (And placing that DLL in the Outpost2 folder didn't solve my problem either).

I also tried replacing the vol files with the older versions, since I know they were repacked, and even split in the case of maps.vol. Still didn't solve the problem though.

There's nothing I can see in the ChangeLog.txt with the current version that would explain this problem.


... and the debugger tells me it's a problem with the .vol files. Seems the original vol files are being loaded, and then when the tech tree resource is looked up, it checks against a new table (that has the maps.vol file split), and all the loaded Vol pointers are null, so it can't find anything. Checking back to where it loaded the Vol data, it seems the pointer was overwritten a bit late, probably due to some patch DLL being loaded later than expected? I have a feeling this is caused by op2ext.dll, but I'm not entirely sure, because I didn't write that DLL, or the Vol loading patch.

This is probably where I stop and just ask BlackBox about it. Meanwhile, if you have a CD, or a CD install, use that as a base, and copy the new exe into that folder.
 
« Last Edit: May 01, 2008, 04:29:16 PM by Hooman »