0
mirror of https://github.com/sampletext32/ParkanPlayground.git synced 2025-05-18 19:31:17 +03:00

create NResUI

This commit is contained in:
bird_egop 2024-11-15 19:06:44 +03:00
parent 0c39485188
commit 1091605e2d
22 changed files with 869 additions and 100 deletions

45
NResLib/NResArchive.cs Normal file
View File

@ -0,0 +1,45 @@
namespace NResLib;
/// <summary>
/// Архив NRes (файл NRes)
/// </summary>
public record NResArchive(NResArchiveHeader Header, List<ListMetadataItem> Files);
/// <summary>
/// Заголовок файла
/// </summary>
/// <param name="NRes">[0..4] ASCII NRes</param>
/// <param name="Version">[4..8] Версия кодировщика (должно быть всегда 0x100)</param>
/// <param name="FileCount">[8..12] Количество файлов </param>
/// <param name="TotalFileLengthBytes">[12..16] Длина всего архива</param>
public record NResArchiveHeader(string NRes, int Version, int FileCount, int TotalFileLengthBytes);
/// <summary>
/// В конце файла есть список метаданных,
/// каждый элемент это 64 байта,
/// найти начало можно как (Header.TotalFileLengthBytes - Header.FileCount * 64)
/// </summary>
/// <param name="FileType">[0..8] ASCII описание типа файла, например TEXM или MAT0</param>
/// <param name="Magic1">[8..12] Неизвестное число</param>
/// <param name="FileLength">[12..16] Длина файла в байтах</param>
/// <param name="Magic2">[16..20] Неизвестное число</param>
/// <param name="FileName">[20..40] ASCII имя файла</param>
/// <param name="Magic3">[40..44] Неизвестное число</param>
/// <param name="Magic4">[44..48] Неизвестное число</param>
/// <param name="Magic5">[48..52] Неизвестное число</param>
/// <param name="Magic6">[52..56] Неизвестное число</param>
/// <param name="OffsetInFile">[56..60] Смещение подфайла от начала NRes (именно самого NRes) в байтах</param>
/// <param name="Index">[60..64] Индекс в файле (от 0, не больше чем кол-во файлов)</param>
public record ListMetadataItem(
string FileType,
int Magic1,
int FileLength,
int Magic2,
string FileName,
int Magic3,
int Magic4,
int Magic5,
int Magic6,
int OffsetInFile,
int Index
);

9
NResLib/NResLib.csproj Normal file
View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,3 @@
namespace NResLib;
public record NResParseResult(NResArchive? Archive = null, string? Error = null);

78
NResLib/NResParser.cs Normal file
View File

@ -0,0 +1,78 @@
using System.Buffers.Binary;
using System.Text;
namespace NResLib;
public static class NResParser
{
public static NResParseResult ReadFile(string path)
{
using FileStream nResFs = new FileStream(path, FileMode.Open);
if (nResFs.Length < 16)
{
return new NResParseResult(null, "Файл не может быть NRes, менее 16 байт");
}
Span<byte> buffer = stackalloc byte[16];
nResFs.ReadExactly(buffer);
if (buffer[0] != 'N' || buffer[1] != 'R' || buffer[2] != 'e' || buffer[3] != 's')
{
return new NResParseResult(null, "Файл не начинается с NRes");
}
var header = new NResArchiveHeader(
NRes: Encoding.ASCII.GetString(buffer[0..4]),
Version: BinaryPrimitives.ReadInt32LittleEndian(buffer[4..8]),
FileCount: BinaryPrimitives.ReadInt32LittleEndian(buffer[8..12]),
TotalFileLengthBytes: BinaryPrimitives.ReadInt32LittleEndian(buffer[12..16])
);
if (header.TotalFileLengthBytes != nResFs.Length)
{
return new NResParseResult(
null,
$"Длина файла не совпадает с заявленным в заголовке.\n" +
$"Заявлено: {header.TotalFileLengthBytes}\n" +
$"Фактически: {nResFs.Length}"
);
}
nResFs.Seek(-header.FileCount * 64, SeekOrigin.End);
var elements = new List<ListMetadataItem>(header.FileCount);
Span<byte> metaDataBuffer = stackalloc byte[64];
for (int i = 0; i < header.FileCount; i++)
{
nResFs.ReadExactly(metaDataBuffer);
elements.Add(
new ListMetadataItem(
FileType: Encoding.ASCII.GetString(metaDataBuffer[..8]),
Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]),
FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]),
Magic2: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]),
FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]),
Magic3: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[40..44]),
Magic4: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[44..48]),
Magic5: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[48..52]),
Magic6: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[52..56]),
OffsetInFile: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[56..60]),
Index: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[60..64])
)
);
metaDataBuffer.Clear();
}
return new NResParseResult(
new NResArchive(
Header: header,
Files: elements
)
);
}
}

View File

@ -0,0 +1,7 @@
namespace NResUI.Abstractions
{
public interface IExitReceiver
{
void OnExit();
}
}

View File

@ -0,0 +1,7 @@
namespace NResUI.Abstractions
{
public interface IImGuiPanel
{
void OnImGuiRender();
}
}

View File

@ -0,0 +1,17 @@
using Silk.NET.Input;
namespace NResUI.Abstractions
{
public interface IKeyPressReceiver
{
void OnKeyPressed(Key key);
}
public interface IKeyReleaseReceiver
{
void OnKeyReleased(Key key);
}
public interface IKeyDownReceiver
{
void OnKeyDown(Key key);
}
}

View File

@ -0,0 +1,6 @@
namespace NResUI.Abstractions
{
public interface IService
{
}
}

View File

@ -0,0 +1,10 @@
namespace NResUI.Abstractions
{
public interface IUpdateReceiver
{
/// <summary>
/// Called before every UI render
/// </summary>
void OnUpdate(float delta);
}
}

140
NResUI/App.cs Normal file
View File

@ -0,0 +1,140 @@
using System.Numerics;
using ImGuiNET;
using Microsoft.Extensions.DependencyInjection;
using NResUI.Abstractions;
using NResUI.Models;
using Silk.NET.Input;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
namespace NResUI;
public class App
{
public GL GL { get; set; }
public IInputContext Input { get; set; }
public static App Instance;
private static bool _dockspaceOpen = true;
private static bool _optFullscreenPersistant = true;
private static bool _optFullscreen = _optFullscreenPersistant;
private static ImGuiDockNodeFlags _dockspaceFlags = ImGuiDockNodeFlags.None;
public ImFontPtr OpenSansFont;
private List<IImGuiPanel> _imGuiPanels;
public App()
{
Instance = this;
}
public void Init(IWindow window, GL openGl, ImFontPtr openSansFont)
{
ImGui.StyleColorsLight();
IServiceCollection serviceCollection = new ServiceCollection();
foreach (var type in Utils.GetAssignableTypes<IService>())
{
serviceCollection.AddSingleton(type);
}
foreach (var type in Utils.GetAssignableTypes<IImGuiPanel>())
{
serviceCollection.AddSingleton(type);
}
serviceCollection.AddSingleton(new ExplorerViewModel());
var serviceProvider = serviceCollection.BuildServiceProvider();
_imGuiPanels = Utils.GetAssignableTypes<IImGuiPanel>()
.Select(t => (serviceProvider.GetService(t) as IImGuiPanel)!)
.ToList();
}
public void OnImGuiRender()
{
ImGui.PushFont(OpenSansFont);
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
// because it would be confusing to have two docking targets within each others.
var windowFlags = ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoDocking;
if (_optFullscreen)
{
var viewport = ImGui.GetMainViewport();
ImGui.SetNextWindowPos(viewport.Pos);
ImGui.SetNextWindowSize(viewport.Size);
ImGui.SetNextWindowViewport(viewport.ID);
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0.0f);
ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0.0f);
windowFlags |= ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize |
ImGuiWindowFlags.NoMove;
windowFlags |= ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus;
}
// When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background and handle the pass-thru hole, so we ask Begin() to not render a background.
if ((_dockspaceFlags & ImGuiDockNodeFlags.PassthruCentralNode) != 0)
windowFlags |= ImGuiWindowFlags.NoBackground;
// Important: note that we proceed even if Begin() returns false (aka window is collapsed).
// This is because we want to keep our DockSpace() active. If a DockSpace() is inactive,
// all active windows docked into it will lose their parent and become undocked.
// We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
// any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0.0f, 0.0f));
ImGui.Begin("DockSpace Demo", ref _dockspaceOpen, windowFlags);
ImGui.PopStyleVar();
if (_optFullscreen)
ImGui.PopStyleVar(2);
// DockSpace
var io = ImGui.GetIO();
var style = ImGui.GetStyle();
var minWinSizeX = style.WindowMinSize.X;
style.WindowMinSize.X = 370.0f;
if ((io.ConfigFlags & ImGuiConfigFlags.DockingEnable) != 0)
{
var dockspaceId = ImGui.GetID("MyDockSpace");
ImGui.DockSpace(dockspaceId, new Vector2(0.0f, 0.0f), _dockspaceFlags);
}
style.WindowMinSize.X = minWinSizeX;
foreach (var imGuiPanel in _imGuiPanels)
{
imGuiPanel.OnImGuiRender();
}
ImGui.ShowMetricsWindow();
ImGui.ShowDemoWindow();
ImGui.PopFont();
ImGui.End();
}
public void Exit()
{
}
public void Update(double delta)
{
}
public void OnKeyPressed(Key key)
{
}
public void OnKeyDown(Key key)
{
}
public void OnKeyReleased(Key key)
{
}
}

