unit DataStructures;

interface

uses SysUtils, Classes, Data, Dialogs, StrUtils, OniDataClass;

type
  Tstructure_entry = record
    Name:     String;
    offset:   LongWord;
    datatype: Word;  // 1..4  : Integer[1..4] dec
    // 5..8  : Integer[1..4] hex
    // 9     : float
    // 10    : bitset
    // 11    : raw-addr
    // 12    : dat-file-ID
    // 13..16: Signed Integer[1..4]
    // 17    : level-ID
    // 100..300: dat-file-name[0..200]
    // 1000..9999: Unused data[0-8999]
    // 10000+: string[0+]
    description: String;
  end;

  TStructDefSub = record
    SubName: String;
    SubDesc: String;
    Entries: array of TStructure_entry;
  end;

  TStructDef = record
    Data:   Boolean;
    Global: array of TStructure_entry;
    Subs:   array of TStructDefSub;
  end;
  THandler = function(connection: TOniData; fileid: LongWord): TRawList;

  TRawListHandlers = record
    Ext:     String[4];
    needed:  Boolean;
    Handler: THandler;
  end;

var
  RawListHandlers: array of TRawListHandlers;
  Raws: String;


function LoadStructureDefinition(connection: TOniData; fileid: LongWord): TStructDef;
function GetDataType(typeid: Word): String;
function GetTypeDataLength(datatype: Word): Word;

implementation

uses Functions, Forms, Template;




