diff --git a/toolnfs/NFSLocaleTool.sln b/toolnfs/NFSLocaleTool.sln
new file mode 100644
index 0000000..b642f87
--- /dev/null
+++ b/toolnfs/NFSLocaleTool.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33213.308
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NFSLocaleTool", "NFSLocaleTool\NFSLocaleTool.csproj", "{721F5EEC-8CF1-4FA1-99A6-88506B5B42C5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {721F5EEC-8CF1-4FA1-99A6-88506B5B42C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {721F5EEC-8CF1-4FA1-99A6-88506B5B42C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {721F5EEC-8CF1-4FA1-99A6-88506B5B42C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {721F5EEC-8CF1-4FA1-99A6-88506B5B42C5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F1CC37B3-9FBC-4792-A463-66C82EA2C007}
+ EndGlobalSection
+EndGlobal
diff --git a/toolnfs/NFSLocaleTool/App.config b/toolnfs/NFSLocaleTool/App.config
new file mode 100644
index 0000000..3916e0e
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/toolnfs/NFSLocaleTool/NFSLocale.cs b/toolnfs/NFSLocaleTool/NFSLocale.cs
new file mode 100644
index 0000000..1cb623d
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/NFSLocale.cs
@@ -0,0 +1,312 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace NFSLocaleTool
+{
+ internal class NFSLocale
+ {
+ //all offsets without 8 bytes
+ public uint Signature = 0x039000;
+ public UInt16[] ArrayToHistogram { get; set; }
+ public string[] DefArrayFile { get; set; }
+ public int DefLengthFile { get; set; }
+ public byte[] DefArrayFromStrings { get; set; }
+ public UInt16[] ArrayOriginList { get; set; }
+ public UInt16[] ArrayCharsList { get; set; }
+ public int FileSize { get; set; }
+ public int EntriesNum { get; set; }
+ public int TableOffset { get; set; }
+ public int DataOffset { get; set; }
+ public int DataOffSize { get; set; }
+ public string Type { get; set; }
+ List Entries { get; set; }
+ List ToEndFile { get; set; }
+ public void ExtractText(string outputFile)
+ {
+ List text = new List();
+ foreach (Entry entry in Entries)
+ {
+ text.Add($"{entry.String.Replace("\n", "¬")}");
+ }
+ File.WriteAllLines(outputFile, text.ToArray());
+ }
+
+ public void WriteFromText(string textFile, string hgfile, string outputChunk, string idsFile)
+ {
+ HistogramReadChars(hgfile);
+ DefaultListChars();
+
+ string[] text = File.ReadAllLines(textFile);
+ string[] ids_ = File.ReadAllLines(idsFile);
+ if(text.Length != ids_.Length)
+ {
+ Console.WriteLine($"Несоответствие кол-ва строк в файле {Path.GetFileName(textFile)} к ID в файле {Path.GetFileName(idsFile)}");
+ Console.WriteLine($"{text.Length}/{ids_.Length}");
+ return;
+ }
+ uint[] ids = new uint[text.Length];
+ uint[] offsets = new uint[text.Length];
+ for(int i = 0; i < ids_.Length; i++)
+ {
+ ids[i] = UInt32.Parse(ids_[i], System.Globalization.NumberStyles.HexNumber);
+ }
+ using(BinaryWriter writer = new BinaryWriter(File.Create(outputChunk)))
+ {
+ writer.Write(Signature);
+ writer.Write(0); //skip filesize
+ writer.Write(text.Length); //listSize
+ writer.Write(140); //table offset dataOffset
+ int textStart = (text.Length * 8) + 0x8C;//calculate text offset stringsOffset 8-9
+ writer.Write(textStart);
+ writer.Write(Encoding.UTF8.GetBytes("Default"));
+
+ writer.BaseStream.Position = textStart + 8; // start text
+ for(int i = 0; i < text.Length; i++)
+ {
+ offsets[i] = (uint)((writer.BaseStream.Position - textStart) - 8);
+ writer.Write(NFSEncoder(text[i].Replace("\"\"", "\"")));
+ writer.Write(new byte()); //null term
+ }
+ writer.BaseStream.Position = 0x94;
+ for (int i = 0; i < text.Length; i++)
+ {
+ writer.Write(ids[i]);
+ writer.Write(offsets[i]);
+ }
+
+ writer.BaseStream.Position = 4;
+ writer.Write((uint)(writer.BaseStream.Length - 8));
+ }
+ }
+
+ public void CreateListChars(string outputfile)
+ {
+ byte[] defList = Resource1.charslist;
+ UInt16[] defListUI = new UInt16[(defList.Length)/2];
+ using (BinaryWriter writer = new BinaryWriter(File.Create(outputfile)))
+ {
+ for (int i = 0; i < defList.Length; i+=2)
+ {
+ ushort _a = (ushort)((defList[i]) | (defList[i + 1]) << 8);
+ defListUI[i/2] = _a;
+ writer.Write(defListUI[i/2]);
+ }
+ }
+ }
+
+ public void HistogramWrite(string hgfile, string file, string outputfile)
+ {
+ HistogramReadChars(file);
+ DefaultListChars();
+
+ int countDefList = File.ReadAllLines(hgfile).Length;
+
+ ArrayCharsList = new UInt16[DataOffSize/2];
+ using (BinaryReader charsReader = new BinaryReader(File.OpenRead(hgfile)))
+ {
+ int countArrayCharsList = 0;
+ UInt16 tempInt;
+ for (int i = 1; i < (countDefList/3); i++)
+ {
+ tempInt = charsReader.ReadUInt16();
+ if ((tempInt != 10) & (tempInt != 13))
+ {
+ ArrayCharsList[countArrayCharsList] = tempInt;
+ countArrayCharsList++;
+ }
+ }
+ }
+
+ //256 dataoffsize
+ UInt16[] arrayRemoveSimilar = new UInt16[DataOffSize/2];
+ int countRemove = 0;
+ for (var i = 0; i < ArrayCharsList.Length; i++)
+ {
+ if (ArrayOriginList.Contains(ArrayCharsList[i]))
+ {
+ continue;
+ }
+ else
+ {
+ arrayRemoveSimilar[countRemove] = ArrayCharsList[i];
+ countRemove++;
+ }
+ }
+
+ ArrayToHistogram = new UInt16[DataOffSize/2];
+ int countToHistogram = 0;
+ countRemove = 0;
+ UInt16 zero = 0;
+ for (var i = 0; i < ArrayOriginList.Length; i++)
+ {
+ if (ArrayOriginList[i] == zero)
+ {
+ if (countRemove < arrayRemoveSimilar.Length)
+ {
+ ArrayToHistogram[countToHistogram] = arrayRemoveSimilar[countRemove];
+ countToHistogram++;
+ countRemove++;
+ }
+ }
+ else
+ {
+ ArrayToHistogram[countToHistogram] = ArrayOriginList[i];
+ countToHistogram++;
+ }
+ }
+
+ using (BinaryWriter hwriter = new BinaryWriter(File.Create(outputfile)))
+ {
+ hwriter.Write(Signature + 1);
+ hwriter.Write(DataOffset);
+ hwriter.Write(DataOffSize);
+ hwriter.BaseStream.Position = 0x10c;
+ foreach (UInt16 val in ArrayToHistogram)
+ {
+ hwriter.Write(val);
+ }
+ var hisEndId = from p in ToEndFile select p.Id;
+ foreach (UInt16 val in hisEndId)
+ {
+ hwriter.Write(val);
+ }
+ }
+ }
+
+
+ public void DefaultListChars()
+ {
+ string defList = NFSLocaleTool.Resource1.default_list_chunk_number;
+ DefArrayFile = defList.Split('\n');
+ DefLengthFile = DefArrayFile.Length;
+
+ DefArrayFromStrings = new byte[DefLengthFile];
+ for (int i = 0; i < DefLengthFile; i++)
+ {
+ DefArrayFromStrings[i] = byte.Parse(DefArrayFile[i], System.Globalization.NumberStyles.HexNumber);
+ }
+ }
+
+
+ public void HistogramReadChars(string file)
+ {
+ using (BinaryReader hCharReader = new BinaryReader(File.OpenRead(file)))
+ {
+ if (hCharReader.ReadUInt32() != Signature + 1)
+ throw new Exception("Unknown file");
+ DataOffset = hCharReader.ReadInt32(); //270600
+ DataOffSize = hCharReader.ReadInt32(); //256
+
+ hCharReader.BaseStream.Position = 0x10c;
+
+ UInt16 tempOriginListReader;
+ ArrayOriginList = new UInt16[DataOffSize/2];
+ for (int i = 0; i < (DataOffSize/2); i++)
+ {
+ tempOriginListReader = hCharReader.ReadUInt16();
+ ArrayOriginList[i] = tempOriginListReader;
+ }
+
+ ToEndFile = new List();
+ for (int i = 0; i < (DataOffset - (DataOffSize + 260)) / 2; i++)
+ {
+ ToEndFile.Add(new Entry()
+ {
+ Id = hCharReader.ReadUInt16()
+ });
+ }
+
+ }
+ }
+
+
+ public void Read(string file, string hgfile)
+ {
+ HistogramReadChars(hgfile);
+ DefaultListChars();
+
+ using (BinaryReader reader = new BinaryReader(File.OpenRead(file)))
+ {
+ if (reader.ReadUInt32() != Signature)
+ throw new Exception("Unknown file");
+ FileSize = reader.ReadInt32();
+ EntriesNum = reader.ReadInt32();
+ TableOffset = reader.ReadInt32();
+ DataOffset = reader.ReadInt32();
+ Type = Utils.ReadString(reader, Encoding.UTF8);
+
+ Entries = new List();
+ reader.BaseStream.Position = TableOffset + 8;
+ for(int i = 0; i < EntriesNum; i++)
+ {
+ Entries.Add(new Entry()
+ {
+ Id = reader.ReadUInt32(),
+ Offset = reader.ReadInt32() + DataOffset + 8
+ });
+ }
+ for(int i = 0; i < EntriesNum; i++)
+ {
+ Entry entry = Entries[i];
+ reader.BaseStream.Position = entry.Offset;
+ entry.String = NFSDecoder(Utils.ReadNullTerminatedArray(reader));
+ Entries[i] = entry;
+ }
+
+ File.WriteAllLines(file + ".ids", Entries.Select(i => i.Id.ToString("X8")));
+ }
+ }
+
+ private byte[] NFSEncoder(string text)
+ {
+ List result = new List();
+
+ for(int i = 0; i < text.Length; i++)
+ {
+ int index = Array.IndexOf(ArrayOriginList, text[i]);
+ if (index != -1)
+ {
+ result.Add(DefArrayFromStrings[index]);
+ }
+ else
+ {
+ result.Add((byte)text[i]);
+ }
+
+ }
+ return result.ToArray();
+ }
+
+ private string NFSDecoder(byte[] data)
+ {
+ string result = "";
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ int index = Array.IndexOf(DefArrayFromStrings, data[i]);
+ if (index != -1)
+ {
+ result += (char)ArrayOriginList[index];
+ }
+ else
+ {
+ result += (char)data[i];
+ }
+ }
+ return result;
+ }
+ }
+
+ internal class Entry
+ {
+ public uint Id { get; set; }
+ public int Offset { get; set; }
+ public byte[] StringArray { get; set; }
+ public string String { get; set; }
+ }
+}
diff --git a/toolnfs/NFSLocaleTool/NFSLocale.cs.bak b/toolnfs/NFSLocaleTool/NFSLocale.cs.bak
new file mode 100644
index 0000000..4815a0f
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/NFSLocale.cs.bak
@@ -0,0 +1,354 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace NFSLocaleTool
+{
+ internal class NFSLocale
+ {
+ //all offsets without 8 bytes
+ public uint Signature = 0x039000;
+ public int FileSize { get; set; }
+ public int EntriesNum { get; set; }
+ public int TableOffset { get; set; }
+ public int DataOffset { get; set; }
+ public string Type { get; set; }
+ List Entries { get; set; }
+
+ public void ExtractText(string outputFile)
+ {
+ List text = new List();
+ foreach (Entry entry in Entries)
+ {
+ text.Add($"{entry.String.Replace("\n", "¬")}");
+ }
+ File.WriteAllLines(outputFile, text.ToArray());
+ }
+
+ public void WriteFromText(string textFile, string outputChunk, string idsFile)
+ {
+ string[] text = File.ReadAllLines(textFile);
+ string[] ids_ = File.ReadAllLines(idsFile);
+ if(text.Length != ids_.Length)
+ {
+ Console.WriteLine($"Несоответствие кол-ва строк в файле {Path.GetFileName(textFile)} к ID в файле {Path.GetFileName(idsFile)}");
+ Console.WriteLine($"{text.Length}/{ids_.Length}");
+ return;
+ }
+ uint[] ids = new uint[text.Length];
+ uint[] offsets = new uint[text.Length];
+ for(int i = 0; i < ids_.Length; i++)
+ {
+ ids[i] = UInt32.Parse(ids_[i], System.Globalization.NumberStyles.HexNumber);
+ }
+ using(BinaryWriter writer = new BinaryWriter(File.Create(outputChunk)))
+ {
+ writer.Write(Signature);
+ writer.Write(0); //skip filesize
+ writer.Write(text.Length); //listSize
+ writer.Write(140); //table offset dataOffset
+ int textStart = (text.Length * 8) + 0x8C;//calculate text offset stringsOffset 8-9
+ writer.Write(textStart);
+ writer.Write(Encoding.UTF8.GetBytes("Default"));
+
+ writer.BaseStream.Position = textStart + 8; // start text
+ for(int i = 0; i < text.Length; i++)
+ {
+ offsets[i] = (uint)((writer.BaseStream.Position - textStart) - 8);
+ writer.Write(NFSEncoder(text[i].Replace("\"\"", "\"")));
+ writer.Write(new byte()); //null term
+ }
+ writer.BaseStream.Position = 0x94;
+ for (int i = 0; i < text.Length; i++)
+ {
+ writer.Write(ids[i]);
+ writer.Write(offsets[i]);
+ }
+
+ writer.BaseStream.Position = 4;
+ writer.Write((uint)(writer.BaseStream.Length - 8));
+ }
+ }
+ public void Read(string file)
+ {
+ using(BinaryReader reader = new BinaryReader(File.OpenRead(file)))
+ {
+ if (reader.ReadUInt32() != Signature)
+ throw new Exception("Unknown file");
+ FileSize = reader.ReadInt32();
+ EntriesNum = reader.ReadInt32();
+ TableOffset = reader.ReadInt32();
+ DataOffset = reader.ReadInt32();
+ Type = Utils.ReadString(reader, Encoding.UTF8);
+
+ Entries = new List();
+ reader.BaseStream.Position = TableOffset + 8;
+ for(int i = 0; i < EntriesNum; i++)
+ {
+ Entries.Add(new Entry()
+ {
+ Id = reader.ReadUInt32(),
+ Offset = reader.ReadInt32() + DataOffset + 8
+ });
+ }
+ for(int i = 0; i < EntriesNum; i++)
+ {
+ Entry entry = Entries[i];
+ reader.BaseStream.Position = entry.Offset;
+ entry.String = NFSDecoder(Utils.ReadNullTerminatedArray(reader));
+ Entries[i] = entry;
+ }
+
+ File.WriteAllLines(file + ".ids", Entries.Select(i => i.Id.ToString("X8")));
+ }
+ }
+
+ private byte[] NFSEncoder(string text)
+ {
+ List result = new List();
+ for(int i = 0; i < text.Length; i++)
+ {
+ switch (text[i])
+ {
+ case 'А': result.Add(0xc0); break;
+ case 'Б': result.Add(0xc2); break;
+ case 'В': result.Add(0x90); break;
+ case 'Г': result.Add(0x91); break;
+ case 'Д': result.Add(0xc3); break;
+ case 'Е': result.Add(0xc4); break;
+ case 'Ё': result.Add(0x9c); break;
+ case 'Ж': result.Add(0xc5); break;
+ case 'З': result.Add(0xc6); break;
+ case 'И': result.Add(0xc7); break;
+ case 'Й': result.Add(0x9e); break;
+ case 'К': result.Add(0xc9); break;
+ case 'Л': result.Add(0xca); break;
+ case 'М': result.Add(0xcb); break;
+ case 'Н': result.Add(0x92); break;
+ case 'О': result.Add(0xcc); break;
+ case 'П': result.Add(0xce); break;
+ case 'Р': result.Add(0xcf); break;
+ case 'С': result.Add(0xd0); break;
+ case 'Т': result.Add(0xd1); break;
+ case 'У': result.Add(0xd2); break;
+ case 'Ф': result.Add(0xd3); break;
+ case 'Х': result.Add(0xd4); break;
+ case 'Ц': result.Add(0xd5); break;
+ case 'Ч': result.Add(0xd6); break;
+ case 'Ш': result.Add(0xd7); break;
+ case 'Щ': result.Add(0xd8); break;
+ case 'Ъ': result.Add(0xd9); break;
+ case 'Ы': result.Add(0xda); break;
+ case 'Ь': result.Add(0xdb); break;
+ case 'Э': result.Add(0xdd); break;
+ case 'Ю': result.Add(0xde); break;
+ case 'Я': result.Add(0xdf); break;
+ case 'а': result.Add(0x93); break;
+ case 'б': result.Add(0xe2); break;
+ case 'в': result.Add(0x8a); break;
+ case 'г': result.Add(0xe3); break;
+ case 'д': result.Add(0x94); break;
+ case 'е': result.Add(0x8b); break;
+ case 'ё': result.Add(0x9b); break;
+ case 'ж': result.Add(0xe4); break;
+ case 'з': result.Add(0xe5); break;
+ case 'и': result.Add(0x8c); break;
+ case 'й': result.Add(0xe6); break;
+ case 'к': result.Add(0xeb); break;
+ case 'л': result.Add(0x95); break;
+ case 'м': result.Add(0xec); break;
+ case 'н': result.Add(0x96); break;
+ case 'о': result.Add(0x89); break;
+ case 'п': result.Add(0x97); break;
+ case 'р': result.Add(0x8d); break;
+ case 'с': result.Add(0x98); break;
+ case 'т': result.Add(0xee); break;
+ case 'у': result.Add(0xf0); break;
+ case 'ф': result.Add(0xf2); break;
+ case 'х': result.Add(0xf4); break;
+ case 'ц': result.Add(0xf5); break;
+ case 'ч': result.Add(0xf7); break;
+ case 'ш': result.Add(0xf8); break;
+ case 'щ': result.Add(0xf9); break;
+ case 'ъ': result.Add(0xfb); break;
+ case 'ы': result.Add(0xfd); break;
+ case 'ь': result.Add(0xfe); break;
+ case 'э': result.Add(0xff); break;
+ case 'ю': result.Add(0xbf); break;
+ case 'я': result.Add(0xaf); break;
+ case '’': result.Add(0x81); break;
+ case '…': result.Add(0x82); break;
+ case '“': result.Add(0x83); break;
+ case '”': result.Add(0x84); break;
+ case '•': result.Add(0x85); break;
+ case '‘': result.Add(0x86); break;
+ case '™': result.Add(0x87); break;
+ case '–': result.Add(0x88); break;
+ case '—': result.Add(0x8e); break;
+ case 'ł': result.Add(0x8f); break;
+ case 'ї': result.Add(0x99); break;
+ case '': result.Add(0x9a); break;
+ case ' ': result.Add(0xa0); break;
+ case '£': result.Add(0xa3); break;
+ case '©': result.Add(0xa9); break;
+ case '®': result.Add(0xae); break;
+ case '°': result.Add(0xb0); break;
+ case '´': result.Add(0xb4); break;
+ case '·': result.Add(0xb7); break;
+ case '¼': result.Add(0xbc); break;
+ case 'Á': result.Add(0xc1); break;
+ case 'Í': result.Add(0xcd); break;
+ case 'Ü': result.Add(0xdc); break;
+ case 'à': result.Add(0xe0); break;
+ case 'á': result.Add(0xe1); break;
+ case 'ç': result.Add(0xe7); break;
+ case 'è': result.Add(0xe8); break;
+ case 'é': result.Add(0xe9); break;
+ case 'ê': result.Add(0xea); break;
+ case 'í': result.Add(0xed); break;
+ case 'ï': result.Add(0xef); break;
+ case 'ñ': result.Add(0xf1); break;
+ case 'ó': result.Add(0xf3); break;
+ case 'ö': result.Add(0xf6); break;
+ case 'ú': result.Add(0xfa); break;
+ case 'ü': result.Add(0xfc); break;
+ case '«': result.Add(0xab); break;
+ case '»': result.Add(0xbb); break;
+ case '¬': result.Add(0x0a); break;
+ default: result.Add((byte)text[i]); break;
+ }
+ }
+ return result.ToArray();
+ }
+
+ private string NFSDecoder(byte[] data)
+ {
+ string result = "";
+ for(int i = 0; i < data.Length; i++)
+ {
+ switch (data[i])
+ {
+ case 0xc0: result += "А"; break;
+ case 0xc2: result += "Б"; break;
+ case 0x90: result += "В"; break;
+ case 0x91: result += "Г"; break;
+ case 0xc3: result += "Д"; break;
+ case 0xc4: result += "Е"; break;
+ case 0x9c: result += "Ё"; break;
+ case 0xc5: result += "Ж"; break;
+ case 0xc6: result += "З"; break;
+ case 0xc7: result += "И"; break;
+ case 0x9e: result += "Й"; break;
+ case 0xc9: result += "К"; break;
+ case 0xca: result += "Л"; break;
+ case 0xcb: result += "М"; break;
+ case 0x92: result += "Н"; break;
+ case 0xcc: result += "О"; break;
+ case 0xce: result += "П"; break;
+ case 0xcf: result += "Р"; break;
+ case 0xd0: result += "С"; break;
+ case 0xd1: result += "Т"; break;
+ case 0xd2: result += "У"; break;
+ case 0xd3: result += "Ф"; break;
+ case 0xd4: result += "Х"; break;
+ case 0xd5: result += "Ц"; break;
+ case 0xd6: result += "Ч"; break;
+ case 0xd7: result += "Ш"; break;
+ case 0xd8: result += "Щ"; break;
+ case 0xd9: result += "Ъ"; break;
+ case 0xda: result += "Ы"; break;
+ case 0xdb: result += "Ь"; break;
+ case 0xdd: result += "Э"; break;
+ case 0xde: result += "Ю"; break;
+ case 0xdf: result += "Я"; break;
+ case 0x93: result += "а"; break;
+ case 0xe2: result += "б"; break;
+ case 0x8a: result += "в"; break;
+ case 0xe3: result += "г"; break;
+ case 0x94: result += "д"; break;
+ case 0x8b: result += "е"; break;
+ case 0x9b: result += "ё"; break;
+ case 0xe4: result += "ж"; break;
+ case 0xe5: result += "з"; break;
+ case 0x8c: result += "и"; break;
+ case 0xe6: result += "й"; break;
+ case 0xeb: result += "к"; break;
+ case 0x95: result += "л"; break;
+ case 0xec: result += "м"; break;
+ case 0x96: result += "н"; break;
+ case 0x89: result += "о"; break;
+ case 0x97: result += "п"; break;
+ case 0x8d: result += "р"; break;
+ case 0x98: result += "с"; break;
+ case 0xee: result += "т"; break;
+ case 0xf0: result += "у"; break;
+ case 0xf2: result += "ф"; break;
+ case 0xf4: result += "х"; break;
+ case 0xf5: result += "ц"; break;
+ case 0xf7: result += "ч"; break;
+ case 0xf8: result += "ш"; break;
+ case 0xf9: result += "щ"; break;
+ case 0xfb: result += "ъ"; break;
+ case 0xfd: result += "ы"; break;
+ case 0xfe: result += "ь"; break;
+ case 0xff: result += "э"; break;
+ case 0xbf: result += "ю"; break;
+ case 0xaf: result += "я"; break;
+ case 0x81: result += '’'; break;
+ case 0x82: result += '…'; break;
+ case 0x83: result += '“'; break;
+ case 0x84: result += '”'; break;
+ case 0x85: result += '•'; break;
+ case 0x86: result += '‘'; break;
+ case 0x87: result += '™'; break;
+ case 0x88: result += '–'; break;
+ case 0x8e: result += '—'; break;
+ case 0x8f: result += 'ł'; break;
+ case 0x99: result += 'ї'; break;
+ case 0x9a: result += ''; break;
+ case 0xa0: result += ' '; break;
+ case 0xa3: result += '£'; break;
+ case 0xa9: result += '©'; break;
+ case 0xae: result += '®'; break;
+ case 0xb0: result += '°'; break;
+ case 0xb4: result += '´'; break;
+ case 0xb7: result += '·'; break;
+ case 0xbc: result += '¼'; break;
+ case 0xc1: result += 'Á'; break;
+ case 0xcd: result += 'Í'; break;
+ case 0xdc: result += 'Ü'; break;
+ case 0xe0: result += 'à'; break;
+ case 0xe1: result += 'á'; break;
+ case 0xe7: result += 'ç'; break;
+ case 0xe8: result += 'è'; break;
+ case 0xe9: result += 'é'; break;
+ case 0xea: result += 'ê'; break;
+ case 0xed: result += 'í'; break;
+ case 0xef: result += 'ï'; break;
+ case 0xf1: result += 'ñ'; break;
+ case 0xf3: result += 'ó'; break;
+ case 0xf6: result += 'ö'; break;
+ case 0xfa: result += 'ú'; break;
+ case 0xfc: result += 'ü'; break;
+ case 0xab: result += '«'; break;
+ case 0xbb: result += '»'; break;
+ case 0x0a: result += '¬'; break;
+ default: result += (char)data[i]; break;
+ }
+ }
+ return result;
+ }
+ }
+
+ internal class Entry
+ {
+ public uint Id { get; set; }
+ public int Offset { get; set; }
+ public byte[] StringArray { get; set; }
+ public string String { get; set; }
+ }
+}
diff --git a/toolnfs/NFSLocaleTool/NFSLocaleTool.csproj b/toolnfs/NFSLocaleTool/NFSLocaleTool.csproj
new file mode 100644
index 0000000..3989499
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/NFSLocaleTool.csproj
@@ -0,0 +1,76 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {721F5EEC-8CF1-4FA1-99A6-88506B5B42C5}
+ Exe
+ NFSLocaleTool
+ NFSLocaleTool
+ v4.8
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resource1.resx
+
+
+
+
+
+
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resource1.Designer.cs
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/toolnfs/NFSLocaleTool/Program.cs b/toolnfs/NFSLocaleTool/Program.cs
new file mode 100644
index 0000000..5309d14
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/Program.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace NFSLocaleTool
+{
+ internal class Program
+ {
+
+ static void Main(string[] args)
+ {
+ NFSLocale nfs = new NFSLocale();
+ if (args.Length == 0)
+ {
+ Console.WriteLine("Create CharsList:");
+ Console.WriteLine(" -cl ");
+ Console.WriteLine("Create Histogram:");
+ Console.WriteLine(" -hg ");
+ Console.WriteLine("Create Text:");
+ Console.WriteLine(" -t ");
+ Console.WriteLine("Create Binary:");
+ Console.WriteLine(" -b ");
+
+ Console.WriteLine("FrostbiteTool.exe -cl chars_list.txt");
+ Console.WriteLine("FrostbiteTool.exe -h histogram.chunk chars_list.txt newhistogram.chunk");
+ Console.WriteLine("FrostbiteTool.exe -t nfsunbound.chunk newhistogram.chunk nfsunbound.chunk.txt");
+ Console.WriteLine("FrostbiteTool.exe -b nfsunbound.chunk.txt newhistogram.chunk nfsunbound.chunk.ids newnfsunbound.chunk");
+ return;
+ }
+ string arg = args[0];
+ switch (arg)
+ {
+ case "-help":
+ {
+ Console.WriteLine("Create CharsList:");
+ Console.WriteLine(" -cl ");
+ Console.WriteLine("Create Histogram:");
+ Console.WriteLine(" -hg ");
+ Console.WriteLine("Create Text:");
+ Console.WriteLine(" -t ");
+ Console.WriteLine("Create Binary:");
+ Console.WriteLine(" -b ");
+
+ Console.WriteLine("FrostbiteTool.exe -cl chars_list.txt");
+ Console.WriteLine("FrostbiteTool.exe -hg histogram.chunk chars_list.txt newhistogram.chunk");
+ Console.WriteLine("FrostbiteTool.exe -t nfsunbound.chunk newhistogram.chunk nfsunbound.chunk.txt");
+ Console.WriteLine("FrostbiteTool.exe -b nfsunbound.chunk.txt newhistogram.chunk nfsunbound.chunk.ids newnfsunbound.chunk");
+ return;
+ }
+
+ case "-h":
+ {
+ Console.WriteLine("Create CharsList:");
+ Console.WriteLine(" -cl ");
+ Console.WriteLine("Create Histogram:");
+ Console.WriteLine(" -hg ");
+ Console.WriteLine("Create Text:");
+ Console.WriteLine(" -t ");
+ Console.WriteLine("Create Binary:");
+ Console.WriteLine(" -b ");
+
+ Console.WriteLine("FrostbiteTool.exe -cl chars_list.txt");
+ Console.WriteLine("FrostbiteTool.exe -hg histogram.chunk chars_list.txt newhistogram.chunk");
+ Console.WriteLine("FrostbiteTool.exe -t nfsunbound.chunk newhistogram.chunk nfsunbound.chunk.txt");
+ Console.WriteLine("FrostbiteTool.exe -b nfsunbound.chunk.txt newhistogram.chunk nfsunbound.chunk.ids newnfsunbound.chunk");
+ return;
+ }
+
+ case "-cl":
+ {
+ nfs.CreateListChars(args[1]);
+ return;
+ }
+ case "-hg":
+ {
+ nfs.HistogramWrite(args[2], args[1], args[3]);
+ return;
+ }
+ case "-t":
+ {
+ nfs.Read(args[1], args[2]);
+ nfs.ExtractText(args[3]);
+ return;
+ }
+ case "-b":
+ {
+ nfs.WriteFromText(args[1], args[2], args[4], args[3]);
+ return;
+ }
+ default: {
+
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/toolnfs/NFSLocaleTool/Properties/AssemblyInfo.cs b/toolnfs/NFSLocaleTool/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..c967dc9
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Общие сведения об этой сборке предоставляются следующим набором
+// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
+// связанные с этой сборкой.
+[assembly: AssemblyTitle("NFSLocaleTool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("NFSLocaleTool")]
+[assembly: AssemblyCopyright("Copyright © 2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
+// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
+// из модели COM задайте для атрибута ComVisible этого типа значение true.
+[assembly: ComVisible(false)]
+
+// Следующий GUID представляет идентификатор typelib, если этот проект доступен из модели COM
+[assembly: Guid("721f5eec-8cf1-4fa1-99a6-88506b5b42c5")]
+
+// Сведения о версии сборки состоят из указанных ниже четырех значений:
+//
+// Основной номер версии
+// Дополнительный номер версии
+// Номер сборки
+// Номер редакции
+//
+// Можно задать все значения или принять номера сборки и редакции по умолчанию
+// используя "*", как показано ниже:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/toolnfs/NFSLocaleTool/Resource1.Designer.cs b/toolnfs/NFSLocaleTool/Resource1.Designer.cs
new file mode 100644
index 0000000..4811ee8
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/Resource1.Designer.cs
@@ -0,0 +1,177 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace NFSLocaleTool {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resource1 {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resource1() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NFSLocaleTool.Resource1", typeof(Resource1).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] chars {
+ get {
+ object obj = ResourceManager.GetObject("chars", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] charslist {
+ get {
+ object obj = ResourceManager.GetObject("charslist", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to 0002
+ ///0081
+ ///0082
+ ///0083
+ ///0084
+ ///0085
+ ///0086
+ ///0087
+ ///0088
+ ///0089
+ ///008A
+ ///008B
+ ///008C
+ ///008D
+ ///008E
+ ///008F
+ ///0090
+ ///0091
+ ///0092
+ ///0093
+ ///0094
+ ///0095
+ ///0096
+ ///0097
+ ///0098
+ ///0099
+ ///009A
+ ///009B
+ ///009C
+ ///009D
+ ///009E
+ ///009F
+ ///00A0
+ ///00A1
+ ///00A2
+ ///00A3
+ ///00A4
+ ///00A5
+ ///00A6
+ ///00A7
+ ///00A8
+ ///00A9
+ ///00AA
+ ///00AB
+ ///00AC
+ ///00AD
+ ///00AE
+ ///00AF
+ ///00B0
+ ///00B1
+ ///00B2
+ ///00B3
+ ///00B4
+ ///00B5
+ ///00B6
+ ///00B7
+ ///00B8
+ ///00B9
+ ///00BA
+ ///00BB
+ ///00BC
+ ///00BD
+ ///00BE
+ ///00BF
+ ///00C0
+ ///00C1
+ ///00C2
+ ///00C3
+ ///00C4
+ ///00C5
+ ///00C6
+ ///00C7
+ ///00C8
+ ///00C9
+ ///00CA
+ ///00CB
+ ///00CC
+ ///00CD
+ ///00CE
+ ///00CF
+ ///00D0
+ ///00D1
+ ///00D2
+ ///00D3
+ ///00D4
+ ///00 [rest of string was truncated]";.
+ ///
+ internal static string default_list_chunk_number {
+ get {
+ return ResourceManager.GetString("default_list_chunk_number", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/toolnfs/NFSLocaleTool/Resource1.resx b/toolnfs/NFSLocaleTool/Resource1.resx
new file mode 100644
index 0000000..0f1b79c
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/Resource1.resx
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ Resources\chars.ids;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\charslist.txt;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Resources\default list.chunk_number.ids;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;windows-1251
+
+
\ No newline at end of file
diff --git a/toolnfs/NFSLocaleTool/Resources/chars.ids b/toolnfs/NFSLocaleTool/Resources/chars.ids
new file mode 100644
index 0000000..12dec35
Binary files /dev/null and b/toolnfs/NFSLocaleTool/Resources/chars.ids differ
diff --git a/toolnfs/NFSLocaleTool/Resources/charslist.txt b/toolnfs/NFSLocaleTool/Resources/charslist.txt
new file mode 100644
index 0000000..3fce381
Binary files /dev/null and b/toolnfs/NFSLocaleTool/Resources/charslist.txt differ
diff --git a/toolnfs/NFSLocaleTool/Resources/default list.chunk_number.ids b/toolnfs/NFSLocaleTool/Resources/default list.chunk_number.ids
new file mode 100644
index 0000000..3f36021
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/Resources/default list.chunk_number.ids
@@ -0,0 +1,128 @@
+0002
+0081
+0082
+0083
+0084
+0085
+0086
+0087
+0088
+0089
+008A
+008B
+008C
+008D
+008E
+008F
+0090
+0091
+0092
+0093
+0094
+0095
+0096
+0097
+0098
+0099
+009A
+009B
+009C
+009D
+009E
+009F
+00A0
+00A1
+00A2
+00A3
+00A4
+00A5
+00A6
+00A7
+00A8
+00A9
+00AA
+00AB
+00AC
+00AD
+00AE
+00AF
+00B0
+00B1
+00B2
+00B3
+00B4
+00B5
+00B6
+00B7
+00B8
+00B9
+00BA
+00BB
+00BC
+00BD
+00BE
+00BF
+00C0
+00C1
+00C2
+00C3
+00C4
+00C5
+00C6
+00C7
+00C8
+00C9
+00CA
+00CB
+00CC
+00CD
+00CE
+00CF
+00D0
+00D1
+00D2
+00D3
+00D4
+00D5
+00D6
+00D7
+00D8
+00D9
+00DA
+00DB
+00DC
+00DD
+00DE
+00DF
+00E0
+00E1
+00E2
+00E3
+00E4
+00E5
+00E6
+00E7
+00E8
+00E9
+00EA
+00EB
+00EC
+00ED
+00EE
+00EF
+00F0
+00F1
+00F2
+00F3
+00F4
+00F5
+00F6
+00F7
+00F8
+00F9
+00FA
+00FB
+00FC
+00FD
+00FE
+00FF
\ No newline at end of file
diff --git a/toolnfs/NFSLocaleTool/Utils.cs b/toolnfs/NFSLocaleTool/Utils.cs
new file mode 100644
index 0000000..84ec090
--- /dev/null
+++ b/toolnfs/NFSLocaleTool/Utils.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NFSLocaleTool
+{
+ public static class Utils
+ {
+ public static byte[] ReadNullTerminatedArray(BinaryReader reader)
+ {
+ List result = new List();
+ while (true)
+ {
+ byte b = reader.ReadByte();
+ if(b != 0)
+ {
+ result.Add(b);
+ continue;
+ }
+ break;
+ }
+ return result.ToArray();
+ }
+
+
+ public static string ReadString(byte[] namebuf, Encoding encoding)
+ {
+ BinaryReader binaryReader = new BinaryReader(new MemoryStream(namebuf));
+ if (encoding == null) throw new ArgumentNullException("encoding");
+
+ List data = new List();
+
+ while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length)
+ {
+ data.Add(binaryReader.ReadByte());
+
+ string partialString = encoding.GetString(data.ToArray(), 0, data.Count);
+
+ if (partialString.Length > 0 && partialString.Last() == '\0')
+ return encoding.GetString(data.SkipLast(encoding.GetByteCount("\0")).ToArray()).TrimEnd('\0');
+ }
+ throw new InvalidDataException("Hit end of stream while reading null-terminated string.");
+ }
+ public static string ReadString(this BinaryReader binaryReader, Encoding encoding)
+ {
+ if (binaryReader == null) throw new ArgumentNullException("binaryReader");
+ if (encoding == null) throw new ArgumentNullException("encoding");
+
+ List data = new List();
+
+ while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length)
+ {
+ data.Add(binaryReader.ReadByte());
+
+ string partialString = encoding.GetString(data.ToArray(), 0, data.Count);
+
+ if (partialString.Length > 0 && partialString.Last() == '\0')
+ return encoding.GetString(data.SkipLast(encoding.GetByteCount("\0")).ToArray()).TrimEnd('\0');
+ }
+ throw new InvalidDataException("Hit end of stream while reading null-terminated string.");
+ }
+ private static IEnumerable SkipLast(this IEnumerable source, int count)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+
+ Queue queue = new Queue();
+
+ foreach (TSource item in source)
+ {
+ queue.Enqueue(item);
+
+ if (queue.Count > count) yield return queue.Dequeue();
+ }
+ }
+ }
+}