mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-01-22 17:12:25 -05:00
WIP on rewriting .dat java deserialiser yet again
This commit is contained in:
parent
fdfd521676
commit
6f3bd4f3f5
2 changed files with 237 additions and 96 deletions
50
src/Errors.h
50
src/Errors.h
|
@ -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
|
||||
|
|
283
src/Formats.c
283
src/Formats.c
|
@ -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")) {
|
||||
|
|
Loading…
Reference in a new issue