// Decode ExD from the raw data and create new ExD instance. public ExDFile(SqFile sqFile, ExHFile exHFile) { Header = exHFile; // Read the raw data from SqFile. byte[] data = sqFile.ReadData(); // Quit if no data is available. if (data == null || data.Length == 0) { return; } // Retrieve offset entries and total row sizes from the data header. int offsetEntriesSize = ToInt32(data, 0x8, true); int totalRowSize = ToInt32(data, 0xc, true); // Copy offset entries from raw data for easier manipulation. // Offset entries start from 0x20. byte[] offsetEntries = new byte[offsetEntriesSize]; Array.Copy(data, 0x20, offsetEntries, 0, offsetEntriesSize); // Copy all rows from raw data for easier manipulation. // Rows start right after offset entries. byte[] rawRows = new byte[totalRowSize]; Array.Copy(data, 0x20 + offsetEntriesSize, rawRows, 0, totalRowSize); // Going through each offset entry. // Offset entry size is 0x8. for (int i = 0; i < offsetEntriesSize; i += 0x8) { ExDRow row = new ExDRow(); row.Parent = this; // First 32 bits of the offset entry is the key for the row that we're reading. row.Key = ToInt32(offsetEntries, i, true); // Next 32 bits are the actual offset. row.Offset = ToInt32(offsetEntries, i + 0x4, true); // The offset includes the offset entries and 0x20 bytes for the raw header. // Actual position in the raw row byte array would be offset - raw header size - offset entries size. int rawRowPosition = row.Offset - 0x20 - offsetEntriesSize; // First 4 bytes in the raw row data is the size of the raw data. row.Size = ToInt32(rawRows, rawRowPosition, true); // Next 2 bytes are the check digit. row.CheckDigit = ToInt16(rawRows, rawRowPosition + 0x4, true); // After the size and the check digit, the raw data is actually divided into two sections. // First section is the fixed size data. // Copy it over to separate array for easier manipulation. byte[] fixedSizeData = new byte[exHFile.FixedSizeDataLength]; Array.Copy(rawRows, rawRowPosition + 0x6, fixedSizeData, 0, exHFile.FixedSizeDataLength); // Finally, rest of the raw row is the second section which contains the additional data that are used for some columns with varying lengths. (i.e. string type) byte[] additionalData = new byte[row.Size - exHFile.FixedSizeDataLength]; Array.Copy(rawRows, rawRowPosition + 0x6 + exHFile.FixedSizeDataLength, additionalData, 0, additionalData.Length); // Let's go through each column of this row now. foreach (ExHColumn column in exHFile.Columns) { ExDData parsedData = new ExDData(); switch (column.Type) { // String type. case 0x0: // The first 4 bytes of the fixed size data contain the starting offset for the additional data section. int valueStart = ToInt32(fixedSizeData, column.Offset, true); // The additional data is 0-terminated. int valueEnd = valueStart; while (valueEnd < additionalData.Length && additionalData[valueEnd] != 0) { valueEnd++; } // Copy the value from additional data to new byte array for decoding. byte[] field = new byte[valueEnd - valueStart]; Array.Copy(additionalData, valueStart, field, 0, field.Length); // Decode it as string. parsedData.Type = typeof(string); parsedData.Value = DecodeString(field); break; // Bool type. case 0x1: // The first byte of the fixed size data is the boolean value. parsedData.Type = typeof(bool); parsedData.Value = fixedSizeData[column.Offset] != 0; break; // Signed byte type. case 0x2: // The first byte of the fixed size data is the value. parsedData.Type = typeof(sbyte); parsedData.Value = (sbyte)fixedSizeData[column.Offset]; break; // Byte type. case 0x3: // The first byte of the fixed size data is the value. parsedData.Type = typeof(byte); parsedData.Value = fixedSizeData[column.Offset]; break; // Short type. case 0x4: // The first 2 bytes of the fixed size data are the value. parsedData.Type = typeof(short); parsedData.Value = ToInt16(fixedSizeData, column.Offset, true); break; // Ushort type. case 0x5: // The first 2 bytes of the fixed size data are the value. parsedData.Type = typeof(ushort); parsedData.Value = ToUInt16(fixedSizeData, column.Offset, true); break; // Int type. case 0x6: // The first 4 bytes of the fixed size data are the value. parsedData.Type = typeof(int); parsedData.Value = ToInt32(fixedSizeData, column.Offset, true); break; // Uint type. case 0x7: // The first 4 bytes of the fixed size data are the value. parsedData.Type = typeof(uint); parsedData.Value = ToUInt32(fixedSizeData, column.Offset, true); break; // Float type. case 0x9: // The first 4 bytes of the fixed size data are the value. parsedData.Type = typeof(float); parsedData.Value = ToSingle(fixedSizeData, column.Offset, true); break; // Long type. case 0xb: // The first 8 bytes of the fixed size data are the value. parsedData.Type = typeof(long); parsedData.Value = ToInt64(fixedSizeData, column.Offset, true); break; // Masked boolean types. // The first byte of the fixed size data contains the boolean value, where the bit offset is type value - 0x19 from right. case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20: parsedData.Type = typeof(bool); parsedData.Value = (fixedSizeData[column.Offset] & (0x1 << (column.Type - 0x19))) != 0; break; // Unrecognized type. default: throw new Exception($"Unrecognized column type: 0x{column.Type.ToString("x")}"); } row.Data.Add(column, parsedData); } Rows.Add(row); } }
// Decode ExH from the raw data and create new ExH instance. public ExHFile(SqFile sqFile) { // Read the SqFile raw data. byte[] data = sqFile.ReadData(); // Quit if no data is available. if (data == null || data.Length == 0) { return; } // Some big endian values from fixed offset. FixedSizeDataLength = ToUInt16(data, 0x6, true); Variant = ToUInt16(data, 0x10, true); // Counts for various entries. ushort columnCount = ToUInt16(data, 0x8, true); ushort rangeCount = ToUInt16(data, 0xa, true); ushort langCount = ToUInt16(data, 0xc, true); // Only care about ExD data table type headers. if (Variant != 1) { return; } Columns = new ExHColumn[columnCount]; for (int i = 0; i < columnCount; i++) { // Column offsets start after the fixed offset values (0x20). // Each column offset is 4 bytes long. int columnOffset = 0x20 + i * 0x4; Columns[i] = new ExHColumn() { Type = ToUInt16(data, columnOffset, true), Offset = ToUInt16(data, columnOffset + 0x2, true) }; } Ranges = new ExHRange[rangeCount]; for (int i = 0; i < rangeCount; i++) { // Range offsets start after the column offsets. // Each range offset is 8 bytes long. int rangeOffset = (0x20 + columnCount * 0x4) + i * 0x8; Ranges[i] = new ExHRange() { Start = ToInt32(data, rangeOffset, true), Length = ToInt32(data, rangeOffset + 0x4, true) }; } Languages = new ExHLanguage[langCount]; for (int i = 0; i < langCount; i++) { // Language offsets start after the range offsets. // Each language offset is 2 bytes long. int langOffset = ((0x20 + columnCount * 0x4) + rangeCount * 0x8) + i * 0x2; Languages[i] = new ExHLanguage() { Value = data[langOffset] }; } }