2
0
mirror of https://github.com/dpethes/rerogue.git synced 2025-06-07 18:58:32 +02:00

model viewer: load models directly from DAT file

Displays a filelist with hob files, use it to select a mesh. Also, hob and hmt
parsers now load data directly from memory streams.
This commit is contained in:
dpethes 2017-02-02 02:05:33 +01:00
parent a2b074d9e8
commit 1aff4888ab
8 changed files with 577 additions and 70 deletions

View File

@ -22,3 +22,4 @@ syntax: glob
*.pnm
*.tga
*.raw
imgui.ini

View File

@ -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);
WriteLn('Loading mesh file');
HobRead(hob);
WriteLn('Loading material file');
HmtRead(hmt);
_hmt_loaded := true;
end else begin
_hmt_loaded := false;
end;
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;

View File

@ -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.

View File

@ -34,7 +34,7 @@
<PackageName Value="LCL"/>
</Item1>
</RequiredPackages>
<Units Count="8">
<Units Count="9">
<Unit0>
<Filename Value="model_viewer.pas"/>
<IsPartOfProject Value="True"/>
@ -67,6 +67,10 @@
<Filename Value="imgui\imgui_impl_sdlgl2.pas"/>
<IsPartOfProject Value="True"/>
</Unit7>
<Unit8>
<Filename Value="..\rs_units\rs_dat.pas"/>
<IsPartOfProject Value="True"/>
</Unit8>
</Units>
</ProjectOptions>
<CompilerOptions>

View File

@ -19,9 +19,9 @@ along with RS model viewer. If not, see <http://www.gnu.org/licenses/>.
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<TFileListItem>;
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.

View File

@ -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;

View File

@ -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;

304
rs_units/rs_dat.pas Normal file
View File

@ -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<TRsDatFileNode>;
{ 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.