1
0
mirror of synced 2025-02-20 04:31:12 +01:00

Reworked the NARC library (#464)

This commit is contained in:
JkKU 2022-07-15 03:30:04 +02:00 committed by GitHub
parent 963ffa1227
commit b21fc43b52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,226 +1,97 @@
using System; using Syroot.BinaryData;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Toolbox.Library; using Toolbox.Library;
using Toolbox.Library.IO; using Toolbox.Library.IO;
using Toolbox.Library.Forms;
using System.Windows.Forms;
namespace FirstPlugin namespace FirstPlugin {
{ class NARC : IArchiveFile, IFileFormat {
class NARC : IArchiveFile, IFileFormat
{
public FileType FileType { get; set; } = FileType.Archive; public FileType FileType { get; set; } = FileType.Archive;
public bool CanSave { get; set; } public bool CanSave { get; set; } = true;
public string[] Description { get; set; } = new string[] { "Nitro Archive (NARC)" }; public string[] Description { get; set; } = new string[] { "Nitro Archive (NARC)" };
public string[] Extension { get; set; } = new string[] { "*.narc" }; public string[] Extension { get; set; } = new string[] { "*.narc" };
public string FileName { get; set; } public string FileName { get; set; }
public string FilePath { get; set; } public string FilePath { get; set; }
public IFileInfo IFileInfo { get; set; } public IFileInfo IFileInfo { get; set; }
public bool Identify(System.IO.Stream stream) public Type[] Types => new Type[0];
{
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
{
return reader.CheckSignature(4, "NARC");
}
}
public Type[] Types public bool CanAddFiles { get; set; } = true;
{ public bool CanRenameFiles { get; set; } = true;
get public bool CanReplaceFiles { get; set; } = true;
{ public bool CanDeleteFiles { get; set; } = true;
List<Type> types = new List<Type>();
return types.ToArray();
}
}
public bool CanAddFiles { get; set; }
public bool CanRenameFiles { get; set; }
public bool CanReplaceFiles { get; set; }
public bool CanDeleteFiles { get; set; }
public List<FileEntry> files = new List<FileEntry>();
public IEnumerable<ArchiveFileInfo> Files => files; public IEnumerable<ArchiveFileInfo> Files => files;
public void ClearFiles() { files.Clear(); } // private
private Header header;
private BTAF btaf;
private BTNF btnf;
private GMIF gmif;
public bool AddFile(ArchiveFileInfo archiveFileInfo) private List<ArchiveFileInfo> files = new List<ArchiveFileInfo>();
{
return false; public bool AddFile(ArchiveFileInfo archiveFileInfo) {
files.Add(archiveFileInfo);
return true;
} }
public bool DeleteFile(ArchiveFileInfo archiveFileInfo) public void ClearFiles() => files.Clear();
{
return false; public bool DeleteFile(ArchiveFileInfo archiveFileInfo) {
return files.Remove(archiveFileInfo);
} }
public class FileEntry : ArchiveFileInfo public bool Identify(Stream stream) {
{ using(FileReader reader = new FileReader(stream, true))
public NARC.FileAllocationEntry entry; return reader.CheckSignature(4, "NARC");
public FileImageBlock fileImage; }
public override Dictionary<string, string> ExtensionImageKeyLookup public void Load(Stream stream) {
{ using(FileReader reader = new FileReader(stream)) {
get header = new Header(reader);
{
return new Dictionary<string, string>()
{
{".cbfmd", "bfres" },
{".cbfa", "bfres" },
{".cbfsa", "bfres" },
{".cbntx", "bntx" },
};
}
}
public NARC ArchiveFile; for(uint i = 0; i < header.DataBlocks; i++) {
long PositionBuf = reader.Position;
string cSectionMagic = Encoding.ASCII.GetString(reader.ReadBytes(4)).ToUpperInvariant();
uint cSectionSize = reader.ReadUInt32();
public FileEntry(NARC narc, string Name) switch(cSectionMagic) {
{ case "BTAF":
ArchiveFile = narc; btaf = new BTAF(reader);
FileName = Name.Replace(" ", string.Empty).RemoveIllegaleFolderNameCharacters(); break;
} case "BTNF":
btnf = new BTNF(reader, (uint) btaf.FileDataArray.Length);
public override byte[] FileData break;
{ case "GMIF":
get { return DecompressBlock(); } gmif = new GMIF(reader, btaf);
set break;
{
}
}
private bool IsTexturesLoaded = false;
public override IFileFormat OpenFile()
{
var FileFormat = base.OpenFile();
bool IsModel = FileFormat is BFRES;
if (IsModel && !IsTexturesLoaded)
{
IsTexturesLoaded = true;
foreach (var file in ArchiveFile.Files)
{
if (Utils.GetExtension(file.FileName) == ".ctex")
{
file.FileFormat = file.OpenFile();
}
} }
reader.Position = PositionBuf + cSectionSize;
} }
return base.OpenFile(); for(int i = 0; i < btnf.FileNames.Length; i++)
} files.Add(new ArchiveFileInfo() {
FileName = btnf.FileNames[i],
private byte[] DecompressBlock() FileData = gmif.FilesData[i]
{ });
byte[] data = GetBlock();
var reader = new FileReader(data);
reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian;
byte compType = reader.ReadByte();
if (compType == 0x50)
{
reader.Seek(4, System.IO.SeekOrigin.Begin);
uint decompSize = reader.ReadUInt32();
uint compSize = (uint)reader.BaseStream.Length - 8;
var comp = new STLibraryCompression.MTA_CUSTOM();
return comp.Decompress(data, decompSize);
}
else if (compType == 0x30 || compType == 0x20)
{
uint decompSize = reader.ReadUInt32();
uint compSize = (uint)reader.BaseStream.Length - 16;
reader.SeekBegin(16);
ushort signature = reader.ReadUInt16();
bool IsGZIP = signature == 0x1F8B;
bool IsZLIB = signature == 0x789C || signature == 0x78DA;
byte[] filedata = reader.getSection(16, (int)compSize);
reader.Close();
reader.Dispose();
if (IsGZIP)
data = STLibraryCompression.GZIP.Decompress(filedata);
else
data = STLibraryCompression.ZLIB.Decompress(filedata, false);
}
return data;
}
public byte[] GetBlock()
{
return Utils.SubArray(fileImage.dataBlock, entry.StartOffset, entry.EndOffset);
} }
} }
Header header; public void Unload() { }
public List<FileEntry> FileEntries = new List<FileEntry>(); internal class Header {
public void Load(System.IO.Stream stream)
{
header = new Header(new FileReader(stream));
var names = GetNames(header.FNTB);
List<byte> Data = new List<byte>();
for (ushort i = 0; i < header.FATB.FileCount; i++)
{
string name = names.Count > i ? names[i] : $"File{i}";
FileEntries.Add(new FileEntry(this, name)
{
entry = header.FATB.FileEntries[i],
fileImage = header.FIMG,
});
files.Add(FileEntries[i]);
}
}
public List<string> GetNames(FileNameTable nameTable)
{
var names = new List<string>();
foreach (var tblEntry in nameTable.entryNameTable)
{
if (tblEntry is EntryNameTableFileEntry)
names.Add(((EntryNameTableFileEntry)tblEntry).entryName);
}
return names;
}
public void Unload()
{
}
public void Save(System.IO.Stream stream)
{
}
//EFE for REing format https://github.com/Gericom/EveryFileExplorer/blob/f9f00d193c9608d71c9a23d9f3ab7e752f4ada2a/NDS/NitroSystem/FND/NARC.cs
public class Header
{
public string Signature;
public ushort ByteOrder; public ushort ByteOrder;
public ushort Version;
public uint FileSize; public uint FileSize;
public uint Version;
public ushort HeaderSize; public ushort HeaderSize;
public ushort DataBlocks; public ushort DataBlocks;
public FileAllocationTableBlock FATB; public Header(FileReader reader) {
public FileNameTable FNTB;
public FileImageBlock FIMG;
public Header(FileReader reader)
{
reader.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian; reader.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian;
reader.ReadSignature(4, "NARC"); reader.ReadSignature(4, "NARC");
@ -231,198 +102,147 @@ namespace FirstPlugin
FileSize = reader.ReadUInt32(); FileSize = reader.ReadUInt32();
HeaderSize = reader.ReadUInt16(); HeaderSize = reader.ReadUInt16();
DataBlocks = reader.ReadUInt16(); DataBlocks = reader.ReadUInt16();
FATB = new FileAllocationTableBlock(reader);
FNTB = new FileNameTable(reader);
FIMG = new FileImageBlock(reader);
}
public void Write(FileWriter writer)
{
writer.WriteSignature(Signature);
writer.Write(ByteOrder);
writer.Write(Version);
writer.Write(FileSize);
writer.Write(HeaderSize);
writer.Write(DataBlocks);
} }
} }
public class FileAllocationTableBlock
{
public string Signature;
public uint Size;
public ushort FileCount;
public ushort Reserved;
public List<FileAllocationEntry> FileEntries = new List<FileAllocationEntry>(); // Mostly everything from this point was imported from:
// https://github.com/Jenrikku/NARCSharp
public void Save(Stream stream) {
using(BinaryDataWriter writer = new BinaryDataWriter(stream)) {
#region Header
writer.Write("NARC", BinaryStringFormat.NoPrefixOrTermination, Encoding.ASCII); // Magic.
public FileAllocationTableBlock(FileReader reader) writer.ByteOrder = (ByteOrder) header.ByteOrder;
{ writer.Write((ushort) 0xFFFE); // ByteOrder.
long startPos = reader.Position;
reader.ReadSignature(4, "BTAF"); writer.Write(header.Version); // Version.
Size = reader.ReadUInt32(); long headerLengthPorsition = writer.Position;
FileCount = reader.ReadUInt16(); writer.Write(0x00000000); // Skips length writing.
Reserved = reader.ReadUInt16();
for (int i = 0; i < FileCount; i++)
FileEntries.Add(new FileAllocationEntry(reader));
reader.Seek(startPos + Size, System.IO.SeekOrigin.Begin); writer.Write(header.HeaderSize); // Header length.
} writer.Write(header.DataBlocks); // Section count.
} #endregion
public class FileAllocationEntry
{
public uint StartOffset;
public uint EndOffset;
public FileAllocationEntry(FileReader reader) #region BTAF preparation
{ long btafPosition = WriteSectionHeader("BTAF"); // Header.
StartOffset = reader.ReadUInt32(); writer.Write((uint) files.Count); // File count.
EndOffset = reader.ReadUInt32();
}
public void Write(FileWriter writer)
{
writer.Write(StartOffset);
writer.Write(EndOffset);
}
}
public class FileImageBlock
{
public string Signature;
public uint Size;
public byte[] dataBlock;
public FileImageBlock(FileReader reader) for(uint i = 0; i < files.Count; i++) // Reads unset bytes per file. (Reserved space for later)
{ writer.Write((long) 0x0000000000000000);
reader.ReadSignature(4, "GMIF");
Size = reader.ReadUInt32(); WriteSectionLength(btafPosition);
dataBlock = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)); #endregion
}
}
public class FileNameTable
{
public string Signature;
public uint Size;
public List<DirectoryTableEntry> directoryTable = new List<DirectoryTableEntry>(); #region BTNF
public List<EntryNameTableEntry> entryNameTable = new List<EntryNameTableEntry>(); long btnfPosition = WriteSectionHeader("BTNF"); // Header.
writer.Write(btnf.Unknown);
public FileNameTable(FileReader reader) foreach(ArchiveFileInfo file in files)
{ writer.Write(file.FileName, BinaryStringFormat.ByteLengthPrefix);
long startPos = reader.BaseStream.Position; writer.Write((byte) 0x00);
reader.ReadSignature(4, "BTNF"); writer.Align(32);
Size = reader.ReadUInt32(); writer.Position += 8;
if (Size == 16)
{ WriteSectionLength(btnfPosition);
reader.Seek(8); #endregion
return;
#region GMIF
long gmifPosition = WriteSectionHeader("GMIF"); // Header.
long btafCurrentPosition = btafPosition + 12; // First offset-size position. (BTAF)
foreach(ArchiveFileInfo file in files) {
WriteBTAFEntry(); // BTAF offset
writer.Write(file.FileData);
WriteBTAFEntry(); // BTAF size.
writer.Align(16);
} }
directoryTable.Add(new DirectoryTableEntry(reader)); WriteSectionLength(gmifPosition);
#endregion
for (int i = 0; i < directoryTable[0].dirParentID - 1; i++) writer.Position = headerLengthPorsition;
{ writer.Write((uint) writer.BaseStream.Length); // Total file length.
directoryTable.Add(new DirectoryTableEntry(reader));
long WriteSectionHeader(string magic) {
long startPosition = writer.Position;
writer.Write(magic, BinaryStringFormat.NoPrefixOrTermination, Encoding.ASCII); // Magic.
writer.Write(0x00000000); // Skips length position.
return startPosition;
} }
entryNameTable = new List<EntryNameTableEntry>();
int EndOfDirectory = 0; void WriteSectionLength(long startPosition) {
while (EndOfDirectory < directoryTable[0].dirParentID) using(writer.TemporarySeek()) {
{ long finalLength = (uint) writer.Position;
byte entryNameLength = reader.ReadByte();
reader.BaseStream.Position--; writer.Position = startPosition + 4;
if (entryNameLength == 0) writer.Write((uint) (finalLength - startPosition));
{
entryNameTable.Add(new EntryNameTableEndOfDirectoryEntry(reader));
EndOfDirectory++;
} }
else if (entryNameLength < 0x80) entryNameTable.Add(new EntryNameTableFileEntry(reader));
else entryNameTable.Add(new EntryNameTableDirectoryEntry(reader));
} }
reader.BaseStream.Position = startPos + Size; void WriteBTAFEntry() {
uint value = (uint) (writer.Position - (gmifPosition + 8));
using(writer.TemporarySeek()) {
writer.Position = btafCurrentPosition;
writer.Write(value);
}
btafCurrentPosition += 4;
}
}
}
#region Sections
internal class BTAF {
public (uint offset, uint size)[] FileDataArray;
public BTAF() { }
public BTAF(BinaryDataReader reader) {
uint numberOfFiles = reader.ReadUInt32();
FileDataArray = new (uint, uint)[numberOfFiles];
for(ulong i = 0; i < numberOfFiles; i++) {
FileDataArray[i].offset = reader.ReadUInt32();
FileDataArray[i].size = reader.ReadUInt32();
}
} }
} }
public class EntryNameTableEndOfDirectoryEntry : EntryNameTableEntry internal class BTNF {
{ public string[] FileNames;
public EntryNameTableEndOfDirectoryEntry() { } public ulong Unknown;
public EntryNameTableEndOfDirectoryEntry(FileReader reader)
: base(reader) { } public BTNF() { }
public override void Write(FileWriter writer) public BTNF(BinaryDataReader reader, uint numberOfFiles) {
{ Unknown = reader.ReadUInt64();
base.Write(writer); FileNames = new string[numberOfFiles];
for(int i = 0; i < numberOfFiles; i++) {
FileNames[i] = reader.ReadString(BinaryStringFormat.ByteLengthPrefix, Encoding.ASCII);
}
} }
} }
public class EntryNameTableDirectoryEntry : EntryNameTableEntry internal class GMIF {
{ public byte[][] FilesData;
public string entryName;
public ushort directoryID;
public EntryNameTableDirectoryEntry(FileReader reader) : base(reader) public GMIF() { }
{ public GMIF(BinaryDataReader reader, BTAF btaf) {
entryName = reader.ReadString(entryNameLength & 0x7F, Encoding.ASCII); FilesData = new byte[btaf.FileDataArray.Length][];
directoryID = reader.ReadUInt16(); long PositionBuf = reader.Position;
}
public override void Write(FileWriter writer) for(int i = 0; i < btaf.FileDataArray.Length; i++) {
{ reader.Position = PositionBuf + btaf.FileDataArray[i].offset;
base.Write(writer);
writer.Write(entryName, Syroot.BinaryData.BinaryStringFormat.ZeroTerminated); FilesData[i] = reader.ReadBytes(
writer.Write(directoryID); (int) (btaf.FileDataArray[i].size - btaf.FileDataArray[i].offset));
} }
}
public class EntryNameTableFileEntry : EntryNameTableEntry
{
public string entryName;
public EntryNameTableFileEntry(FileReader reader) : base(reader)
{
entryName = reader.ReadString(entryNameLength, Encoding.ASCII);
}
public override void Write(FileWriter writer)
{
writer.Write(entryName, Syroot.BinaryData.BinaryStringFormat.ZeroTerminated);
}
}
public class EntryNameTableEntry
{
public byte entryNameLength;
protected EntryNameTableEntry() { }
public EntryNameTableEntry(FileReader reader)
{
entryNameLength = reader.ReadByte();
}
public virtual void Write(FileWriter writer)
{
writer.Write(entryNameLength);
}
}
public class DirectoryTableEntry
{
public uint dirEntryStart;
public ushort dirEntryFileID;
public ushort dirParentID;
public DirectoryTableEntry() { }
public DirectoryTableEntry(FileReader reader)
{
dirEntryStart = reader.ReadUInt32();
dirEntryFileID = reader.ReadUInt16();
dirParentID = reader.ReadUInt16();
}
public void Write(FileWriter writer)
{
writer.Write(dirEntryStart);
writer.Write(dirEntryFileID);
writer.Write(dirParentID);
} }
} }
#endregion
} }
} }