WIP on rewriting .dat java deserialiser yet again

This commit is contained in:
UnknownShadow200 2021-11-28 13:25:41 +11:00
parent fdfd521676
commit 6f3bd4f3f5
2 changed files with 237 additions and 96 deletions

View file

@ -82,34 +82,36 @@ enum CC_ERRORS {
DAT_ERR_VERSION = 0xCCDED03FUL, /* DAT stream byte #5 isn't 2 */
DAT_ERR_JIDENTIFIER = 0xCCDED040UL, /* DAT stream bytes #6-#7 aren't 0xACED */
DAT_ERR_JVERSION = 0xCCDED041UL, /* DAT stream bytes #8-#9 aren't 0x0005 */
DAT_ERR_ROOT_TYPE = 0xCCDED042UL, /* Java root element type isn't Object */
DAT_ERR_ROOT_OBJECT = 0xCCDED042UL, /* DAT version 2 root value isn't an object */
JAVA_ERR_JSTRING_LEN = 0xCCDED043UL, /* Java string is too long */
JAVA_ERR_JFIELD_CLASS_NAME = 0xCCDED044UL, /* Java field classname type is invalid */
JAVA_ERR_JCLASS_TYPE = 0xCCDED045UL, /* Java class classdesc type is invalid */
JAVA_ERR_JCLASS_FIELDS = 0xCCDED046UL, /* Java class classdesc has too many fields */
JAVA_ERR_JCLASS_ANNOTATION = 0xCCDED047UL, /* Java classdesc uses unsupported annotations */
JAVA_ERR_JOBJECT_TYPE = 0xCCDED048UL, /* Java object field data type is invalid */
JAVA_ERR_JARRAY_TYPE = 0xCCDED049UL, /* Java array field data type is invalid */
JAVA_ERR_JARRAY_CONTENT = 0xCCDED04AUL, /* Java array field isn't a byte array */
JAVA_ERR_INVALID_TYPECODE = 0xCCDED043UL, /* Typecode is invalid or incorrect */
JAVA_ERR_JSTRING_LEN = 0xCCDED044UL, /* String length is too long */
JAVA_ERR_JFIELD_CLASS_NAME = 0xCCDED045UL, /* Field classname type is invalid */
JAVA_ERR_JCLASS_TYPE = 0xCCDED046UL, /* ClassDescriptor type is invalid */
JAVA_ERR_JCLASS_FIELDS = 0xCCDED047UL, /* ClassDescriptor has too many fields */
JAVA_ERR_JCLASS_ANNOTATION = 0xCCDED048UL, /* ClassDescriptor uses unsupported annotations */
JAVA_ERR_JCLASSES_COUNT = 0xCCDED049UL, /* Too many ClassDescriptors in stream */
JAVA_ERR_JCLASS_REFERENCE = 0xCCDED04AUL, /* Reference refers to non-existent ClassDescriptor */
JAVA_ERR_JOBJECT_FLAGS = 0xCCDED04BUL, /* Object class isn't deserialisable */
JAVA_ERR_JVALUE_TYPE = 0xCCDED04CUL, /* Value data type is invalid */
NBT_ERR_UNKNOWN = 0xCCDED04BUL, /* NBT tag has an unknown type */
CW_ERR_ROOT_TAG = 0xCCDED04CUL, /* NBT root tag isn't a Compound tag */
CW_ERR_STRING_LEN = 0xCCDED04DUL, /* NBT string is too long */
CW_ERR_UUID_LEN = 0xCCDED04EUL, /* Map UUID byte array length is not 16 */
NBT_ERR_UNKNOWN = 0xCCDED050UL, /* NBT tag has an unknown type */
CW_ERR_ROOT_TAG = 0xCCDED051UL, /* NBT root tag isn't a Compound tag */
CW_ERR_STRING_LEN = 0xCCDED052UL, /* NBT string is too long */
CW_ERR_UUID_LEN = 0xCCDED053UL, /* Map UUID byte array length is not 16 */
AL_ERR_INIT_DEVICE = 0xCCDED04FUL, /* Unknown error occurred creating OpenAL device */
AL_ERR_INIT_CONTEXT = 0xCCDED050UL, /* Unknown error occurred creating OpenAL context */
AL_ERR_INIT_DEVICE = 0xCCDED054UL, /* Unknown error occurred creating OpenAL device */
AL_ERR_INIT_CONTEXT = 0xCCDED055UL, /* Unknown error occurred creating OpenAL context */
INF_ERR_BLOCKTYPE = 0xCCDED051UL, /* Block has invalid block type */
INF_ERR_LEN_VERIFY = 0xCCDED052UL, /* Block length checksum failed */
INF_ERR_REPEAT_BEG = 0xCCDED053UL, /* Attempted to repeat codewords before first code */
INF_ERR_REPEAT_END = 0xCCDED054UL, /* Attempted to repeat codewords after last code */
INF_ERR_INVALID_CODE = 0xCCDED055UL, /* Attempted to decode unknown codeword */
INF_ERR_NUM_CODES = 0xCCDED056UL, /* Too many codewords specified for bit length */
INF_ERR_BLOCKTYPE = 0xCCDED056UL, /* Block has invalid block type */
INF_ERR_LEN_VERIFY = 0xCCDED057UL, /* Block length checksum failed */
INF_ERR_REPEAT_BEG = 0xCCDED058UL, /* Attempted to repeat codewords before first code */
INF_ERR_REPEAT_END = 0xCCDED059UL, /* Attempted to repeat codewords after last code */
INF_ERR_INVALID_CODE = 0xCCDED05AUL, /* Attempted to decode unknown codeword */
INF_ERR_NUM_CODES = 0xCCDED05BUL, /* Too many codewords specified for bit length */
ERR_DOWNLOAD_INVALID = 0xCCDED057UL, /* Unspecified error occurred downloading data */
ERR_NO_AUDIO_OUTPUT = 0xCCDED058UL, /* No audio output devices are connected */
ERR_INVALID_URL = 0xCCDED059UL /* Invalid URL provided to download from */
ERR_DOWNLOAD_INVALID = 0xCCDED05CUL, /* Unspecified error occurred downloading data */
ERR_NO_AUDIO_OUTPUT = 0xCCDED05DUL, /* No audio output devices are connected */
ERR_INVALID_URL = 0xCCDED05EUL /* Invalid URL provided to download from */
};
#endif