View File

@ -0,0 +1,127 @@
using ImGuiNET;
using NResUI.Abstractions;
using NResUI.Models;
namespace NResUI.ImGuiUI;
public class ExplorerPanel : IImGuiPanel
{
private readonly ExplorerViewModel _viewModel;
public ExplorerPanel(ExplorerViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnImGuiRender()
{
if (ImGui.Begin("Explorer"))
{
if (!_viewModel.HasFile)
{
ImGui.Text("No NRes is opened");
}
else
{
if (_viewModel.Error != null)
{
ImGui.Text(_viewModel.Error);
}
if (_viewModel.Archive is not null)
{
ImGui.Text(_viewModel.Path);
ImGui.Text("Header: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.NRes);
ImGui.Text("Version: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.Version.ToString());
ImGui.Text("File Count: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.FileCount.ToString());
ImGui.Text("Total File Length: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
if (ImGui.BeginTable("content", 11))
{
ImGui.TableSetupColumn("Тип файла");
ImGui.TableSetupColumn("Magic1");
ImGui.TableSetupColumn("Длина файла в байтах");
ImGui.TableSetupColumn("Magic2");
ImGui.TableSetupColumn("Имя файла");
ImGui.TableSetupColumn("Magic3");
ImGui.TableSetupColumn("Magic4");
ImGui.TableSetupColumn("Magic5");
ImGui.TableSetupColumn("Magic6");
ImGui.TableSetupColumn("Смещение в байтах");
ImGui.TableSetupColumn("Индекс в файле");
ImGui.TableHeadersRow();
for (int i = 0; i < _viewModel.Archive.Files.Count; i++)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(_viewModel.Archive.Files[i].FileType);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic1.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.FileLength.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic2.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(_viewModel.Archive.Files[i].FileName);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic3.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic4.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic5.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic6.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.OffsetInFile.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Index.ToString()
);
}
ImGui.EndTable();
}
}
}
ImGui.End();
}
}
}

View File

@ -0,0 +1,39 @@
using System.Numerics;
using ImGuiNET;
using NResUI.Abstractions;
namespace NResUI.ImGuiUI;
public abstract class ImGuiModalPanel : IImGuiPanel
{
protected abstract string ImGuiId { get; }
private bool _shouldOpen = false;
public virtual void Open()
{
_shouldOpen = true;
}
protected abstract void OnImGuiRenderContent();
public void OnImGuiRender()
{
// this is a ImGui stack fix. Because menubars and some other controls use their separate stack context,
// The panel gets rendered on it's own, at the root of the stack, and with _shouldOpen we control, if the panel should open this frame.
if (_shouldOpen)
{
ImGui.OpenPopup(ImGuiId, ImGuiPopupFlags.AnyPopupLevel);
_shouldOpen = false;
}
ImGui.SetNextWindowSize(new Vector2(600, 400));
if (ImGui.BeginPopup(ImGuiId, ImGuiWindowFlags.NoResize))
{
OnImGuiRenderContent();
ImGui.EndPopup();
}
}
}

View File

@ -0,0 +1,108 @@
using System.Numerics;
using ImGuiNET;
using NativeFileDialogSharp;
using NResLib;
using NResUI.Abstractions;
using NResUI.Models;
namespace NResUI.ImGuiUI
{
public class MainMenuBar : IImGuiPanel
{
private readonly ExplorerViewModel _explorerViewModel;
public MainMenuBar(ExplorerViewModel explorerViewModel)
{
_explorerViewModel = explorerViewModel;
}
public void OnImGuiRender()
{
if (ImGui.BeginMenuBar())
{
if (ImGui.BeginMenu("File"))
{
if (ImGui.MenuItem("Open NRes"))
{
var result = Dialog.FileOpen();
if (result.IsOk)
{
var path = result.Path;
var parseResult = NResParser.ReadFile(path);
_explorerViewModel.SetParseResult(parseResult, path);
}
}
if (_explorerViewModel.HasFile)
{
if (ImGui.MenuItem("Экспортировать"))
{
var result = Dialog.FolderPicker();
if (result.IsOk)
{
var path = result.Path;
Console.WriteLine(path);
}
}
}
if (ImGui.BeginMenu("Open Recent"))
{
ImGui.EndMenu();
}
if (ImGui.MenuItem("Exit"))
{
App.Instance.Exit();
}
ImGui.EndMenu();
}
if (ImGui.BeginMenu("Windows"))
{
if (ImGui.MenuItem("Settings"))
{
}
ImGui.EndMenu();
}
ImGui.EndMenuBar();
}
}
// This is a direct port of imgui_demo.cpp HelpMarker function
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L190
private void ShowHint(string message)
{
// ImGui.TextDisabled("(?)");
if (ImGui.IsItemHovered())
{
// Change background transparency
ImGui.PushStyleColor(
ImGuiCol.PopupBg,
new Vector4(
1,
1,
1,
0.8f
)
);
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f);
ImGui.TextUnformatted(message);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
ImGui.PopStyleColor();
}
}
}
}

View File

@ -0,0 +1,26 @@
using NResLib;
namespace NResUI.Models;
public class ExplorerViewModel
{
public bool HasFile { get; set; }
public string? Error { get; set; }
public NResArchive? Archive { get; set; }
public string? Path { get; set; }
public void SetParseResult(NResParseResult result, string path)
{
Error = result.Error;
if (result.Archive != null)
{
HasFile = true;
}
Archive = result.Archive;
Path = path;
}
}

25
NResUI/NResUI.csproj Normal file
View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="assets\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
<PackageReference Include="Silk.NET" Version="2.22.0" />
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NResLib\NResLib.csproj" />
</ItemGroup>
</Project>

109
NResUI/Program.cs Normal file
View File

@ -0,0 +1,109 @@
// See https://aka.ms/new-console-template for more information
using System.Drawing;
using ImGuiNET;
using NResUI;
using Silk.NET.Input;
using Silk.NET.OpenGL;
using Silk.NET.OpenGL.Extensions.ImGui;
using Silk.NET.Windowing;
var window = Window.Create(WindowOptions.Default);
// Declare some variables
ImGuiController controller = null!;
GL gl = null!;
IInputContext inputContext = null!;
var app = new App();
// Our loading function
window.Load += () =>
{
var openGl = window.CreateOpenGL();
ImFontPtr mainFont = null;
controller = new ImGuiController(
gl = openGl, // load OpenGL
window, // pass in our window
inputContext = window.CreateInput(), // create an input context
() =>
{
var io = ImGui.GetIO();
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
mainFont = io.Fonts.AddFontFromFileTTF(
filename: "assets/Font/OpenSans-Regular.ttf",
size_pixels: 18,
font_cfg: null,
glyph_ranges: io.Fonts.GetGlyphRangesCyrillic()
);
}
);
app.Init(window, openGl, mainFont);
inputContext.Keyboards[0]
.KeyDown += (keyboard, key, scancode) => { app.OnKeyDown(key); };
inputContext.Keyboards[0]
.KeyUp += (keyboard, key, scancode) => { app.OnKeyPressed(key); };
inputContext.Keyboards[0]
.KeyUp += (keyboard, key, scancode) => { app.OnKeyReleased(key); };
};
// Handle resizes
window.FramebufferResize += s =>
{
// Adjust the viewport to the new window size
gl.Viewport(s);
};
// Handles the dile drop and receives the array of paths to the files.
window.FileDrop += paths => { };
window.Update += delta =>
{
// Make sure ImGui is up-to-date
controller.Update((float) delta);
app.Update(delta);
};
// The render function
window.Render += delta =>
{
// This is where you'll do any rendering beneath the ImGui context
// Here, we just have a blank screen.
gl.ClearColor(
Color.FromArgb(
255,
(int) (.45f * 255),
(int) (.55f * 255),
(int) (.60f * 255)
)
);
gl.Clear((uint) ClearBufferMask.ColorBufferBit);
app.OnImGuiRender();
// Make sure ImGui renders too!
controller.Render();
};
// The closing function
window.Closing += () =>
{
app.Exit();
ImGui.SaveIniSettingsToDisk("imgui.ini");
// Dispose our controller first
controller?.Dispose();
// Dispose the input context
inputContext?.Dispose();
// Unload OpenGL
gl?.Dispose();
};
// Now that everything's defined, let's run this bad boy!
window.Run();

64
NResUI/Utils.cs Normal file
View File

@ -0,0 +1,64 @@
using System.Reflection;
namespace NResUI
{
public static class Utils
{
public static bool IsDirectory(this FileSystemInfo info)
{
// get the file attributes for file or directory
FileAttributes attr = info.Attributes;
//detect whether its a directory or file
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
return true;
else
return false;
}
public static bool IsDirectoryPath(this string path)
{
return Directory.Exists(path);
}
public static void ClearContent(this DirectoryInfo directoryInfo)
{
foreach (var directory in directoryInfo.EnumerateDirectories())
{
directory.Delete(true);
}
foreach (var file in directoryInfo.EnumerateFiles())
{
file.Delete();
}
}
public static IEnumerable<Type> GetAssignableTypesFromAssembly<T>(Assembly assembly)
{
return assembly.ExportedTypes
.Where(t => t.IsAssignableTo(typeof(T)) && t is {IsAbstract: false, IsInterface: false});
}
public static IList<Type> GetAssignableTypes<T>()
{
var executingAssembly = Assembly.GetExecutingAssembly();
var referencedAssemblyNames = executingAssembly.GetReferencedAssemblies();
var types = referencedAssemblyNames.SelectMany(
name =>
GetAssignableTypesFromAssembly<T>(Assembly.Load(name))
)
.Concat(
GetAssignableTypesFromAssembly<T>(executingAssembly)
)
.ToList();
return types;
}
public static bool IsNullOrEmpty(this string? str)
{
return string.IsNullOrEmpty(str);
}
}
}

Binary file not shown.

View File

@ -6,6 +6,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextureDecoder", "TextureDe
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLUnpacker", "NLUnpacker\NLUnpacker.csproj", "{50C83E6C-23ED-4A8E-B948-89686A742CF0}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NLUnpacker", "NLUnpacker\NLUnpacker.csproj", "{50C83E6C-23ED-4A8E-B948-89686A742CF0}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResUI", "NResUI\NResUI.csproj", "{7456A089-0701-416C-8668-1F740BF4B72C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NResLib", "NResLib\NResLib.csproj", "{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -24,5 +28,13 @@ Global
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {50C83E6C-23ED-4A8E-B948-89686A742CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.Build.0 = Release|Any CPU {50C83E6C-23ED-4A8E-B948-89686A742CF0}.Release|Any CPU.Build.0 = Release|Any CPU
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7456A089-0701-416C-8668-1F740BF4B72C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7456A089-0701-416C-8668-1F740BF4B72C}.Release|Any CPU.Build.0 = Release|Any CPU
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9429AEAE-80A6-4EE7-AB66-9161CC4C3A3D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,3 +0,0 @@
namespace ParkanPlayground;
public record ListMetadataItem(string ItemType, int ItemLength, string FileName, int OffsetInFile);

View File

@ -7,4 +7,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NResLib\NResLib.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,105 +1,41 @@
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text;using ParkanPlayground; using System.Text;
using NResLib;
var libFile = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\ui\\ui_back.lib"; var libFile = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\ui\\ui_back.lib";
using FileStream nResFs = new FileStream(libFile, FileMode.Open); var parseResult = NResParser.ReadFile(libFile);
Span<byte> buffer = stackalloc byte[4]; if (parseResult.Error != null)
nResFs.ReadExactly(buffer);
var nResHeader = BinaryPrimitives.ReadInt32LittleEndian(buffer);
var str = Encoding.ASCII.GetString(buffer);
Console.WriteLine($"NRES Header: {nResHeader:X} - {str}");
// ----
nResFs.ReadExactly(buffer);
var version = BinaryPrimitives.ReadInt32LittleEndian(buffer);
Console.WriteLine($"VERSION: {version:X}");
// ----
nResFs.ReadExactly(buffer);
var elementCount = BinaryPrimitives.ReadInt32LittleEndian(buffer);
Console.WriteLine($"ElementCount: {elementCount}");
// ----
nResFs.ReadExactly(buffer);
var totalLength = BinaryPrimitives.ReadInt32LittleEndian(buffer);
Console.WriteLine($"TOTAL_LENGTH: {totalLength}");
// ----
nResFs.Seek(-elementCount * 64, SeekOrigin.End);
_ = 5;
var elements = new List<ListMetadataItem>(elementCount);
Span<byte> metaDataBuffer = stackalloc byte[64];
for (int i = 0; i < elementCount; i++)
{ {
nResFs.ReadExactly(metaDataBuffer); Console.WriteLine(parseResult.Error);
return;
var itemType = Encoding.ASCII.GetString(metaDataBuffer.Slice(0, 8));
var itemLength = BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer.Slice(12, 4));
var fileNameBlock = metaDataBuffer.Slice(20, 20);
var len = fileNameBlock.IndexOf((byte)'\0');
if (len == -1) len = 20; // whole 20 bytes is a filename
var fileName = Encoding.ASCII.GetString(fileNameBlock.Slice(0, len));
var offsetInFile = BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer.Slice(56, 4));
var lastMagicNumber = BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer.Slice(60, 4));
Console.WriteLine(
$"File {i+1}: \n" +
$"\tType: {itemType}\n" +
$"\tItemLength: {itemLength}\n" +
$"\tFileName: {fileName}\n" +
$"\tOffsetInFile: {offsetInFile}\n" +
$"\tLastMagicNumber: {lastMagicNumber}"
);
elements.Add(new ListMetadataItem(itemType, itemLength, fileName, offsetInFile));
metaDataBuffer.Clear();
} }
var libFileName = new FileInfo(libFile).Name; // var libFileName = new FileInfo(libFile).Name;
//
if (Directory.Exists(libFileName)) // if (Directory.Exists(libFileName))
{ // {
Directory.Delete(libFileName, true); // Directory.Delete(libFileName, true);
} // }
//
var dir = Directory.CreateDirectory(libFileName); // var dir = Directory.CreateDirectory(libFileName);
//
byte[] copyBuffer = new byte[8192]; // byte[] copyBuffer = new byte[8192];
//
foreach (var element in elements) // foreach (var element in elements)
{ // {
nResFs.Seek(element.OffsetInFile, SeekOrigin.Begin); // nResFs.Seek(element.OffsetInFile, SeekOrigin.Begin);
using var destFs = new FileStream(Path.Combine(libFileName, element.FileName), FileMode.CreateNew); // using var destFs = new FileStream(Path.Combine(libFileName, element.FileName), FileMode.CreateNew);
//
var totalCopiedBytes = 0; // var totalCopiedBytes = 0;
while (totalCopiedBytes < element.ItemLength) // while (totalCopiedBytes < element.ItemLength)
{ // {
var needReadBytes = Math.Min(element.ItemLength - totalCopiedBytes, copyBuffer.Length); // var needReadBytes = Math.Min(element.ItemLength - totalCopiedBytes, copyBuffer.Length);
var readBytes = nResFs.Read(copyBuffer, 0, needReadBytes); // var readBytes = nResFs.Read(copyBuffer, 0, needReadBytes);
//
destFs.Write(copyBuffer, 0, readBytes); // destFs.Write(copyBuffer, 0, readBytes);
//
totalCopiedBytes += readBytes; // totalCopiedBytes += readBytes;
} // }
} // }