function GetTypeDataLength(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;
    1000..9999:
      Result := datatype - 1000;
    10000..65535:
      Result := datatype - 10000;
  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 AGDB(connection: TOniData; fileid: LongWord): TRawList;
var
  link:  LongWord;
  links: LongWord;
  i:     LongWord;
begin
  if not connection.OSisMac then
  begin
    connection.LoadDatFilePart(fileid, $1C, 4, @links);
    links := links * 2;
    SetLength(Result, links);
    for i := 0 to links - 1 do
    begin
      Result[i].src_offset := $20 + i * 4;
      connection.LoadDatFilePart(fileid, $20 + i * 4, 4, @link);
      Result[i].raw_addr := link;
      Result[i].raw_size := 32;
      Result[i].loc_sep  := False;
    end;
  end;
end;




function AKVA(connection: TOniData; fileid: LongWord): TRawList;
var
  link:  LongWord;
  links: LongWord;
  i:     LongWord;
begin
  if not connection.OSisMac then
  begin
    connection.LoadDatFilePart(fileid, $1C, 4, @links);
    SetLength(Result, links);
    for i := 0 to links - 1 do
    begin
      Result[i].src_offset := $20 + i * $74 + $24;
      connection.LoadDatFilePart(fileid, $20 + i * $74 + $24, 4, @link);
      Result[i].raw_addr := link;
      connection.LoadDatFilePart(fileid, $20 + i * $74 + $28, 4, @link);
      Result[i].raw_size := link;
      Result[i].loc_sep  := False;
    end;
  end;
end;




function BINA(connection: TOniData; fileid: LongWord): TRawList;
var
  link:     LongWord;
  datasize: LongWord;
begin
  connection.LoadDatFilePart(fileid, $0C, 4, @link);
  connection.LoadDatFilePart(fileid, $08, 4, @datasize);
  SetLength(Result, 1);
  Result[0].src_offset := $0C;
  Result[0].raw_addr   := link;
  Result[0].raw_size   := datasize;
  Result[0].loc_sep    := connection.OSisMac;
end;




function OSBD(connection: TOniData; fileid: LongWord): TRawList;
var
  link:     LongWord;
  datasize: LongWord;
begin
  connection.LoadDatFilePart(fileid, $08, 4, @datasize);
  connection.LoadDatFilePart(fileid, $0C, 4, @link);
  SetLength(Result, 1);
  Result[0].src_offset := $0C;
  Result[0].raw_addr   := link;
  Result[0].raw_size   := datasize;
  Result[0].loc_sep    := connection.OSisMac;
end;




function SNDD(connection: TOniData; fileid: LongWord): TRawList;
var
  link:     LongWord;
  datasize: LongWord;
begin
  SetLength(Result, 1);
  if not connection.OSisMac then
  begin
    connection.LoadDatFilePart(fileid, $40, 4, @datasize);
    connection.LoadDatFilePart(fileid, $44, 4, @link);
    Result[0].src_offset := $44;
  end
  else
  begin
    connection.LoadDatFilePart(fileid, $10, 4, @datasize);
    connection.LoadDatFilePart(fileid, $14, 4, @link);
    Result[0].src_offset := $14;
  end;
  Result[0].raw_addr := link;
  Result[0].raw_size := datasize;
  Result[0].loc_sep  := False;
end;




function SUBT(connection: TOniData; fileid: LongWord): TRawList;
var
  baselink, lastlink: LongWord;
  links: LongWord;
  j, k:  LongWord;
  Data:  Tdata;
begin
  connection.LoadDatFilePart(fileid, $18, 4, @baselink);
  connection.LoadDatFilePart(fileid, $1C, 4, @links);
  if links > 0 then
  begin
    connection.LoadDatFilePart(fileid, $20 + (links - 1) * 4, 4, @lastlink);
    SetLength(Data, lastlink + 1024);
    TOniDataDat(connection).LoadRawOffset(False,
      baselink, lastlink + 1024, Data);
    //      connection.LoadRawFile(fileid,$1C,baselink,lastlink+1024,False,@data[0]);
    k := 0;
    for j := 0 to 1024 do
    begin
      if (Data[lastlink + j] = $00) or (j = 1024) then
      begin
        if j < 1024 then
        begin
          if k = 0 then
          begin
            k := 1;
          end
          else
          begin
            SetLength(Result, 1);
            Result[0].src_offset := $18;
            Result[0].raw_addr   := baselink;
            Result[0].raw_size   := lastlink + j;
            Break;
          end;
        end;
      end;
    end;
  end;
end;




function TRAM(connection: TOniData; fileid: LongWord): TRawList;
var
  i:      Integer;
  link:   LongWord;
  frames: Word;
  tempb:  Byte;
  tempw:  Word;
  templ:  LongWord;
  Data:   Tdata;
  offset: Word;
  frame_count: Byte;
begin
  SetLength(Result, 13);
  connection.LoadDatFilePart(fileid, $16C, 2, @frames);
  {y-pos}
  connection.LoadDatFilePart(fileid, $0C, 4, @link);
  Result[0].src_offset := $0C;
  Result[0].raw_addr   := link;
  Result[0].raw_size   := frames * 4;
  {x-z-pos}
  connection.LoadDatFilePart(fileid, $10, 4, @link);
  Result[1].src_offset := $10;
  Result[1].raw_addr   := link;
  Result[1].raw_size   := frames * 8;
  {attacks}
  connection.LoadDatFilePart(fileid, $182, 1, @tempb);
  connection.LoadDatFilePart(fileid, $14, 4, @link);
  Result[2].src_offset := $14;
  Result[2].raw_addr   := link;
  Result[2].raw_size   := tempb * 32;
  {damage}
  connection.LoadDatFilePart(fileid, $183, 1, @tempb);
  connection.LoadDatFilePart(fileid, $18, 4, @link);
  Result[3].src_offset := $18;
  Result[3].raw_addr   := link;
  Result[3].raw_size   := tempb * 8;
  {motionblur}
  connection.LoadDatFilePart(fileid, $184, 1, @tempb);
  connection.LoadDatFilePart(fileid, $1C, 4, @link);
  Result[4].src_offset := $1C;
  Result[4].raw_addr   := link;
  Result[4].raw_size   := tempb * 8;
  {shortcut}
  connection.LoadDatFilePart(fileid, $185, 1, @tempb);
  connection.LoadDatFilePart(fileid, $20, 4, @link);
  Result[5].src_offset := $20;
  Result[5].raw_addr   := link;
  Result[5].raw_size   := tempb * 8;
  {throw}
  connection.LoadDatFilePart(fileid, $24, 4, @link);
  Result[6].src_offset := $24;
  Result[6].raw_addr   := link;
  if link > 0 then
    Result[6].raw_size := 24
  else
    Result[6].raw_size := 0;
  {footstep}
  connection.LoadDatFilePart(fileid, $186, 1, @tempb);
  connection.LoadDatFilePart(fileid, $28, 4, @link);
  Result[7].src_offset := $28;
  Result[7].raw_addr   := link;
  Result[7].raw_size   := tempb * 4;
  {particle}
  connection.LoadDatFilePart(fileid, $187, 1, @tempb);
  connection.LoadDatFilePart(fileid, $2C, 4, @link);
  Result[8].src_offset := $2C;
  Result[8].raw_addr   := link;
  Result[8].raw_size   := tempb * 24;
  {position}
  connection.LoadDatFilePart(fileid, $30, 4, @link);
  Result[9].src_offset := $30;
  Result[9].raw_addr   := link;
  Result[9].raw_size   := frames * 8;
  {particle}
  connection.LoadDatFilePart(fileid, $154, 2, @tempw);
  connection.LoadDatFilePart(fileid, $38, 4, @link);
  Result[11].src_offset := $38;
  Result[11].raw_addr   := link;
  Result[11].raw_size   := tempw * 34;
  {extent}
  connection.LoadDatFilePart(fileid, $138, 4, @templ);
  connection.LoadDatFilePart(fileid, $13C, 4, @link);
  Result[12].src_offset := $13C;
  Result[12].raw_addr   := link;
  Result[12].raw_size   := templ * 12;

  connection.LoadDatFilePart(fileid, $34, 4, @link);
  if link > 0 then
  begin
    connection.LoadDatFilePart(fileid, $160, 2, @tempw);
    frame_count := 0;
    i := 0;
    SetLength(Data, $FFFF);
    TOniDataDat(connection).LoadRawOffset(False, link, $FFFF, Data);
    offset := Data[$24] + Data[$25] * 256;
    while (offset + i < Length(Data)) and (frame_count < frames - 1) do
    begin
      Inc(i, tempw);
      frame_count := frame_count + Data[offset + i];
      Inc(i);
    end;
    if offset + i < Length(Data) then
    begin
      Inc(i, tempw);
      Result[10].raw_size := offset + i;
    end
    else
    begin
      Result[10].raw_size := 0;
    end;
  end;
  Result[10].src_offset := $34;
  Result[10].raw_addr   := link;
end;




function TXMP(connection: TOniData; fileid: LongWord): TRawList;
var
  link_pc:   LongWord;
  link_mac:  LongWord;
  x, y:      Word;
  storetype: Byte;
  datasize:  LongWord;
begin
  connection.LoadDatFilePart(fileid, $8C, SizeOf(x), @x);
  connection.LoadDatFilePart(fileid, $8E, SizeOf(y), @y);
  connection.LoadDatFilePart(fileid, $90, SizeOf(storetype), @storetype);
  connection.LoadDatFilePart(fileid, $9C, 4, @link_pc);
  connection.LoadDatFilePart(fileid, $A0, 4, @link_mac);
  case storetype of
    0, 1, 2:
      datasize := x * y * 2;
    8:
      datasize := x * y * 4;
    9:
      datasize := x * y div 2;
  end;
  SetLength(Result, 1);
  if not connection.OSisMac then
  begin
    Result[0].src_offset := $9C;
    Result[0].raw_addr   := link_pc;
  end
  else
  begin
    Result[0].src_offset := $A0;
    Result[0].raw_addr   := link_mac;
  end;
  Result[0].raw_size := datasize;
  Result[0].loc_sep  := connection.OSisMac;
end;




procedure InsertRawListHandler(ext: String; needed: Boolean; handler: THandler);
begin
  SetLength(RawListHandlers, Length(RawListHandlers) + 1);
  RawListHandlers[High(RawListHandlers)].Ext := ext;
  RawListHandlers[High(RawListHandlers)].needed := needed;
  RawListHandlers[High(RawListHandlers)].handler := handler;
  Raws := Raws + ext;
  AddToolListEntry('rawedit', '', ext);
end;




function LoadStructureDefinition(connection: TOniData; fileid: LongWord): TStructDef;
var
  i:      LongWord;
  current_type: Byte; //0: Global, 1: Undynamic, 2: Dynamic
  current_base, current_package, current_package_size: LongWord;
  packages: LongWord;
  deffile: Text;
  structentry: TStructure_Entry;
  fields: TStringArray;
  filename: String;
  ext:    String[4];
  temps:  String;
  Data:   TData;
begin
  SetLength(Result.Global, 0);
  SetLength(Result.Subs, 0);
  Result.Data := False;
  ext      := connection.GetFileInfo(fileid).Extension;
  filename := ExtractFilePath(Application.ExeName) + '\StructDefs\' + ext + '.txt';
  if FileExists(filename) then
  begin
    Data := connection.LoadDatFile(fileid);
    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 := HexToLong(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    := HexToLong(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[HexToLong(fields[3])];
                    2:
                      packages := Data[HexToLong(fields[3])] + Data[HexToLong(fields[3]) + 1] * 256;
                    4:
                      packages := Data[HexToLong(fields[3])] + Data[HexToLong(fields[3]) + 1] *
                        256 + Data[HexToLong(fields[3]) + 2] * 256 * 256 + Data[HexToLong(fields[3]) + 3] * 256 * 256 * 256;
                  end;
                end
                else
                begin
                  packages := HexToLong(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 := HexToLong(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 + HexToLong(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;


begin
  Raws := '';
  InsertRawListHandler('AGDB',False,AGDB);
  InsertRawListHandler('AKVA', True, AKVA);
  InsertRawListHandler('BINA', True, BINA);
  InsertRawListHandler('OSBD', True, OSBD);
  InsertRawListHandler('SNDD', True, SNDD);
  InsertRawListHandler('SUBT', True, SUBT);
  InsertRawListHandler('TRAM', True, TRAM);
  InsertRawListHandler('TXMP', True, TXMP);
end.