-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create Built In Module to address garage data corruption during saving #204
Comments
Link to forum thread containing the discussion: |
I believe I have the conditions that lead to the double save corruption problem. It happens when the unit array base address is a multiple of 120. (The size of a unit record is 120 bytes).
Digging into the memory allocation routines a bit, I found internal calls to For a bug reproducer level, we can offset the // Untested
*reinterpret_cast<std::uintptr_t*>(unitArrayPointerAddress) += reinterpret_cast<std::uintptr_t>(-unitArrayBaseAddress) % 120; More properly, we should reallocate the array to include space for an extra unit record, just to make sure the full array fits in the shifted position. Realistically, we can probably get away with not doing that since we won't ever be using the full unit array in a bug reproducer, and should never access past the first 8 bytes of the last record (which due to alignment and offsetting properties, should still be within the original array). Adding the above to |
To add details to how the above was determined, here's a bit about the transformations that are done. To index into the unit array &unit = &units[] + unitIndex * sizeof(Unit); To go the reverse direction, the formula is: unitIndex = (&unit - &units[]) / sizeof(Unit); Marking up the above formulas with type information, they become: (uintptr_t)&unit = (uintptr_t)&units[] + (size_t)unitIndex * (size_t)sizeof(Unit); (size_t)unitIndex = ((uintptr_t)&unit - (uintptr_t)&units[]) / (size_t)sizeof(Unit); In practice, things get a little more interesting with the types. In the first direction, converting a Now, the shift In the reverse direction, the division was done with the Adjusting for signedness, the address to index formula was actually implemented as: (size_t)unitIndex = ((intptr_t)&unit - (intptr_t)&units[]) / (size_t)sizeof(Unit); Now, the garage code used a sentinel value of ceiling(((intptr_t)&unit - (intptr_t)&units[]) / (size_t)sizeof(Unit))
= ceiling((-2 - &units[]) / 120)
= ((-2 - &units[]) + (-(-2 - &units[]) % 120)) / 120 // Add remainder to obtain multiple of 120 (round up)
= ((-2 - &units[]) + ((2 + &units[]) % 120)) / 120 Using the above, we can calculate the full round trip, from pointer to index, and then back to pointer, the formula is: ceiling(((intptr_t)&unit - (intptr_t)&units[]) / (size_t)sizeof(Unit)) * 120 + &units[]
= (((-2 - &units[]) + ((2 + &units[]) % 120)) / 120) * 120 + &units[]
= ((-2 - &units[]) + ((2 + &units[]) % 120)) + &units[]
= -2 + ((2 + &units[]) % 120) Now, the bug occurred when the round trip conversion stored the value 0. Solving the above for 0, we have: -2 + ((2 + &units[]) % 120) == 0
((2 + &units[]) % 120) == 2
&units[] % 120 == 0 The bug then works as follows: |
Sorry to be so dense, but where does your suggestion fit into our code: // Untested
*reinterpret_cast<std::uintptr_t*>(unitArrayPointerAddress) += reinterpret_cast<std::uintptr_t>(-unitArrayBaseAddress) % 120; void PatchUnitArrayAddress()
{
std::uintptr_t unitArray = 0x04170030;
constexpr std::size_t unitArraySize = 0x3c078;
LPVOID result = VirtualAlloc(reinterpret_cast<LPVOID>(unitArray), unitArraySize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
constexpr std::uintptr_t unitArrayPointer = 0x54F848;
*reinterpret_cast<std::uintptr_t*>(unitArrayPointer) = unitArray;
} |
It should replace the last line. We won't need the We should probably get the bug reproducer uploaded to GitHub. I'm unable to build missions from Linux, so it would be nice to get the project building on a CI server. Might even be a good test project to experiment with CI artifacts and GitHub releases. Minor point, but for the bug reproducer project, it may make sense to create two separate commits. The first commit could add the |
So, how do we know what the unitArrayBaseAddress is then? void PatchUnitArrayAddress()
{
constexpr std::uintptr_t unitArrayPointerAddress = 0x54F848;
*reinterpret_cast<std::uintptr_t*>(unitArrayPointerAddress) += reinterpret_cast<std::uintptr_t>(-unitArrayBaseAddress) % 120;
} |
Oh, sorry, I was being sloppy. The names are a bit hard to get right. The void PatchUnitArrayAddress()
{
constexpr std::uintptr_t unitArrayPointerAddress = 0x54F848;
auto unitArrayPointer = reinterpret_cast<std::uintptr_t*>(unitArrayPointerAddress);
*unitArrayPointer += -(*unitArrayPointer) % 120;
} Don't worry if the details aren't quite right yet. We can push that part off into a branch if needed. Having the core bug reproducer with just the garage and loaded unit would be helpful for now. |
Write a built in module to patch Outpost 2 and prevent a union field from being set to null intermittently during save process.
See forum post for details.
The text was updated successfully, but these errors were encountered: