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