2024-11-15 19:06:44 +03:00
|
|
|
|
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(
|
2024-11-15 20:29:02 +03:00
|
|
|
|
NRes: Encoding.ASCII.GetString(buffer[0..4]).TrimEnd('\0'),
|
2024-11-15 19:06:44 +03:00
|
|
|
|
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);
|
2025-03-09 22:56:59 +03:00
|
|
|
|
var type = "";
|
2024-11-15 19:06:44 +03:00
|
|
|
|
|
2025-03-09 22:56:59 +03:00
|
|
|
|
for (int j = 0; j < 4; j++)
|
|
|
|
|
{
|
|
|
|
|
if (!char.IsLetterOrDigit((char)metaDataBuffer[j]))
|
|
|
|
|
{
|
|
|
|
|
type += metaDataBuffer[j]
|
|
|
|
|
.ToString("X2") + " ";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
type += (char)metaDataBuffer[j];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var type2 = BinaryPrimitives.ReadUInt32LittleEndian(metaDataBuffer.Slice(4));
|
|
|
|
|
|
|
|
|
|
type = type.Trim();
|
|
|
|
|
|
2024-11-15 19:06:44 +03:00
|
|
|
|
elements.Add(
|
|
|
|
|
new ListMetadataItem(
|
2025-03-09 22:56:59 +03:00
|
|
|
|
FileType: type,
|
|
|
|
|
ElementCount: type2,
|
2024-11-15 19:06:44 +03:00
|
|
|
|
Magic1: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[8..12]),
|
|
|
|
|
FileLength: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[12..16]),
|
2025-03-09 22:56:59 +03:00
|
|
|
|
ElementSize: BinaryPrimitives.ReadInt32LittleEndian(metaDataBuffer[16..20]),
|
2024-11-15 20:29:02 +03:00
|
|
|
|
FileName: Encoding.ASCII.GetString(metaDataBuffer[20..40]).TrimEnd('\0'),
|
2024-11-15 19:06:44 +03:00
|
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|