Skip to content

Commit

Permalink
Handle struct array properties whose first element is a struct with a…
Browse files Browse the repository at this point in the history
…ll default values.
  • Loading branch information
tracktwo committed Mar 12, 2016
1 parent b23672c commit 5198ca1
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 10 deletions.
20 changes: 20 additions & 0 deletions json2xcom.cpp
Expand Up @@ -23,6 +23,7 @@ property_ptr build_float_property(const Json& json);
property_ptr build_bool_property(const Json& json);
property_ptr build_object_property(const Json& json);
property_ptr build_string_property(const Json& json);
property_ptr build_name_property(const Json& json);
property_ptr build_enum_property(const Json& json);
property_ptr build_struct_property(const Json& json);
property_ptr build_array_property(const Json& json);
Expand All @@ -39,6 +40,7 @@ static property_dispatch dispatch_table[] = {
{ "FloatProperty", build_float_property },
{ "BoolProperty", build_bool_property },
{ "StrProperty", build_string_property },
{ "NameProperty", build_name_property },
{ "ObjectProperty", build_object_property },
{ "ByteProperty", build_enum_property },
{ "StructProperty", build_struct_property },
Expand Down Expand Up @@ -199,6 +201,24 @@ property_ptr build_string_property(const Json& json)
xcom_string{ json["value"].string_value(), wide });
}

property_ptr build_name_property(const Json& json)
{
std::string err;
Json::shape shape = {
{ "name", Json::STRING },
{ "string", Json::STRING },
{ "number", Json::NUMBER }
};

if (!json.has_shape(shape, err)) {
throw std::runtime_error(
"Error reading json file: format mismatch in name property");
}

return std::make_unique<name_property>(json["name"].string_value(),
json["string"].string_value(), json["number"].int_value());
}

property_ptr build_object_property(const Json& json)
{
std::string err;
Expand Down
1 change: 1 addition & 0 deletions util.cpp
Expand Up @@ -291,6 +291,7 @@ namespace xcom
case property::kind_t::object_property: return "ObjectProperty";
case property::kind_t::enum_property: return "ByteProperty";
case property::kind_t::struct_property: return "StructProperty";
case property::kind_t::name_property: return "NameProperty";
case property::kind_t::array_property:
case property::kind_t::object_array_property:
case property::kind_t::number_array_property:
Expand Down
35 changes: 35 additions & 0 deletions xcom.h
Expand Up @@ -167,6 +167,7 @@ namespace xcom
bool_property,
string_property,
object_property,
name_property,
enum_property,
struct_property,
array_property,
Expand Down Expand Up @@ -199,6 +200,7 @@ namespace xcom
struct bool_property;
struct string_property;
struct object_property;
struct name_property;
struct enum_property;
struct struct_property;
struct array_property;
Expand Down Expand Up @@ -226,6 +228,7 @@ namespace xcom
virtual void visit(bool_property*) = 0;
virtual void visit(string_property*) = 0;
virtual void visit(object_property*) = 0;
virtual void visit(name_property*) = 0;
virtual void visit(enum_property*) = 0;
virtual void visit(struct_property*) = 0;
virtual void visit(array_property*) = 0;
Expand Down Expand Up @@ -339,6 +342,34 @@ namespace xcom
xcom_string str;
};

// A name property contains a string value referencing an Unreal name entry, and
// a number whose use is currently unknown.
//
// Be careful: the name field of properties refers to the name of the property,
// the contents of a name property is in the str field, similar to a string
// property.
struct name_property : public property
{
name_property(const std::string& n, const std::string& s, int32_t d) :
property(n, kind_t::name_property), str(s), number(d) {}

virtual void accept(property_visitor *v) {
v->visit(this);
}

virtual size_t size() const {
// Length of the string + null byte + the string size integer + the
// number value.
// Note: This is making the (reasonable?) assumption that name strings
// can never be empty and the (unreasonable?) assumption that names
// are always ASCII.
return str.length() + 1 + 4 + 4;
}

std::string str;
int32_t number;
};

// A dynamic array property. Represents an Unreal dynamic array. This type
// is used for "raw" arrays where we have not correctly determined the
// contents of the array. The saved array format doesn't explicitly say
Expand Down Expand Up @@ -496,6 +527,10 @@ namespace xcom
value{ ev, i } {}

size_t size() const {
// Handle the special "None" byte type which is just a single byte.
if (type == "None") {
return 1;
}
// size does not include the size of the enum type string
return value.name.length() + 5 + 4;
}
Expand Down
9 changes: 9 additions & 0 deletions xcom2json.cpp
Expand Up @@ -242,6 +242,15 @@ struct json_property_visitor : public property_visitor
w.end_object();
}

virtual void visit(name_property *prop) override
{
w.begin_object(true);
write_common(prop, true);
w.write_string("string", prop->str, true);
w.write_int("number", prop->number, true);
w.end_object();
}

virtual void visit(object_property *prop) override
{
w.begin_object(true);
Expand Down
69 changes: 61 additions & 8 deletions xcomreader.cpp
Expand Up @@ -126,6 +126,20 @@ namespace xcom
return std::make_unique<struct_property>(name, struct_name,
r.read_raw_bytes(12), 12);
}
else if (struct_name.compare("Rotator") == 0) {
return std::make_unique<struct_property>(name, struct_name,
r.read_raw_bytes(12), 12);
}
else if (struct_name.compare("Box") == 0) {
// A "box" type. Unknown contents but always 25 bytes long
return std::make_unique<struct_property>(name, struct_name,
r.read_raw_bytes(25), 25);
}
else if (struct_name.compare("Color") == 0) {
// A Color type. Unknown contents (4 bytes)
return std::make_unique<struct_property>(name, struct_name,
r.read_raw_bytes(4), 4);
}
else {
property_list structProps = read_properties(r);
return std::make_unique<struct_property>(name, struct_name,
Expand Down Expand Up @@ -159,6 +173,13 @@ namespace xcom
return property::kind_t::last_property;
}

// If the first thing we get is a "None", then this is a struct
// property with all default values in the first element.
if (s.str.compare("None") == 0)
{
return property::kind_t::struct_array_property;
}

// Try to read another string. If we find a non-zero length string this must be
// an array of strings. Otherwise it's likely an array of enums or structs.
{
Expand Down Expand Up @@ -293,6 +314,7 @@ namespace xcom
if (name.compare("None") == 0) {
break;
}

std::string prop_type = r.read_string();
int32_t unknown2 = r.read_int();
if (unknown2 != 0) {
Expand Down Expand Up @@ -328,10 +350,20 @@ namespace xcom
"Read non-zero enum property unknown value: %x\n",
inner_unknown);
}
std::string enum_val = r.read_string();
int32_t extra_val = r.read_int();
prop = std::make_unique<enum_property>(name, enum_type,
if (enum_type == "None") {
// Sigh. ByteProperty can be an enum value, or can just
// be a raw byte if the "type" field is "None". Read just
// a single byte and use that as the "extra" value.
unsigned char c = r.read_byte();
prop = std::make_unique<enum_property>(name, enum_type,
"None", c);
}
else {
std::string enum_val = r.read_string();
int32_t extra_val = r.read_int();
prop = std::make_unique<enum_property>(name, enum_type,
enum_val, extra_val);
}
}
else if (prop_type.compare("BoolProperty") == 0) {
assert(prop_size == 0);
Expand All @@ -352,6 +384,11 @@ namespace xcom
xcom_string str = r.read_unicode_string();
prop = std::make_unique<string_property>(name, str);
}
else if (prop_type.compare("NameProperty") == 0) {
std::string str = r.read_string();
int32_t number = r.read_int();
prop = std::make_unique<name_property>(name, str, number);
}
else
{
throw format_exception(r.offset(),
Expand All @@ -364,22 +401,28 @@ namespace xcom
properties.push_back(std::move(prop));
}
else {
#if 0
if (properties.back()->name.compare(name) != 0) {
throw format_exception(r.offset(),
"Static array index found but doesn't match previous property\n");
}
#endif

if (properties.back()->kind == property::kind_t::static_array_property) {
// We already have a static array. Sanity check the
// array index and add it
static_array_property *static_array =
static_cast<static_array_property*>(properties.back().get());
#if 0
assert(array_index == static_array->properties.size());
#endif
static_array->properties.push_back(std::move(prop));
}
else {
// Not yet a static array. This new property should have index 1.
#if 0
assert(array_index == 1);
#endif

// Pop off the old property
property_ptr last_property = std::move(properties.back());
Expand Down Expand Up @@ -491,7 +534,8 @@ namespace xcom
checkpoint_chunk_table read_checkpoint_chunk_table(xcom_io &r)
{
checkpoint_chunk_table checkpoints;

std::vector<name_table> name_tables;
std::vector<actor_template_table> actor_templates;
// Read the checkpoint chunks
do {
checkpoint_chunk chunk;
Expand All @@ -507,13 +551,17 @@ namespace xcom
chunk.unknown_int2 = r.read_int();
chunk.checkpoints = read_checkpoint_table(r);
int32_t name_table_length = r.read_int();
assert(name_table_length == 0);
// assert(name_table_length == 0);
//TODO
if (name_table_length > 0) {
name_tables.push_back(read_name_table(r));
}
chunk.class_name = r.read_string();
chunk.actors = read_actor_table(r);
chunk.unknown_int3 = r.read_int();
// (only seems to be present for tactical saves?)
actor_template_table actor_templates = read_actor_template_table(r);
assert(actor_templates.size() == 0);
actor_templates.push_back(read_actor_template_table(r));
// assert(actor_templates.size() == 0);
chunk.display_name = r.read_string(); //unknown (game name)
chunk.map_name = r.read_string(); //unknown (map name)
chunk.unknown_int4 = r.read_int(); //unknown (checksum?)
Expand Down Expand Up @@ -643,7 +691,12 @@ namespace xcom

xcom_io rdr{ std::move(b) };
save.hdr = read_header(rdr);
xcom_io uncompressed(decompress(rdr));
buffer<unsigned char> uncompressed_buf = decompress(rdr);

FILE *fp = fopen("output.dat", "wb");
fwrite(uncompressed_buf.buf.get(), 1, uncompressed_buf.length, fp);
fclose(fp);
xcom_io uncompressed(std::move(uncompressed_buf));
save.actors = read_actor_table(uncompressed);
save.checkpoints = read_checkpoint_chunk_table(uncompressed);

Expand Down
15 changes: 13 additions & 2 deletions xcomwriter.cpp
Expand Up @@ -100,6 +100,12 @@ namespace xcom
io_.write_unicode_string(prop->str);
}

virtual void visit(name_property *prop) override
{
io_.write_string(prop->str);
io_.write_int(prop->number);
}

virtual void visit(object_property *prop) override
{
if (prop->actor == 0xffffffff) {
Expand All @@ -116,8 +122,13 @@ namespace xcom
{
io_.write_string(prop->type);
io_.write_int(0);
io_.write_string(prop->value.name);
io_.write_int(prop->value.number);
if (prop->type == "None") {
io_.write_byte(prop->value.number);
}
else {
io_.write_string(prop->value.name);
io_.write_int(prop->value.number);
}
}

virtual void visit(struct_property *prop) override
Expand Down

0 comments on commit 5198ca1

Please sign in to comment.