unit DatStructureLoader;

interface

type
  TStructureEntry = record
    Name:     String;
    offset:   Integer;
    datatype: Word;
      // 1..4  : Integer[1..4] dec
      // 5..8  : Integer[1..4] hex
      // 9     : float
      // 10    : bitset
      // 11    : raw-addr
      // 12    : untyped-dat-file-ID-link
      // 13..16: Signed Integer[1..4]
      // 17    : level-ID
      // 100..300: dat-file-name[0..200]
      // 501..614: typed-dat-file-ID-link
      // 1000..9999: Unused data[0-8999]
      // 10000+: string[0+]
    description: String;
  end;

  TStructDefSub = record
    SubName: String;
    SubDesc: String;
    Entries: array of TStructureEntry;
  end;

  TStructDef = record
    Data:   Boolean;
    Global: array of TStructureEntry;
    Subs:   array of TStructDefSub;
  end;


function LoadStructureDefinition(ConnectionID, FileID: Integer): TStructDef;
function GetDataType(TypeID: Word): String;
function GetDataTypeLength(DataType: Word): Word;

implementation

uses
  SysUtils, Classes, Functions, ConnectionManager, TypeDefs, Forms, StrUtils, Data;

type
  TStringArray = array of String;

function GetDataTypeLength(DataType: Word): Word;
begin
  case datatype of
    1..4:
      Result := datatype;
    5..8:
      Result := datatype - 4;
    9:
      Result := 4;
    10:
      Result := 1;
    11:
      Result := 4;
    12:
      Result := 4;
    13..16:
      Result := datatype - 12;
    17:
      Result := 4;
    100..300:
      Result := datatype - 100;
    500..614:
      Result := 4;
    1000..9999:
      Result := datatype - 1000;
    10000..65535:
      Result := datatype - 10000;
  else
    Result := 0;
  end;
end;



function GetDataType(typeid: Word): String;
begin
  case typeid of
    1..4:
      Result := 'Int' + IntToStr(typeid * 8);
    5..8:
      Result := 'Int' + IntToStr((typeid - 4) * 8);
    9:
      Result := 'Float';
    10:
      Result := 'BitSet';
    11:
      Result := 'Raw-Address';
    12:
      Result := '.dat-file-ID';
    13..16:
      Result := 'SignedInt' + IntToStr((typeid - 12) * 8);
    17:
      Result := 'LevelID';
    100..300:
      Result := '.dat-file-name(' + IntToStr(typeid - 100) + ')';
    1000..9999:
      Result := 'Unused(' + IntToStr(typeid - 1000) + ')';
    10000..65535:
      Result := 'String(' + IntToStr(typeid - 10000) + ')';
  end;
end;



function Explode(_string: String; delimiter: Char): TStringArray;
var
  start, len: Word;
begin
  SetLength(Result, 0);
  start := 1;
  while PosEx(delimiter, _string, start) > 0 do
  begin
    SetLength(Result, Length(Result) + 1);
    len := PosEx(delimiter, _string, start) - start;
    Result[High(Result)] := MidStr(_string, start, len);
    start := start + len + 1;
  end;
  SetLength(Result, Length(Result) + 1);
  Result[High(Result)] := MidStr(_string, start, Length(_string) - start + 1);
end;



function LoadStructureDefinition(ConnectionID, FileID: Integer): TStructDef;
var
  current_type: Byte; //0: Global, 1: Undynamic, 2: Dynamic
  current_base, current_package, current_package_size: Integer;
  packages: Integer;
  deffile: Text;
  structentry: TStructureEntry;
  fields: TStringArray;
  filename: String;
  ext:    String[4];
  temps:  String;
  Data:   TByteData;
