diff --git a/.hgignore b/.hgignore index b7cdcec..dff6d92 100644 --- a/.hgignore +++ b/.hgignore @@ -21,4 +21,5 @@ syntax: glob *.png *.pnm *.tga -*.raw \ No newline at end of file +*.raw +imgui.ini \ No newline at end of file diff --git a/model_viewer/hob_mesh.pas b/model_viewer/hob_mesh.pas index d734f31..64faa60 100644 --- a/model_viewer/hob_mesh.pas +++ b/model_viewer/hob_mesh.pas @@ -4,7 +4,7 @@ unit hob_mesh; interface uses - Classes, SysUtils, gl, GLext, math, gvector, + Classes, SysUtils, gl, GLext, math, gvector, imgui, hob_parser, hmt_parser; type @@ -50,12 +50,12 @@ type _materials: array of TMaterial; _hmt: THmtFile; _hmt_loaded: boolean; - procedure HmtRead(const filename: string); - procedure HobRead(const filename: string); + procedure HmtRead(stream: TMemoryStream); + procedure HobRead(stream: TMemoryStream); procedure HobReadMesh(const mesh: THobObject); public destructor Destroy; override; - procedure Load(const hob_filename, hmt_filename: string); + procedure Load(hob, hmt: TMemoryStream); procedure InitGL; procedure DrawGL(opts: TRenderOpts); procedure ExportObj(const obj_name: string); @@ -150,12 +150,12 @@ begin end; -procedure TModel.HobRead(const filename: string); +procedure TModel.HobRead(stream: TMemoryStream); var i: Integer; hob: THobFile; begin - hob := ParseHobFile(filename); + hob := ParseHobFile(stream); for i := 0 to 0 do HobReadMesh(hob.objects[i]); WriteLn('vertices: ', _vertices.Size); @@ -163,7 +163,7 @@ begin end; -procedure TModel.HmtRead(const filename: string); +procedure TModel.HmtRead(stream: TMemoryStream); procedure SetTexByName (var mat: TMaterial; const name: string); var i: integer; @@ -192,7 +192,7 @@ procedure TModel.HmtRead(const filename: string); var i: integer; begin - _hmt := ParseHmtFile(filename); + _hmt := ParseHmtFile(stream); SetLength(_materials, _hmt.material_count); for i := 0 to _hmt.material_count - 1 do SetTexByName(_materials[i], _hmt.materials[i].name_string); @@ -205,19 +205,15 @@ begin // _triangles.Free; end; -procedure TModel.Load(const hob_filename, hmt_filename: string); +procedure TModel.Load(hob, hmt: TMemoryStream); begin _vertices := TVertexList.Create; //_triangles := TTriangleList.Create; - WriteLn('Loading mesh file ', hob_filename); - HobRead(hob_filename); - if FileExists(hmt_filename) then begin - WriteLn('Loading material file ', hmt_filename); - HmtRead(hmt_filename); - _hmt_loaded := true; - end else begin - _hmt_loaded := false; - end; + WriteLn('Loading mesh file'); + HobRead(hob); + WriteLn('Loading material file'); + HmtRead(hmt); + _hmt_loaded := true; end; procedure pnm_save(const fname: string; const p: pbyte; const w, h: integer); @@ -286,6 +282,7 @@ procedure TModel.DrawGL(opts: TRenderOpts); var vert: TVertex; i, k: integer; + triangle_count: integer = 0; procedure DrawTri(tri: TTriangle); var @@ -309,13 +306,12 @@ var glVertex3fv(@tri.vertices[k]); end; glEnd; + triangle_count += 1; end; begin if opts.wireframe then - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) - else - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); if opts.points then begin @@ -333,6 +329,13 @@ begin //k := min(opts.fg_to_draw, Length(_triangles) - 1); for i := 0 to _triangles[k].Size - 1 do DrawTri(_triangles[k][i]); + + if opts.wireframe then + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + igBegin('Mesh'); + ImguiText(Format('triangles: %d (vertices: %d)', [triangle_count, _vertices.Size])); + igEnd; end; diff --git a/model_viewer/imgui/imgui.pas b/model_viewer/imgui/imgui.pas index a223bb6..f696148 100644 --- a/model_viewer/imgui/imgui.pas +++ b/model_viewer/imgui/imgui.pas @@ -35,7 +35,6 @@ type PImGuiContext = Pointer; PImGuiSizeConstraintCallbackData = ^ImGuiSizeConstraintCallbackData; PImGuiStorage = ^ImGuiStorage; - PImGuiStyle = ^ImGuiStyle; PImGuiTextEditCallbackData = ^ImGuiTextEditCallbackData; ImVec2 = record @@ -98,7 +97,83 @@ type //ImGuiKey_COUNT // this is unnecessary, if we declare KeyMap as array[ImGuiKey_] ); - //structs + ImGuiCol_ = ( + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, + ImGuiCol_ChildWindowBg, + ImGuiCol_PopupBg, + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_TitleBgActive, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_ComboBg, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Column, + ImGuiCol_ColumnHovered, + ImGuiCol_ColumnActive, + ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_CloseButton, + ImGuiCol_CloseButtonHovered, + ImGuiCol_CloseButtonActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TextSelectedBg, + ImGuiCol_ModalWindowDarkening + //ImGuiCol_COUNT - unnecessary + ); + + + { structs } + ImGuiStyle = record + Alpha : single; + WindowPadding : ImVec2; + WindowMinSize : ImVec2; + WindowRounding : single; + WindowTitleAlign : ImVec2; + ChildWindowRounding : single; + FramePadding : ImVec2; + FrameRounding : single; + ItemSpacing : ImVec2; + ItemInnerSpacing : ImVec2; + TouchExtraPadding : ImVec2; + IndentSpacing : single; + ColumnsMinSpacing : single; + ScrollbarSize : single; + ScrollbarRounding : single; + GrabMinSize : single; + GrabRounding : single; + ButtonTextAlign : ImVec2; + DisplayWindowPadding : ImVec2; + DisplaySafeAreaPadding : ImVec2; + AntiAliasedLines : bool; + AntiAliasedShapes : bool; + CurveTessellationTol : single; + Colors: array[ImGuiCol_] of ImVec4; + end; + PImGuiStyle = ^ImGuiStyle; + ImGuiIO = record DisplaySize : ImVec2; DeltaTime : single; @@ -194,8 +269,6 @@ type TotalIdxCount: integer; end; - ImGuiStyle = record - end; ImGuiTextEditCallbackData = record end; ImGuiSizeConstraintCallbackData = record @@ -702,6 +775,9 @@ procedure ImDrawList_UpdateTextureID(list: PImDrawList); cdecl; external ImguiLi //binding helpers function ImVec2Init(const x, y: single): Imvec2; inline; +procedure ImguiText(const s: string); inline; +function ImguiSelectable(const s: string; const selected: boolean): boolean; inline; + implementation function ImVec2Init(const x, y: single): Imvec2; @@ -710,4 +786,14 @@ begin result.y := y; end; +procedure ImguiText(const s: string); +begin + igText(pchar(s)); +end; + +function ImguiSelectable(const s: string; const selected: boolean): boolean; +begin + result := igSelectable(pchar(s), selected, 0, ImVec2Init(0,0)); +end; + end. diff --git a/model_viewer/model_viewer.lpi b/model_viewer/model_viewer.lpi index ff9130e..250025c 100644 --- a/model_viewer/model_viewer.lpi +++ b/model_viewer/model_viewer.lpi @@ -34,7 +34,7 @@ - + @@ -67,6 +67,10 @@ + + + + diff --git a/model_viewer/model_viewer.pas b/model_viewer/model_viewer.pas index 06587bf..efeb08b 100644 --- a/model_viewer/model_viewer.pas +++ b/model_viewer/model_viewer.pas @@ -19,9 +19,9 @@ along with RS model viewer. If not, see . program model_viewer; uses - sysutils, math, + sysutils, classes, math, strutils, gvector, gl, glu, glext, sdl2, imgui, imgui_impl_sdlgl2, - hob_mesh; + rs_dat, hob_mesh; const SCR_W_fscrn = 1024; @@ -35,11 +35,23 @@ const PitchIncrement = 0.5; MouseTranslateMultiply = 0.025; +type + TFileListItem = record + name: string; + node_hob: PRsDatFileNode; + node_hmt: PRsDatFileNode; + end; + TFileList = specialize TVector; + var g_window: PSDL_Window; g_ogl_context: TSDL_GLContext; - model: TModel; + g_rsdata: TRSDatFile; + g_filelist: TFileList; + g_model: TModel; + g_model_name: string; + g_model_loading_failed: Boolean; view: record rotation_angle: single; @@ -132,9 +144,10 @@ begin if view.rotation_angle > 360 then view.rotation_angle -= 360; - model.DrawGL(view.opts); + if g_model <> nil then begin + g_model.DrawGL(view.opts); + end; - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); igRender; SDL_GL_SwapWindow(g_window); @@ -272,7 +285,7 @@ begin view.autorotate := not view.autorotate; key_pressed.autorotate := true; end; - //model rendering opts + //g_model rendering opts SDLK_w: if not key_pressed.wireframe then begin view.opts.wireframe := not view.opts.wireframe; @@ -362,22 +375,137 @@ begin end; {case} end; +procedure LoadMesh(item: TFileListItem); +var + hob, hmt: TMemoryStream; +begin + if g_model <> nil then + g_model.Free; + hob := TMemoryStream.Create; + hob.WriteBuffer(item.node_hob^.Data^, item.node_hob^.size); + hob.Seek(0, soBeginning); + + hmt := TMemoryStream.Create; + hmt.WriteBuffer(item.node_hmt^.Data^, item.node_hmt^.size); + hmt.Seek(0, soBeginning); + + g_model := TModel.Create; + g_model.Load(hob, hmt); + g_model.InitGL; +end; + + +procedure DrawGui; +var + showtest: bool; + style: PImGuiStyle; + file_item: TFileListItem; + fitem_selected: Boolean = false; + selected_mesh_name: String; + selected_item: TFileListItem; +begin + ImGui_ImplSdlGL2_NewFrame(g_window); + + style := igGetStyle(); + style^.WindowRounding := 0; + + igBegin('Mesh'); + if not g_model_loading_failed then begin + ImguiText(g_model_name); + end else + ImguiText('mesh loading failed :('); + igEnd; + + igBegin('Rendering options'); + igCheckbox('points', @view.opts.points); + igCheckbox('wireframe', @view.opts.wireframe); + igCheckbox('textures', @view.opts.textures); + igCheckbox('vertex colors', @view.opts.vcolors); + if g_model <> nil then + if igButton('Export to obj', ImVec2Init(0,0)) then + g_model.ExportObj('rs_exported.obj'); + igEnd; + + selected_mesh_name := EmptyStr; + igBegin('File list'); + //todo filter + for file_item in g_filelist do begin + fitem_selected := file_item.name = g_model_name; + if ImguiSelectable(file_item.name, fitem_selected) then begin + selected_mesh_name := file_item.name; + selected_item := file_item; + end; + end; + igEnd; + + //igShowTestWindow(@showtest); + + if (selected_mesh_name <> EmptyStr) and (selected_mesh_name <> g_model_name) then begin + try + LoadMesh(selected_item); + g_model_name := selected_mesh_name; + g_model_loading_failed := false; + except + g_model_loading_failed := true; + end; + end; +end; + +//we only care about HOB and HMT files +procedure LoadMeshFilelist; + procedure AddFile(const path: string; const fnode, fnode_next: PRsDatFileNode); + var + item: TFileListItem; + name: String; + i: integer; + begin + if fnode^.is_directory then begin + for i := 0 to Length(fnode^.nodes)-2 do //hob/hmt always go in pairs + AddFile(path + fnode^.Name + '/', fnode^.nodes[i], fnode^.nodes[i+1]); + end + else begin + name := fnode^.name; + if AnsiEndsText('hob', name) then begin + Assert(AnsiEndsText('hmt', fnode_next^.name), 'no HMT file!'); + item.name := path + name; + item.node_hob := fnode; + item.node_hmt := fnode_next; + g_filelist.PushBack(item); + end; + end; + end; +var + rs_files: TRsDatFileNodeList; + file_: TRsDatFileNode; +begin + rs_files := g_rsdata.GetStructure(); + for file_ in rs_files do begin + AddFile('', @file_, nil); + end; +end; + + //****************************************************************************** var sec, frames: integer; event: TSDL_Event; done: boolean; - hob_file, hmt_file, obj_file: string; begin - if Paramcount < 1 then begin - writeln('specify HOB file'); + if not (FileExists(RS_DATA_HDR) and FileExists(RS_DATA_DAT)) then begin + writeln('RS data files not found!'); exit; end; - hob_file := ParamStr(1); - hmt_file := StringReplace(hob_file, '.hob', '.hmt', [rfIgnoreCase]); - model := TModel.Create; - model.Load(hob_file, hmt_file); + + writeln('loading data'); + g_rsdata := TRSDatFile.Create(RS_DATA_HDR, RS_DATA_DAT); + g_rsdata.Parse(); + g_filelist := TFileList.Create; + LoadMeshFilelist(); + + g_model := nil; + g_model_name := ''; + g_model_loading_failed := false; writeln('Init SDL...'); SDL_Init(SDL_INIT_VIDEO or SDL_INIT_TIMER); @@ -387,11 +515,6 @@ begin SetGLWindowSize(g_window^.w, g_window^.h); InitView; - model.InitGL; - - //export - //obj_file := StringReplace(hob_file, '.hob', '.obj', [rfIgnoreCase]); - //model.ExportObj(obj_file); sec := SDL_GetTicks; frames := 0; @@ -399,15 +522,8 @@ begin key_pressed.wireframe := false; key_pressed.fullscreen := false; while not Done do begin - ImGui_ImplSdlGL2_NewFrame(g_window); - - igBegin('rendering options'); - igCheckbox('points', @view.opts.points); - igCheckbox('wireframe', @view.opts.wireframe); - igCheckbox('textures', @view.opts.textures); - igCheckbox('vertex colors', @view.opts.vcolors); - igEnd; + DrawGui; DrawGLScene; while SDL_PollEvent(@event) > 0 do @@ -419,13 +535,16 @@ begin frames := 0; sec := SDL_GetTicks; end; - SDL_Delay(10); + SDL_Delay(5); //WindowScreenshot( surface^.w, surface^.h ); end; - model.Free; - WindowFree; SDL_Quit; + + if g_model <> nil then + g_model.Free; + g_filelist.Free; + g_rsdata.Free; end. diff --git a/rs_units/hmt_parser.pas b/rs_units/hmt_parser.pas index 4890e84..a82d7a6 100644 --- a/rs_units/hmt_parser.pas +++ b/rs_units/hmt_parser.pas @@ -36,7 +36,7 @@ type textures: array of THmtTexture; end; - function ParseHmtFile(const fname: string): THmtFile; + function ParseHmtFile(f: TMemoryStream): THmtFile; //************************************************************************************************** implementation @@ -138,15 +138,11 @@ begin end; -function ParseHmtFile(const fname: string): THmtFile; +function ParseHmtFile(f: TMemoryStream): THmtFile; var - f: TMemoryStream; hmt: THmtFile; i: Integer; begin - f := TMemoryStream.Create; - f.LoadFromFile(fname); - //read main info hmt.material_count := f.ReadDWord; hmt.texture_offset := f.ReadDWord; @@ -175,7 +171,6 @@ begin ReadTexture(hmt.textures[i], f); end; - f.Free; result := hmt; end; diff --git a/rs_units/hob_parser.pas b/rs_units/hob_parser.pas index 6d34f4f..ab89944 100644 --- a/rs_units/hob_parser.pas +++ b/rs_units/hob_parser.pas @@ -64,7 +64,7 @@ type objects: array of THobObject; end; -function ParseHobFile(const fname: string): THobFile; +function ParseHobFile(f: TMemoryStream): THobFile; //************************************************************************************************** implementation @@ -323,16 +323,12 @@ begin end; -function ParseHobFile(const fname: string): THobFile; +function ParseHobFile(f: TMemoryStream): THobFile; var - f: TMemoryStream; hob: THobFile; i: integer; filepos: int64; begin - f := TMemoryStream.Create; - f.LoadFromFile(fname); - hob.obj_count := f.ReadDWord; f.ReadDWord; //sometimes face block start, but useless in general @@ -353,7 +349,6 @@ begin f.Seek(filepos + 116, fsFromBeginning); end; - f.Free; result := hob; end; diff --git a/rs_units/rs_dat.pas b/rs_units/rs_dat.pas new file mode 100644 index 0000000..c4c155c --- /dev/null +++ b/rs_units/rs_dat.pas @@ -0,0 +1,304 @@ +{ +Loads DATA.DAT/HDR files to memory and parses the file structure. +RS most likely loads just the file structure and the actual file data gets loaded as needed. +We have plenty of ram nowadays, so read files just once and then load from memory. +} +unit rs_dat; +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, gvector; + +const + RS_DATA_HDR = 'DATA.HDR'; + RS_DATA_DAT = 'DATA.DAT'; + RS_DATA_FEDirectoryFlag = %10000000; + +type + //data file entry + TRsDatFileEntry = packed record + offset: longword; + length: longword; + padding: longword; + type_flag: word; + sub_entry_size: word; + filename: array[0..15] of char; + end; + PRsDatFileEntry = ^TRsDatFileEntry; + + //file or directory node + PRsDatFileNode = ^TRsDatFileNode; + TRsDatFileNode = record + Name: string; + is_directory: boolean; + subentries_count: integer; + offset: longword; + size: longword; + entry: TRsDatFileEntry; + Data: pbyte; + nodes: array of PRsDatFileNode; //children if directory + end; + + //header file entry + TRsHdrSection = record + Name: string; //section name + dat_offset: integer; //offset in dat file + Data: pbyte; //section's data pointer + nodes: array of TRsDatFileNode; //all file entries / nodes + root: TRsDatFileNode; //tree structure of nodes + end; + TRsDatFileNodeList = specialize TVector; + + { TRSDatFile } + + TRSDatFile = class + public + constructor Create(const hdr_file, dat_file: string); //loads header & data files to memory + destructor Destroy; override; //cleanup loaded memory + procedure Parse(); //parse file structure + function GetStructure(): TRsDatFileNodeList; //list of sections, list must be freed by user + procedure WriteFilesToDirectory(const path: string); + private + m_sections: array of TRsHdrSection; + m_data: pbyte; + + procedure ReadHeaderFile(const fname: string); + procedure ReadDatFile(const fname: string); + function ReadEntry(const stream: TMemoryStream; const mem_data: pbyte): TRsDatFileNode; + procedure ReadSectionEntries(var section: TRsHdrSection; const mem_data: pbyte); + procedure ParseSectionStructure(var section: TRsHdrSection); + end; + +function CountSubNodes(node: PRsDatFileNode): integer; +function CountSubNodeSizes(node: PRsDatFileNode): integer; + + +//************************************************************************************************** +implementation + +function CountSubNodes(node: PRsDatFileNode): integer; +var + i: integer; +begin + Result := 1; + if node^.is_directory then + for i := 0 to Length(node^.nodes) - 1 do + Result += CountSubNodes(node^.nodes[i]); +end; + +function CountSubNodeSizes(node: PRsDatFileNode): integer; +var + i: integer; +begin + Result := node^.size; + if node^.is_directory then + for i := 0 to Length(node^.nodes) - 1 do + Result += CountSubNodeSizes(node^.nodes[i]); +end; + +procedure SaveFile(const name: string; const buffer: pbyte; const buf_size: integer); +var + f: file; + fname: string; +begin + fname := name; + AssignFile(f, fname); + Rewrite(f, 1); + BlockWrite(f, buffer^, buf_size); + CloseFile(f); +end; + +{ TRSDatFile } + +constructor TRSDatFile.Create(const hdr_file, dat_file: string); +begin + ReadHeaderFile(hdr_file); + ReadDatFile(dat_file); +end; + +destructor TRSDatFile.Destroy; +begin + inherited Destroy; + m_sections := nil; + freemem(m_data); +end; + +procedure TRSDatFile.Parse; +var + i: integer; +begin + for i := 0 to length(m_sections) - 1 do begin + Writeln('reading section ', m_sections[i].name); + ReadSectionEntries(m_sections[i], m_data + m_sections[i].dat_offset); + ParseSectionStructure(m_sections[i]); + end; +end; + +function TRSDatFile.GetStructure: TRsDatFileNodeList; +var + i: Integer; +begin + Assert(Length(m_sections) > 0, 'data not parsed'); + result := TRsDatFileNodeList.Create; + for i := 0 to length(m_sections) - 1 do + result.PushBack(m_sections[i].root); +end; + +procedure TRSDatFile.ReadHeaderFile(const fname: string); +var + f: file; + section: TRsHdrSection; + section_n: integer; + i: integer; + buffer: array[0..15] of char; +begin + assignfile(f, fname); + reset(f, 1); + section_n := FileSize(f) div 32; + SetLength(m_sections, section_n); + + for i := 0 to section_n - 1 do begin + blockread(f, buffer, 16); //name + section.name := trim(buffer); + blockread(f, buffer, 12); //empty + blockread(f, section.dat_offset, 4); //offset in m_data.DAT + m_sections[i] := section; + end; + + closefile(f); +end; + +procedure TRSDatFile.ReadDatFile(const fname: string); +var + f: file; + fsize: integer; +begin + AssignFile(f, fname); + reset(f, 1); + fsize := FileSize(f); + m_data := getmem(fsize); + Blockread(f, m_data^, fsize); + closefile(f); +end; + +function TRSDatFile.ReadEntry(const stream: TMemoryStream; const mem_data: pbyte): TRsDatFileNode; +var + entry: TRsDatFileEntry; +begin + stream.ReadBuffer(entry, 32); + result.name := Trim(entry.filename); + result.offset := entry.offset; + result.size := entry.length; + result.is_directory := (entry.type_flag and RS_DATA_FEDirectoryFlag) <> 0; + result.subentries_count := entry.sub_entry_size div 32 - 1; + result.entry := entry; + result.data := nil; + if not result.is_directory then + result.Data := mem_data + entry.offset; + + //if (result.offset mod 32) <> 0 then writeln('unaligned offset'); + writeln(stderr, format('name: %s size: %d dir: %s subsize: %d flags: %s', + [result.Name, entry.length, BoolToStr(result.is_directory), + entry.sub_entry_size, binStr(entry.type_flag, 16)])); +end; + +procedure TRSDatFile.ReadSectionEntries(var section: TRsHdrSection; const mem_data: pbyte); +var + entries_offset: integer; + entries_size: integer; + entry_count: integer; + stream: TMemoryStream; + i: integer; +begin + section.data := mem_data; + entries_offset := (pinteger(mem_data))^; //offset relative to section beginning + entries_size := (pinteger(mem_data + 4))^; //length in bytes + entry_count := entries_size div 32; //actual count of entries + writeln('entries: ', entry_count); + + //load entries to stream for easier parsing + stream := TMemoryStream.Create; + stream.WriteBuffer((mem_data + entries_offset)^, entries_size); + stream.Seek(0, soBeginning); + + SetLength(section.nodes, entry_count); + for i := 0 to entry_count - 1 do begin + section.nodes[i] := ReadEntry(stream, mem_data); + end; + + stream.Free; +end; + + +procedure AddNode(const parent: PRsDatFileNode; var nodes: array of TRsDatFileNode; var node_index: integer); +var + i: integer; + node: PRsDatFileNode; +begin + if node_index > length(nodes) - 1 then + exit; + + //add current to parent + node := @nodes[node_index]; + i := length(parent^.nodes); + Setlength(parent^.nodes, i + 1); + parent^.nodes[i] := node; + //writeln('added node: ', node^.name, ' to parent ', parent^.name); + + //add all subentries if any + if node^.is_directory then begin + //writeln(' subentries: ', node^.subentries_count); + while CountSubNodes(node) < node^.subentries_count + 1 do begin + node_index += 1; + AddNode(node, nodes, node_index); + end; + end; +end; + +procedure TRSDatFile.ParseSectionStructure(var section: TRsHdrSection); +var + node_idx: integer = 0; +begin + section.root.name := section.name; + section.root.is_directory := true; + section.root.data := nil; + while node_idx < length(section.nodes) do begin + AddNode(@section.root, section.nodes, node_idx); + node_idx += 1; + end; +end; + + +procedure WriteDirectory(const node: PRsDatFileNode; const path: string; const data: pbyte); +var + dir: string; + subnode: PRsDatFileNode; + i: Integer; +begin + dir := path + node^.name; + if not DirectoryExists(dir) then + MkDir(dir); + for i := 0 to length(node^.nodes) - 1 do begin + subnode := node^.nodes[i]; + if subnode^.is_directory then + WriteDirectory(subnode, dir + DirectorySeparator, data) + else + SaveFile(dir + DirectorySeparator + subnode^.name, data + subnode^.offset, subnode^.size); + end; +end; + +procedure TRSDatFile.WriteFilesToDirectory(const path: string); +var + section: TRsHdrSection; +begin + for section in m_sections do begin + WriteDirectory(@section.root, path, section.data); + end; +end; + + + +end. +