using System.Collections.Generic; using System.IO; using UnityEngine; public class ScriptLoader { public Script Load(byte[] binaryData) { Script script = new Script(); using (BinaryReader reader = new BinaryReader(new MemoryStream(binaryData))) { uint subTableOffset = reader.ReadUInt32(); uint unk2 = reader.ReadUInt32(); uint subCount = reader.ReadUInt32(); uint cmdListOffset = reader.ReadUInt32(); uint paramsListOffset = reader.ReadUInt32(); uint textArrayOffset = reader.ReadUInt32(); uint endOfFileOffset = (uint)binaryData.Length; // Конец файла script.SubroutineTable = ParseSubTable(reader, subCount, subTableOffset); reader.BaseStream.Position = cmdListOffset; List commandsWithoutParams = ParseCmdListWithoutParams(reader, paramsListOffset); script.Commands = new List(commandsWithoutParams.Count); for (int i = 0; i < commandsWithoutParams.Count; i++) { ScriptCommand cmd = commandsWithoutParams[i]; uint currentParamsOffset = cmd.ParamsOffset; uint nextParamsOffset; if (i < commandsWithoutParams.Count - 1) { nextParamsOffset = commandsWithoutParams[i + 1].ParamsOffset; } else { nextParamsOffset = textArrayOffset; } int paramsLengthInBytes = (int)(nextParamsOffset - currentParamsOffset); cmd.Params = ReadParamValues(reader, currentParamsOffset, paramsLengthInBytes); script.Commands.Add(cmd); } script.TextData = ReadBytes(reader, textArrayOffset, endOfFileOffset); } return script; } private List ParseCmdListWithoutParams(BinaryReader reader, uint paramsListOffset) { var commands = new List(); while (reader.BaseStream.Position < paramsListOffset) { ScriptCommand cmd = new ScriptCommand { OpCode = reader.ReadUInt32() & 0xFFFF, //Cleaning to 16-bit command (May be some trash or what ???), ParamsOffset = reader.ReadUInt32() }; commands.Add(cmd); } return commands; } private int[] ReadParamValues(BinaryReader reader, uint startOffset, int lengthInBytes) { if (lengthInBytes <= 0) return new int[0]; if (lengthInBytes % 4 != 0) { Debug.LogWarning($"Parameter block length ({lengthInBytes}) is not a multiple of 4. Truncating."); lengthInBytes = (lengthInBytes / 4) * 4; // Округляем вниз до ближайшего, кратного 4 } int paramCount = lengthInBytes / 4; int[] parameters = new int[paramCount]; reader.BaseStream.Position = startOffset; for (int i = 0; i < paramCount; i++) { parameters[i] = reader.ReadInt32(); } return parameters; } private byte[] ReadBytes(BinaryReader reader, uint startAddr, uint endAddr) { if (startAddr >= endAddr) return new byte[0]; int length = (int)(endAddr - startAddr); reader.BaseStream.Position = startAddr; return reader.ReadBytes(length); } private Dictionary ParseSubTable(BinaryReader reader, uint subCount, long baseOffset) { var table = new Dictionary(); for (ushort id = 0; id < subCount; id++) { long entryOffset = baseOffset + (id * 72); // 72 bytes on each subroutine header reader.BaseStream.Position = entryOffset; Subroutine sub = new Subroutine(); sub.Id = id; // Читаем имя (null-terminated string) List nameBytes = new List(); byte b; while ((b = reader.ReadByte()) != 0) { nameBytes.Add(b); } sub.Name = System.Text.Encoding.UTF8.GetString(nameBytes.ToArray()); reader.BaseStream.Position = entryOffset + 64; //address always +64 byte offset from structure start sub.Address = reader.ReadUInt32(); table.Add(id, sub); } return table; } } public class Script { public Dictionary SubroutineTable { get; set; } public List Commands { get; set; } public byte[] TextData { get; set; } // textBlob } public class Subroutine { public ushort Id { get; set; } public string Name { get; set; } public uint Address { get; set; } // Адрес первой команды подпрограммы в списке Commands } public class ScriptCommand { public uint OpCode { get; set; } public ushort executedStatus { get; set; } //For original engine compability (0 - not executed, 3 - parsed, may be other statuses - need to reverse) public uint ParamsOffset { get; set; } public int[] Params { get; set; } // command params array }