View file

@ -746,22 +746,50 @@ enum JFieldType {
};
#define JNAME_SIZE 48
static cc_uint32 reference_id;
#define SC_WRITE_METHOD 0x0
#define SC_SERIALIZABLE 0x02
union JValue {
cc_uint8 U8;
cc_int32 I32;
cc_uint32 U32;
float F32;
struct { cc_uint8* Ptr; cc_uint32 Size; } Array;
};
struct JFieldDesc {
cc_uint8 Type;
cc_uint8 FieldName[JNAME_SIZE];
union {
cc_uint8 U8;
cc_int32 I32;
cc_uint32 U32;
float F32;
struct { cc_uint8* Ptr; cc_uint32 Size; } Array;
} Value;
union JValue Value;
/* "Value" field here is not accurate to how java deserialising actually works, */
/* but easier to store here since only care about Level class values anyways */
};
struct JClassDesc;
struct JClassDesc {
cc_uint8 ClassName[JNAME_SIZE];
cc_uint8 Flags;
int FieldsCount;
struct JFieldDesc Fields[22];
cc_uint32 Reference;
struct JClassDesc* SuperClass;
struct JClassDesc* tmp;
};
struct JArray {
struct JClassDesc* Desc;
cc_uint8* Data; /* for byte arrays */
cc_uint32 Size; /* for byte arrays */
};
struct JUnion {
cc_uint8 Type;
union {
cc_uint8 String[JNAME_SIZE]; /* TC_STRING */
struct JClassDesc* Object; /* TC_OBJECT */
struct JArray Array; /* TC_ARRAY */
} Value;
};
static cc_result Java_ReadString(struct Stream* stream, cc_uint8* buffer) {
@ -776,6 +804,23 @@ static cc_result Java_ReadString(struct Stream* stream, cc_uint8* buffer) {
return Stream_Read(stream, buffer, len);
}
static cc_result Java_SkipAnnotation(struct Stream* stream) {
cc_uint8 typeCode;
cc_result res;
if ((res = stream->ReadU8(stream, &typeCode))) return res;
if (typeCode != TC_ENDBLOCKDATA) return JAVA_ERR_JCLASS_ANNOTATION;
return 0;
}
/* .dat files only seem to use at most 16 different class types */
#define CLASS_CAPACITY 20
static struct JClassDesc class_empty;
static struct JClassDesc* class_cache;
static int class_count;
static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc** desc);
static cc_result Java_ReadFieldDesc(struct Stream* stream, struct JFieldDesc* desc) {
cc_uint8 typeCode;
cc_uint8 className1[JNAME_SIZE];
@ -798,19 +843,14 @@ static cc_result Java_ReadFieldDesc(struct Stream* stream, struct JFieldDesc* de
return 0;
}
static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc* desc) {
cc_uint8 typeCode;
static cc_result Java_ReadNewClassDesc(struct Stream* stream, struct JClassDesc* desc) {
cc_uint8 count[2];
struct JClassDesc superClassDesc;
cc_result res;
int i;
if ((res = stream->ReadU8(stream, &typeCode))) return res;
if (typeCode == TC_NULL) { desc->ClassName[0] = '\0'; desc->FieldsCount = 0; return 0; }
if (typeCode != TC_CLASSDESC) return JAVA_ERR_JCLASS_TYPE;
if ((res = Java_ReadString(stream, desc->ClassName))) return res;
if ((res = stream->Skip(stream, 9))) return res; /* (8) serial version UID, (1) flags */
if ((res = stream->Skip(stream, 8))) return res; /* serial version UID */
if ((res = stream->ReadU8(stream, &desc->Flags))) return res;
if ((res = Stream_Read(stream, count, 2))) return res;
desc->FieldsCount = Stream_GetU16_BE(count);
@ -820,75 +860,166 @@ static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc* de
if ((res = Java_ReadFieldDesc(stream, &desc->Fields[i]))) return res;
}
if ((res = stream->ReadU8(stream, &typeCode))) return res;
if (typeCode != TC_ENDBLOCKDATA) return JAVA_ERR_JCLASS_ANNOTATION;
return Java_ReadClassDesc(stream, &superClassDesc);
if ((res = Java_SkipAnnotation(stream))) return res;
return Java_ReadClassDesc(stream, &desc->SuperClass);
}
static cc_result Java_ReadFieldData(struct Stream* stream, struct JFieldDesc* field) {
static cc_result Java_ReadClassDesc(struct Stream* stream, struct JClassDesc** desc) {
cc_uint8 typeCode;
cc_string fieldName;
cc_uint32 count;
struct JClassDesc arrayClassDesc;
cc_uint32 reference;
cc_result res;
int i;
if ((res = stream->ReadU8(stream, &typeCode))) return res;
switch (typeCode)
{
case TC_NULL:
*desc = &class_empty;
return 0;
case TC_REFERENCE:
if ((res = Stream_ReadU32_BE(stream, &reference))) return res;
/* Use a previously defined ClassDescriptor */
for (i = 0; i < class_count; i++)
{
if (class_cache[i].Reference != reference) continue;
*desc = &class_cache[i];
return 0;
}
return JAVA_ERR_JCLASS_REFERENCE;
case TC_CLASSDESC:
if (class_count >= CLASS_CAPACITY) return JAVA_ERR_JCLASSES_COUNT;
*desc = &class_cache[class_count++];
return Java_ReadNewClassDesc(stream, *desc);
}
return JAVA_ERR_JCLASS_TYPE;
}
static cc_result Java_ReadObject(struct Stream* stream, struct JUnion* object);
static cc_result Java_ReadValue(struct Stream* stream, cc_uint8 type, union JValue* value) {
struct JUnion obj;
cc_result res;
switch (field->Type) {
switch (type) {
case JFIELD_I8:
case JFIELD_BOOL:
return stream->ReadU8(stream, &field->Value.U8);
return stream->ReadU8(stream, &value->U8);
case JFIELD_F32:
case JFIELD_I32:
return Stream_ReadU32_BE(stream, &field->Value.U32);
return Stream_ReadU32_BE(stream, &value->U32);
case JFIELD_I64:
return stream->Skip(stream, 8); /* (8) data */
case JFIELD_OBJECT:
return Java_ReadObject(stream, &obj);
case JFIELD_OBJECT: {
/* Luckily for us, we only have to account for blockMap object */
/* Other objects (e.g. player) are stored after the fields we actually care about, so ignore them */
fieldName = String_FromRaw((char*)field->FieldName, JNAME_SIZE);
if (!String_CaselessEqualsConst(&fieldName, "blockMap")) return 0;
if ((res = stream->ReadU8(stream, &typeCode))) return res;
case JFIELD_ARRAY:
if ((res = Java_ReadObject(stream, &obj))) return res;
value->Array.Size = 0;
value->Array.Ptr = NULL;
/* Skip all blockMap data with awful hacks */
/* These offsets were based on server_level.dat map from original minecraft classic server */
if (typeCode == TC_OBJECT) {
if ((res = stream->Skip(stream, 315))) return res;
if ((res = Stream_ReadU32_BE(stream, &count))) return res;
if ((res = stream->Skip(stream, 17 * count))) return res;
if ((res = stream->Skip(stream, 152))) return res;
} else if (typeCode != TC_NULL) {
/* WoM maps have this field as null, which makes things easier for us */
return JAVA_ERR_JOBJECT_TYPE;
/* Array is a byte array */
/* NOTE: This can technically leak memory if this array is discarded, however */
/* so far the only observed byte array in .dat files is the map blocks anyways */
if (obj.Type == TC_ARRAY && obj.Value.Array.Desc->ClassName[1] == JFIELD_I8) {
value->Array.Size = obj.Value.Array.Size;
value->Array.Ptr = obj.Value.Array.Data;
}
} break;
return 0;
}
return JAVA_ERR_JVALUE_TYPE;
}
case JFIELD_ARRAY: {
if ((res = stream->ReadU8(stream, &typeCode))) return res;
/* NULL/empty array */
if (typeCode == TC_NULL) {
field->Value.Array.Size = 0;
field->Value.Array.Ptr = NULL;
break;
}
static cc_result Java_ReadClassData(struct Stream* stream, struct JClassDesc* desc) {
struct JFieldDesc* field;
cc_result res;
int i;
if (typeCode != TC_ARRAY) return JAVA_ERR_JARRAY_TYPE;
if ((res = Java_ReadClassDesc(stream, &arrayClassDesc))) return res;
if (arrayClassDesc.ClassName[1] != JFIELD_I8) return JAVA_ERR_JARRAY_CONTENT;
if (!(desc->Flags & SC_SERIALIZABLE))
return JAVA_ERR_JOBJECT_FLAGS;
if ((res = Stream_ReadU32_BE(stream, &count))) return res;
field->Value.Array.Size = count;
field->Value.Array.Ptr = (cc_uint8*)Mem_TryAlloc(count, 1);
for (i = 0; i < desc->FieldsCount; i++)
{
field = &desc->Fields[i];
if ((res = Java_ReadValue(stream, field->Type, &field->Value))) return res;
}
if (!field->Value.Array.Ptr) return ERR_OUT_OF_MEMORY;
res = Stream_Read(stream, field->Value.Array.Ptr, count);
if (res) { Mem_Free(field->Value.Array.Ptr); return res; }
} break;
if (desc->Flags & SC_WRITE_METHOD)
return Java_SkipAnnotation(stream);
return 0;
}
static cc_result Java_ReadNewObject(struct Stream* stream, struct JUnion* object) {
struct JClassDesc* head;
cc_result res;
if ((res = Java_ReadClassDesc(stream, &object->Value.Object))) return res;
/* Linked list of classes, with most superclass fist as head */
head = object->Value.Object; head->tmp = NULL;
while (head->SuperClass) {
head->SuperClass->tmp = head;
head = head->SuperClass;
}
/* Class data is read with most superclass first */
while (head) {
if ((res = Java_ReadClassData(stream, head))) return res;
head = head->tmp;
}
return 0;
}
static cc_result Java_ReadNewArray(struct Stream* stream, struct JUnion* object) {
struct JArray* array = &object->Value.Array;
union JValue value;
cc_uint32 count;
cc_uint8 type;
cc_result res;
int i;
if ((res = Java_ReadClassDesc(stream, &array->Desc))) return res;
if ((res = Stream_ReadU32_BE(stream, &count))) return res;
type = array->Desc->ClassName[1];
if (type != JFIELD_I8) {
/* Not a byte array, so just discard the unnecessary values */
for (i = 0; i < count; i++)
{
if ((res = Java_ReadValue(stream, type, &value))) return res;
}
return 0;
}
if ((res = Stream_ReadU32_BE(stream, &count))) return res;
array->Size = count;
array->Data = (cc_uint8*)Mem_TryAlloc(count, 1);
if (!array->Data) return ERR_OUT_OF_MEMORY;
res = Stream_Read(stream, array->Data, count);
if (res) { Mem_Free(array->Data); }
return res;
}
static cc_result Java_ReadObject(struct Stream* stream, struct JUnion* object) {
cc_uint32 reference;
cc_result res;
if ((res = stream->ReadU8(stream, &object->Type))) return res;
switch (object->Type)
{
case TC_STRING: return Java_ReadString(stream, object->Value.String);
case TC_NULL: return 0;
case TC_REFERENCE: return Stream_ReadU32_BE(stream, &reference);
case TC_OBJECT: return Java_ReadNewObject(stream, object);
case TC_ARRAY: return Java_ReadNewArray(stream, object);
}
return JAVA_ERR_INVALID_TYPECODE;
}
static int Java_I32(struct JFieldDesc* field) {
if (field->Type != JFIELD_I32) Logger_Abort("Field type must be Int32");
return field->Value.I32;
@ -965,23 +1096,31 @@ static cc_result Dat_LoadFormat1(struct Stream* stream) {
static cc_result Dat_LoadFormat2(struct Stream* stream) {
struct LocalPlayer* p = &LocalPlayer_Instance;
cc_uint8 header[2 + 2 + 1];
struct JClassDesc obj;
struct JClassDesc classes[CLASS_CAPACITY];
cc_uint8 header[2 + 2];
struct JUnion obj;
struct JClassDesc* desc;
struct JFieldDesc* field;
cc_string fieldName;
cc_result res;
int i;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
/* Java seralisation headers */
if (Stream_GetU16_BE(header + 0) != 0xACED) return DAT_ERR_JIDENTIFIER;
if (Stream_GetU16_BE(header + 2) != 0x0005) return DAT_ERR_JVERSION;
if (header[4] != TC_OBJECT) return DAT_ERR_ROOT_TYPE;
if ((res = Java_ReadClassDesc(stream, &obj))) return res;
/* Reset state for Java Serialisation */
class_cache = classes;
class_count = 0;
reference_id = 0x7E0000;
for (i = 0; i < obj.FieldsCount; i++) {
field = &obj.Fields[i];
if ((res = Java_ReadFieldData(stream, field))) return res;
/* Java seralisation headers */
if (Stream_GetU16_BE(header + 0) != 0xACED) return DAT_ERR_JIDENTIFIER;
if (Stream_GetU16_BE(header + 2) != 0x0005) return DAT_ERR_JVERSION;
if ((res = Java_ReadObject(stream, &obj))) return res;
if (obj.Type != TC_OBJECT) return DAT_ERR_ROOT_OBJECT;
desc = obj.Value.Object;
for (i = 0; i < desc->FieldsCount; i++) {
field = &desc->Fields[i];
fieldName = String_FromRaw((char*)field->FieldName, JNAME_SIZE);
if (String_CaselessEqualsConst(&fieldName, "width")) {