As we've discussed in the past, video game code probably shouldn't be held to the standards of your average WTF: they're operating under wildly different constraints. So, for example, when a popular indie game open sources itself, and people find all sorts of horrors in the codebase: hey, the game shipped and made money. This isn't life or death stuff.
It's a little different when you're building the engine. You're not just hacking together whatever you need to make your product work, but putting together a reusable platform to make other people's products work.
Rich D, who previously shared some horrors he found in the Unreal engine, recently discovered that UnrealScript has a useful sounding JsonObject
. Since Rich is thinking in terms of mods, being able to read/write JSON to handle mod configuration is useful, but anyone designing a game might have many good reasons to want JSON documents.
The file starts promisingly with:
class JsonObject extends Object
native;
/// COMMENT!!
…
It's good that someone put that comment there, because I assume it was meant as a reminder: comment this code. And the comments start giving us hints of some weird things:
/**
* Looks up a value with the given key in the ObjectMap. If it was a number
* in the Json string, this will be prepended with \# (see below helpers)
*
* @param Key The key to search for
*
* @return A string value
*/
native function string GetStringValue(const string Key);
The method GetStringValue
returns a string from JSON, but if the string is a number, it… puts a \#
in front of it? Why?
function int GetIntValue(const string Key)
{
local string Value;
// look up the key, and skip the \#
Value = Mid(GetStringValue(Key), 2);
return int(Value);
}
Oh… that's why. So that we can ignore it. There's a similar version of this method for GetFloatValue
, and GetBoolValue
.
So, how do those \#
s get prepended? Well, as it turns out, there are also set methods:
function SetIntValue(const string Key, int Value)
{
SetStringValue(Key, "\\#" $ Value);
}
In addition to these methods, there are also native
methods (e.g., methods which bind to native code, and thus don't have an UnrealScript body) to encode/decode JSON:
/**
* Encodes an object hierarchy to a string suitable for sending over the web
*
* @param Root The toplevel object in the hierarchy
*
* @return A well-formatted Json string
*/
static native function string EncodeJson(JsonObject Root);
/**
* Decodes a Json string into an object hierarchy (all needed objects will be created)
*
* @param Str A Json string (probably received from the web)
*
* @return The root object of the resulting hierarchy
*/
static native function JsonObject DecodeJson(const string Str);
Guided by this code, Rich went on to do a few tests:
- Calling
SetStringValue
with a string that happens to start with\#
causesEncodeJson
to produce malformed output. - Calling
SetStringValue
with any string that might require escape characters (newlines, backslashes, etc.) will not escape those characters, producing malformed output. - Which means that the output of
EncodeJson
cannot reliably be parsed byDecodeJson
, as sometimes the output is invalid - Sometimes, when
DecodeJson
receives an invalid document, instead of throwing an error, it just crashes the entire game
Rich has wisely decided not to leverage this object, for now.