using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Text; using SanAndreasUnity.Utilities; namespace SanAndreasUnity.Importing.GXT { public class GXT : IDisposable { #region LoadPath private static string _language= "american"; public static string Language { get => _language; set { var path = GetPathFromLanguage(value); if (File.Exists(path)) { _language = value; } } } public static string GXTDir => Path.Combine(Config.GamePath, "text"); private static string GetPathFromLanguage(string language) => Path.Combine(GXTDir, $"{language}.gxt"); private static string LoadPath => GetPathFromLanguage(_language); #endregion #region GXTData private Int16 _version; public List SubTableNames { get; } = new List(); public Dictionary> TableEntryNameDict { get; } = new Dictionary>(); public Dictionary EntryNameWordDict { get; } = new Dictionary(); private static GXT _current; public static GXT Current { get { return _current; } } private MemoryStream _rawData; #endregion public GXT(string fp) { var bytes = File.ReadAllBytes(LoadPath); _rawData = new MemoryStream(bytes, false); } public static void Load() { if (_current != null) { return; } _current = new GXT(LoadPath); _current.InternalLoad(); _current._rawData.Dispose(); } private void InternalLoad() { var encoding = LoadHeader(); var tkeyEntryOffsets = LoadTableBlock(encoding); for (var i = 0; i < SubTableNames.Count; i++) { var subTableName = SubTableNames[i]; _rawData.Seek(tkeyEntryOffsets[i], SeekOrigin.Begin); using (var binReader = new BinaryReader(_rawData, encoding, true)) { if (i == 0) //parse tkey directly { LoadTKEY(binReader, subTableName); } else //skip tkeyname { _rawData.Seek(8, SeekOrigin.Current); LoadTKEY(binReader, subTableName); } } } } private void LoadTKEY(BinaryReader binaryReader, string tableName) { //Console.WriteLine(nameof(LoadTKEY)); var tkey = new string(binaryReader.ReadChars(4)); var blockSize = binaryReader.ReadInt32(); //Console.WriteLine($"tkey:{tkey} blockSize:{blockSize}"); if (tkey != "TKEY") { throw new ConstraintException(); } var entryOffsets = new List(); var tdataEntryCount = blockSize / 8; var entryNames = new List(); for (int i = 0; i < tdataEntryCount; i++) { var entryOffset = binaryReader.ReadInt32(); var crc32 = binaryReader.ReadInt32(); entryNames.Add(crc32); entryOffsets.Add(entryOffset); } TableEntryNameDict[tableName] = entryNames; //read tdat var tdat = new string(binaryReader.ReadChars(4)); if (tdat != "TDAT") { throw new ConstraintException(); } var tdatBlockSize = binaryReader.ReadInt32(); var bytes = binaryReader.ReadBytes(tdatBlockSize); var wordBytes = new List(); for (int i = 0; i < entryOffsets.Count; i++) { var entryName = entryNames[i]; //todo this should be double checked; I think it could be used a lib method. //but not familiar with relevant api. for (int j = entryOffsets[i]; ; j++) { var c = Convert.ToChar(bytes[j]); if (c == '\0') { break; } wordBytes.Add(bytes[j]); } EntryNameWordDict[entryName] = Win1252ToString(wordBytes.ToArray()); wordBytes.Clear(); } } private string Win1252ToString(byte[] bytes) { Encoding win1252 = Encoding.GetEncoding(1252); var utf8bytes = Encoding.Convert(win1252, Encoding.UTF8, bytes); return Encoding.UTF8.GetString(utf8bytes); } private Encoding LoadHeader() { using (var binReader = new BinaryReader(_rawData, Encoding.ASCII, true)) { _version = binReader.ReadInt16(); var bitsPerChar = binReader.ReadInt16(); if (bitsPerChar == 8) { return Encoding.ASCII; } else if (bitsPerChar == 16) { return Encoding.Unicode; } } return Encoding.ASCII; } private List LoadTableBlock(Encoding encoding) { using (var binReader = new BinaryReader(_rawData, encoding, true)) { var offsetList = new List(); var tablConst = binReader.ReadChars(4); if (new string(tablConst) != "TABL") { throw new ConstraintException(); } var blockSize = binReader.ReadInt32(); var entryCount = blockSize / 12; for (int i = 0; i < entryCount; i++) { var subtableName = new string(binReader.ReadChars(8)); var offset = binReader.ReadInt32(); //Console.WriteLine($"subtablename:{subtableName} offset:{offset}"); SubTableNames.Add(subtableName); offsetList.Add(offset); } return offsetList; } } public void Dispose() { if (_rawData != null) { _rawData.Dispose(); _rawData = null; } } } }