begin
  SetLength(Result.Global, 0);
  SetLength(Result.Subs, 0);
  Result.Data := False;
  ext      := ConManager.Connection[ConnectionID].GetFileInfo(fileid).Extension;
  filename := ExtractFilePath(Application.ExeName) + '\StructDefs\' + ext + '.txt';
  if FileExists(filename) then
  begin
    ConManager.Connection[ConnectionID].LoadDatFile(FileID, Data);
    AssignFile(deffile, filename);
    Reset(deffile);
    current_type := 0;
    Result.Data  := True;
    if not EOF(deffile) then
    begin
      ReadLn(deffile, temps);
      while not EOF(deffile) do
      begin
        ReadLn(deffile, temps);
        if (Length(temps) > 0) and (temps[1] <> '#') then
        begin
          if temps[1] = '*' then
          begin
            fields := Explode(temps, #9);
            case Length(fields) of
              1..2:
              begin
                current_type := 1;
                current_base := 0;
                SetLength(Result.Subs, Length(Result.Subs) + 1);
                Result.Subs[High(Result.Subs)].SubName :=
                  MidStr(fields[0], 2, Length(fields[0]) - 1);
                if Length(fields) = 2 then
                  Result.Subs[High(Result.Subs)].SubDesc := fields[1];
              end;
              3:
              begin
                current_type := 1;
                current_base := StrToInt(fields[2]);
                SetLength(Result.Subs, Length(Result.Subs) + 1);
                Result.Subs[High(Result.Subs)].SubName :=
                  MidStr(fields[0], 2, Length(fields[0]) - 1);
                Result.Subs[High(Result.Subs)].SubDesc := fields[1];
              end;
              6:
              begin
                current_type    := 2;
                current_base    := StrToInt(fields[2]);
                current_package := 0;
                current_package_size := StrToInt(fields[5]);
                if fields[4][1] <> '$' then
                begin
                  case StrToInt(fields[4]) of
                    1:
                      packages := Data[StrToInt(fields[3])];
                    2:
                      packages := Data[StrToInt(fields[3])] + Data[StrToInt(fields[3]) + 1] * 256;
                    4:
                      packages := Data[StrToInt(fields[3])] + Data[StrToInt(fields[3]) + 1] *
                        256 + Data[StrToInt(fields[3]) + 2] * 256 * 256 + Data[StrToInt(fields[3]) + 3] * 256 * 256 * 256;
                  end;
                end
                else
                begin
                  packages := StrToInt(fields[4]);
                end;
                SetLength(Result.Subs, Length(Result.Subs) + packages);
                for current_package := 0 to packages - 1 do
                begin
                  Result.Subs[High(Result.Subs) - packages +
                    current_package + 1].SubName :=
                    MidStr(fields[0], 2, Length(fields[0]) - 1) +
                    '[' + IntToStr(current_package) + ']' + '#' +
                    IntToHex(current_base + current_package * current_package_size, 8) +
                    '#' + IntToHex(current_package_size, 8);
                  Result.Subs[High(Result.Subs) - packages +
                    current_package + 1].SubDesc :=
                    fields[1];
                end;
              end;
            end;
          end
          else
          begin
            fields := Explode(temps, #9);
            if (Length(fields) = 3) or (Length(fields) = 4) then
            begin
              if not AppSettings.HideUnusedData or
                ((StrToInt(fields[2]) < 1000) or (StrToInt(fields[2]) > 9999)) then
              begin
                structentry.Name     := fields[0];
                structentry.datatype := StrToInt(fields[2]);
                if Length(fields) = 4 then
                  structentry.description := fields[3]
                else
                  structentry.description := '';
                if current_type in [0, 1] then
                begin
                  structentry.offset := StrToInt(fields[1]) + current_base;
                  if Length(Result.Subs) = 0 then
                  begin
                    SetLength(Result.Global, Length(Result.Global) + 1);
                    Result.Global[High(Result.Global)] := structentry;
                  end
                  else
                  begin
                    SetLength(Result.Subs[High(Result.Subs)].Entries,
                      Length(Result.Subs[High(Result.Subs)].Entries) + 1);
                    Result.Subs[High(Result.Subs)].Entries[High(
                      Result.Subs[High(Result.Subs)].Entries)] := structentry;
                  end;
                end
                else
                begin
                  for current_package := 0 to packages - 1 do
                  begin
                    structentry.offset :=
                      current_base + current_package * current_package_size + StrToInt(fields[1]);
                    with Result.Subs[High(Result.Subs) - packages + current_package + 1] do
                    begin
                      SetLength(Entries, Length(Entries) + 1);
                      Entries[High(Entries)] := structentry;
                    end;
                  end;
                end;
              end;
            end;
          end;
        end;
      end;
    end;
    CloseFile(deffile);
  end;
end;


end.