Skip to content

Instantly share code, notes, and snippets.

@LinkOFF7
Created February 2, 2023 18:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LinkOFF7/adf3c2dcb834482d83dd23d172b26b15 to your computer and use it in GitHub Desktop.
Save LinkOFF7/adf3c2dcb834482d83dd23d172b26b15 to your computer and use it in GitHub Desktop.
Need for Speed Heat/Unbound Cyrillic Encoder
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using tomodachi;
namespace FrostbiteResearch
{
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<Entry> Entries { get; set; }
public void ExtractText(string outputFile)
{
List<string> text = new List<string>();
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);
writer.Write(140); //table offset
int textStart = (text.Length * 8) + 0x8C;//calculate text offset
writer.Write(textStart);
writer.Write(Encoding.ASCII.GetBytes("Default"));
writer.BaseStream.Position = textStart + 8;
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.ASCII);
Entries = new List<Entry>();
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<byte> result = new List<byte>();
for(int i = 0; i < text.Length; i++)
{
switch (text[i])
{
case 'А': result.Add(0x8D); break;
case 'Б': result.Add(0xB9); break;
case 'В': result.Add(0xA4); break;
case 'Г': result.Add(0xAD); break;
case 'Д': result.Add(0xA2); break;
case 'Е': result.Add(0x97); break;
case 'Ё': result.Add(0xCB); break;
case 'Ж': result.Add(0xC3); break;
case 'З': result.Add(0xB2); break;
case 'И': result.Add(0x96); break;
case 'Й': result.Add(0xB6); break;
case 'К': result.Add(0x9A); break;
case 'Л': result.Add(0xA7); break;
case 'М': result.Add(0xAC); break;
case 'Н': result.Add(0x91); break;
case 'О': result.Add(0x92); break;
case 'П': result.Add(0xA3); break;
case 'Р': result.Add(0x99); break;
case 'С': result.Add(0x9C); break;
case 'Т': result.Add(0x9E); break;
case 'У': result.Add(0xA8); break;
case 'Ф': result.Add(0xC5); break;
case 'Х': result.Add(0xC4); break;
case 'Ц': result.Add(0xC7); break;
case 'Ч': result.Add(0xBC); break;
case 'Ш': result.Add(0xC2); break;
case 'Щ': result.Add(0xCC); break;
case 'Ъ': result.Add(0xD2); break;
case 'Ы': result.Add(0xB5); break;
case 'Ь': result.Add(0xBA); break;
case 'Э': result.Add(0xB8); break;
case 'Ю': result.Add(0xCA); break;
case 'Я': result.Add(0xB3); break;
case 'а': result.Add(0x83); break;
case 'б': result.Add(0x98); break;
case 'в': result.Add(0x89); break;
case 'г': result.Add(0x9B); break;
case 'д': result.Add(0x8B); break;
case 'е': result.Add(0x82); break;
case 'ё': result.Add(0xB1); break;
case 'ж': result.Add(0xA6); break;
case 'з': result.Add(0x9D); break;
case 'и': result.Add(0x85); break;
case 'й': result.Add(0xA1); break;
case 'к': result.Add(0x8C); break;
case 'л': result.Add(0x8A); break;
case 'м': result.Add(0x8E); break;
case 'н': result.Add(0x86); break;
case 'о': result.Add(0x81); break;
case 'п': result.Add(0x90); break;
case 'р': result.Add(0x88); break;
case 'с': result.Add(0x87); break;
case 'т': result.Add(0x84); break;
case 'у': result.Add(0x8F); break;
case 'ф': result.Add(0xBD); break;
case 'х': result.Add(0xAA); break;
case 'ц': result.Add(0xC0); break;
case 'ч': result.Add(0x9F); break;
case 'ш': result.Add(0xA5); break;
case 'щ': result.Add(0xBF); break;
case 'ъ': result.Add(0xCF); break;
case 'ы': result.Add(0x93); break;
case 'ь': result.Add(0x94); break;
case 'э': result.Add(0xBE); break;
case 'ю': result.Add(0xAF); break;
case 'я': result.Add(0x95); break;
case '—': result.Add(0xC6); break;
case '…': result.Add(0xD1); break;
case '™': result.Add(0xD0); break;
case '“': result.Add(0xCD); break;
case '’': result.Add(0xD4); break;
case '”': result.Add(0xCE); break;
case '–': result.Add(0xD3); break;
case '': result.Add(0xD5); break;
case '№': result.Add(0xD6); break;
case '●': result.Add(0xD7); 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 0x8D: result += "А"; break;
case 0xB9: result += "Б"; break;
case 0xA4: result += "В"; break;
case 0xAD: result += "Г"; break;
case 0xA2: result += "Д"; break;
case 0x97: result += "Е"; break;
case 0xCB: result += "Ё"; break;
case 0xC3: result += "Ж"; break;
case 0xB2: result += "З"; break;
case 0x96: result += "И"; break;
case 0xB6: result += "Й"; break;
case 0x9A: result += "К"; break;
case 0xA7: result += "Л"; break;
case 0xAC: result += "М"; break;
case 0x91: result += "Н"; break;
case 0x92: result += "О"; break;
case 0xA3: result += "П"; break;
case 0x99: result += "Р"; break;
case 0x9C: result += "С"; break;
case 0x9E: result += "Т"; break;
case 0xA8: result += "У"; break;
case 0xC5: result += "Ф"; break;
case 0xC4: result += "Х"; break;
case 0xC7: result += "Ц"; break;
case 0xBC: result += "Ч"; break;
case 0xC2: result += "Ш"; break;
case 0xCC: result += "Щ"; break;
case 0xD2: result += "Ъ"; break;
case 0xB5: result += "Ы"; break;
case 0xBA: result += "Ь"; break;
case 0xB8: result += "Э"; break;
case 0xCA: result += "Ю"; break;
case 0xB3: result += "Я"; break;
case 0x83: result += "а"; break;
case 0x98: result += "б"; break;
case 0x89: result += "в"; break;
case 0x9B: result += "г"; break;
case 0x8B: result += "д"; break;
case 0x82: result += "е"; break;
case 0xB1: result += "ё"; break;
case 0xA6: result += "ж"; break;
case 0x9D: result += "з"; break;
case 0x85: result += "и"; break;
case 0xA1: result += "й"; break;
case 0x8C: result += "к"; break;
case 0x8A: result += "л"; break;
case 0x8E: result += "м"; break;
case 0x86: result += "н"; break;
case 0x81: result += "о"; break;
case 0x90: result += "п"; break;
case 0x88: result += "р"; break;
case 0x87: result += "с"; break;
case 0x84: result += "т"; break;
case 0x8F: result += "у"; break;
case 0xBD: result += "ф"; break;
case 0xAA: result += "х"; break;
case 0xC0: result += "ц"; break;
case 0x9F: result += "ч"; break;
case 0xA5: result += "ш"; break;
case 0xBF: result += "щ"; break;
case 0xCF: result += "ъ"; break;
case 0x93: result += "ы"; break;
case 0x94: result += "ь"; break;
case 0xBE: result += "э"; break;
case 0xAF: result += "ю"; break;
case 0x95: result += "я"; break;
case 0xC6: result += "—"; break;
case 0xD1: result += "…"; break;
case 0xD0: result += "™"; break;
case 0xCD: result += "“"; break;
case 0xD4: result += "’"; break;
case 0xCE: result += "”"; break;
case 0xD3: result += "–"; break;
case 0xD5: result += ""; break;
case 0xD6: result += "№"; break;
case 0xD7: 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; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment