Skip to content

Commit

Permalink
Core - Fix load/save of deleted entities such as bushes and trees (#153)
Browse files Browse the repository at this point in the history
* Fix ACE_HexTools.HexStrToInt to support any length hex strings

EntityIDs are constructed from Low and high 32-bit IDs, the original code here didn't account for entities that have a high ID of zero, and values that were less then 8 characters long.

This new implementation removes the float math and uses bitwise operations to correctly convert to the expected array format in the correct byte order with no length limitations for the input.

* Update ACE_EntityIdHelper to support low IDs entity IDs

Map entities such as trees and bushes do not have a high ID. This results in the game engine returning `0x0x` prefixes when ToString is called.

ToInt and FromString have also been updated to use a high ID of 0 when the array only contains a single element.

* Added myself to AUTHORS

* Gracefully handle invalid/nil EntityID values

* Put nibbles directly at the correct position

---------

Co-authored-by: Kex <[email protected]>
  • Loading branch information
gnif and Kexanone authored Jan 30, 2025
1 parent da635e0 commit 387c890
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 51 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ veteran29
AkiKay
Badger 2-3
Glowbal
gnif <[email protected]>
Jonpas <[email protected]>
LorenLuke
OverlordZorn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class ACE_EditorStruct : SCR_JsonApiStruct

foreach (string str : m_aACE_DeletedEntityIDs)
{
entityIDs.Insert(ACE_EntityIdHelper.FromString(str));
EntityID entity = ACE_EntityIdHelper.FromString(str);
if (entity != EntityID.INVALID)
entityIDs.Insert(entity);
}

manager.DeleteEntitiesByIdGlobal(entityIDs);
Expand Down
39 changes: 33 additions & 6 deletions addons/core/scripts/Game/ACE_Core/Global/ACE_EntityIdHelper.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,50 @@ class ACE_EntityIdHelper
//------------------------------------------------------------------------------------------------
static array<int> ToInt(EntityID id)
{
return ACE_HexTools.HexStringToInt(ToString(id));
array<int> bits = ACE_HexTools.HexStringToInt(ToString(id));
if (bits.Count() == 1)
bits.InsertAt(0, 0);

return bits;
}

//------------------------------------------------------------------------------------------------
static EntityID FromString(string str)
{
array<int> bits = ACE_HexTools.HexStringToInt(str);
if (bits.Count() < 2)
// See: https://feedback.bistudio.com/T188436
if (str == "0x(nil)" || str == "(nil)")
return EntityID.INVALID;

array<int> bits = ACE_HexTools.HexStringToInt(str);
if (bits.Count() == 1)
bits.InsertAt(0, 0);
else
if (bits.Count() > 2)
return EntityID.INVALID;

return EntityID.FromInt(bits[0], bits[1]);
}

//------------------------------------------------------------------------------------------------
static string ToString(EntityID id)
{
if (id == EntityID.INVALID)
return "0x0";

// Drop the last three characters, which are " {}"
return id.ToString().Substring(0, 18);
string str = id.ToString();

// Low id items such as trees/bushes end up with this odd format, fix it
// See: https://feedback.bistudio.com/T188436
if (str.StartsWith("0x0x"))
str = str.Substring(2, str.Length() - 5);
else
str = str.Substring(0, str.Length() - 3);

// Just incase EntityID.ToString still returned an invalid value
if (str == "0x(nil)" || str == "(nil)")
return "0x0";

return str;
}
}
74 changes: 30 additions & 44 deletions addons/core/scripts/Game/ACE_Core/Global/ACE_HexTools.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,50 @@ class ACE_HexTools
{
protected static int s_iAscii0 = "0".ToAscii();
protected static int s_iAscii9 = "9".ToAscii();
protected static int s_iAsciiA = "A".ToAscii();
protected static int s_iAsciiF = "F".ToAscii();
protected static int s_iAsciiLoA = "a".ToAscii();
protected static int s_iAsciiLoF = "f".ToAscii();

protected static int s_iAsciiA = "a".ToAscii();
protected static int s_iAsciiF = "f".ToAscii();

//------------------------------------------------------------------------------------------------
//! Gets int values of hexadecimal string. Expected to be prefixed with "0x"
//! Ordered by most significant bits
static array<int> HexStringToInt(string hexString)
{
if (hexString.Substring(0, 2) != "0x")
return {};

int numHexChars = hexString.Length() - 2;
if (!float.AlmostEqual(Math.Mod(numHexChars, 8), 0))
hexString.TrimInPlace();
hexString.ToLower();
if (!hexString.StartsWith("0x"))
return {};

hexString = hexString.Substring(2, numHexChars);

int numBit32 = (hexString.Length()) / 8;
array<int> result = {};
result.Reserve(numBit32);
int numNibbles = hexString.Length() - 2;
// Calculate how many 32 bit integers are needed to store the hex
int numValues = ((numNibbles + 7) & ~7) / 8;
result.Reserve(numValues);

int value = 0;
int outIdx = 0;

for (int i_bit32 = 0; i_bit32 < numBit32; i_bit32++)
for (int inpIdx = numNibbles - 1; inpIdx >= 0; --inpIdx)
{
int bit32;
int nibble = hexString.ToAscii(inpIdx + 2);

for (int i_bit4 = 8 * i_bit32; i_bit4 < 8 * (i_bit32 + 1); i_bit4++)
{
int bit4 = HexCharToInt(hexString, index: i_bit4);
if (bit4 < 0)
return result;

bit32 = 16 * bit32 + bit4;
}
if (nibble >= s_iAscii0 && nibble <= s_iAscii9)
nibble -= s_iAscii0;
else if (nibble >= s_iAsciiA && nibble <= s_iAsciiF)
nibble -= s_iAsciiA - 10;
else
return {}; //invalid character in string

result.Insert(bit32);
// Add the nibble to the correct position in the 32 bit integer
value |= nibble << 4 * outIdx;

if (++outIdx == 8 || inpIdx == 0)
{
result.InsertAt(value, --numValues);
value = 0;
outIdx = 0;
}
}

return result;
}

//------------------------------------------------------------------------------------------------
//! Gets int value of a character in a hexadecimal string
//! Returns -1 if parsing failed
//! \param index Index of the character, 0 by default.
static int HexCharToInt(string hexString, int index = 0)
{
int value = hexString.ToAscii(index);
if (value >= s_iAscii0 && value <= s_iAscii9)
value -= s_iAscii0;
else if (value >= s_iAsciiA && value <= s_iAsciiF)
value -= s_iAsciiA - 10;
else if (value >= s_iAsciiLoA && value <= s_iAsciiLoF)
value -= s_iAsciiLoA - 10;
else
return -1;

return value;
}
}

0 comments on commit 387c890

Please sign in to comment.