mirror of
https://github.com/AdAstra-LD/DS-Pokemon-Rom-Editor.git
synced 2026-05-10 06:01:05 -05:00
218 lines
10 KiB
C#
218 lines
10 KiB
C#
using DSPRE;
|
|
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
namespace NarcAPI {
|
|
public class Narc { //Nitro Archive
|
|
public String Name { get; set; }
|
|
private MemoryStream[] Elements;
|
|
private int FileNameTableOffset, FileImageOffset;
|
|
|
|
private const int NARC_FILE_MAGIC_NUM = 0x4352414E; //"NARC" in ascii/unicode
|
|
private const int FILE_ALLOCATION_TABLE_OFFSET = 0x10;
|
|
private const int FILE_ALLOCATION_TABLE_NUM_ELEMENTS_OFFSET = 0x18;
|
|
private const int FILE_ALLOCATION_TABLE_HEADER_LENGTH = 12;
|
|
private const int FILE_ALLOCATION_TABLE_ELEMENT_LENGTH = 0x8;
|
|
private const int FILE_IMAGE_HEADER_SIZE = 0x8;
|
|
private const int FILE_NAME_TABLE_SIGNATURE_LENGTH = 0x4;
|
|
|
|
private Narc(String name) {
|
|
this.Name = name;
|
|
}
|
|
|
|
public static Narc NewEmpty(String name = "NewNarc") {
|
|
Narc narc = new Narc(name);
|
|
return narc;
|
|
}
|
|
|
|
public static Narc Open(String filePath) {
|
|
Narc narc = new Narc(Path.GetFileNameWithoutExtension(filePath));
|
|
BinaryReader br = new BinaryReader(File.OpenRead(filePath));
|
|
|
|
if (br.ReadUInt32() != NARC_FILE_MAGIC_NUM) {
|
|
return null;
|
|
}
|
|
|
|
narc.ReadOffsets(br);
|
|
narc.ReadElements(br);
|
|
br.Close();
|
|
return narc;
|
|
}
|
|
|
|
public static Narc FromFolder(String dirPath) {
|
|
Narc narc = new Narc(Path.GetDirectoryName(dirPath));
|
|
String[] fileNames = Directory.GetFiles(dirPath, "*.*", SearchOption.AllDirectories);
|
|
uint numberOfElements = (uint)fileNames.Length;
|
|
narc.Elements = new MemoryStream[numberOfElements];
|
|
|
|
Parallel.For(0, numberOfElements, i => {
|
|
FileStream fs = File.OpenRead(fileNames[i]);
|
|
MemoryStream ms = new MemoryStream();
|
|
byte[] buffer = new byte[fs.Length];
|
|
fs.Read(buffer, 0, (int)fs.Length);
|
|
ms.Write(buffer, 0, (int)fs.Length);
|
|
narc.Elements[i] = ms;
|
|
fs.Close();
|
|
});
|
|
return narc;
|
|
}
|
|
|
|
public void Save(String filePath) {
|
|
uint fileSizeOffset, fileImageSizeOffset, curOffset;
|
|
|
|
BinaryWriter bw = new BinaryWriter(File.Create(filePath));
|
|
// Write NARC Section
|
|
bw.Write(NARC_FILE_MAGIC_NUM);
|
|
bw.Write(0x0100FFFE);
|
|
fileSizeOffset = (uint)bw.BaseStream.Position;
|
|
bw.Write((UInt32)0x0);
|
|
bw.Write((ushort)16); //full size of header section
|
|
bw.Write((ushort)3); //the number of sections in the header
|
|
// Write FATB Section
|
|
bw.Write(0x46415442); // "BTAF"
|
|
bw.Write((UInt32)(FILE_ALLOCATION_TABLE_HEADER_LENGTH + Elements.Length * FILE_ALLOCATION_TABLE_ELEMENT_LENGTH));
|
|
bw.Write((UInt32)Elements.Length); // Number of elements
|
|
curOffset = 0;
|
|
for (int i = 0; i < Elements.Length; i++) {
|
|
while (curOffset % 4 != 0) {
|
|
curOffset++; // Force offsets to be a multiple of 4
|
|
}
|
|
|
|
bw.Write(curOffset);
|
|
curOffset += (uint)Elements[i].Length;
|
|
bw.Write(curOffset);
|
|
}
|
|
// Write FNTB Section (No names, sorry =( )
|
|
bw.Write(0x464E5442); //"BTNF"
|
|
bw.Write(0x10); //FNTB Size
|
|
bw.Write(0x4); //the offset of the first name directory
|
|
bw.Write(0x10000); //filler data describing a file at position 0 with 1 directory in the archive
|
|
// Write FIMG Section
|
|
bw.Write(0x46494D47); // "GMIF"
|
|
fileImageSizeOffset = (uint)bw.BaseStream.Position;
|
|
bw.Write((UInt32)0x0);
|
|
curOffset = 0;
|
|
byte[] buffer;
|
|
for (int i = 0; i < Elements.Length; i++) {
|
|
while (curOffset % 4 != 0) { // Force offsets to be a multiple of 4
|
|
bw.Write((Byte)0xFF); curOffset++;
|
|
}
|
|
// Data writin'
|
|
buffer = new byte[Elements[i].Length];
|
|
Elements[i].Seek(0, SeekOrigin.Begin);
|
|
Elements[i].Read(buffer, 0, (int)Elements[i].Length);
|
|
bw.Write(buffer, 0, (int)Elements[i].Length);
|
|
curOffset += (uint)Elements[i].Length;
|
|
}
|
|
// Writes sizes
|
|
int fileSize = (int)bw.BaseStream.Position;
|
|
bw.Seek((int)fileSizeOffset, SeekOrigin.Begin); // File size
|
|
bw.Write((UInt32)fileSize);
|
|
bw.Seek((int)fileImageSizeOffset, SeekOrigin.Begin); // seeks back to FIMG size
|
|
bw.Write((UInt32)curOffset + FILE_IMAGE_HEADER_SIZE); // FIMG size == Last end offset + File image header size
|
|
bw.Close();
|
|
}
|
|
|
|
public void ExtractToFolder(String dirPath, string extension = null) {
|
|
if ( string.IsNullOrWhiteSpace(dirPath) ) {
|
|
MessageBox.Show("Dir path + \"" + dirPath + "\" is invalid.", "Can't create directory", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return;
|
|
}
|
|
|
|
if (Directory.Exists(dirPath)) {
|
|
if (Directory.GetFiles(dirPath).Length > 0) {
|
|
try {
|
|
if (dirPath.IndexOf(RomInfo.folderSuffix, StringComparison.CurrentCultureIgnoreCase) >= 0) {
|
|
Directory.Delete(dirPath, true);
|
|
Console.WriteLine("Deleted DSPRE-related folder \"" + dirPath + "\" without user confirmation.");
|
|
} else {
|
|
DialogResult d = MessageBox.Show("Directory \"" + dirPath + "\" already exists and is not empty.\n" +
|
|
"Do you want to delete its contents?", "Directory not empty", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
|
if (d.Equals(DialogResult.Yes)) {
|
|
Directory.Delete(dirPath, true);
|
|
Console.WriteLine("Deleted non-DSPRE-related folder \"" + dirPath + "\" after user confirmation.");
|
|
}
|
|
}
|
|
} catch (IOException) {
|
|
MessageBox.Show("NARC has not been extracted.\nCan't delete directory: \n" + dirPath + "\nThis might be a temporary issue.\nMake sure no other process is using it and try again.", "Delete Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Directory.Exists(dirPath)) {
|
|
try {
|
|
Directory.CreateDirectory(dirPath);
|
|
Console.WriteLine("Created NARC folder \"" + dirPath + "\".");
|
|
} catch (IOException) {
|
|
MessageBox.Show("NARC has not been extracted.\nCan't create directory: \n" + dirPath + "\nThis might be a temporary issue.\nMake sure no other process is using it and try again.", "Creation Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Parallel.For(0, Elements.Length, i => {
|
|
string path = Path.Combine(dirPath, i.ToString("D4") + (string.IsNullOrWhiteSpace(extension) ? "" : extension) );
|
|
using (BinaryWriter wr = new BinaryWriter(File.Create(path))) {
|
|
long len = Elements[i].Length;
|
|
byte[] buffer = new byte[len];
|
|
Elements[i].Seek(0, SeekOrigin.Begin);
|
|
Elements[i].Read(buffer, 0, (int)len);
|
|
wr.Write(buffer);
|
|
}
|
|
});
|
|
}
|
|
|
|
public void Free() { // Libera todos los recursos de memoria asociados (cierra los streams)
|
|
Parallel.For(0, Elements.Length, i => {
|
|
Elements[i].Close();
|
|
});
|
|
}
|
|
|
|
public MemoryStream this[int elemIndex] {
|
|
get {
|
|
return Elements[elemIndex];
|
|
}
|
|
set {
|
|
Elements[elemIndex] = value;
|
|
}
|
|
}
|
|
|
|
public int GetElementsLength() {
|
|
return Elements.Length;
|
|
}
|
|
|
|
private void ReadOffsets(BinaryReader br) {
|
|
br.BaseStream.Position = FILE_ALLOCATION_TABLE_NUM_ELEMENTS_OFFSET;
|
|
FileNameTableOffset = (int)br.ReadUInt32() * FILE_ALLOCATION_TABLE_ELEMENT_LENGTH + FILE_ALLOCATION_TABLE_OFFSET + FILE_ALLOCATION_TABLE_HEADER_LENGTH;
|
|
br.BaseStream.Position = FileNameTableOffset + FILE_NAME_TABLE_SIGNATURE_LENGTH;
|
|
FileImageOffset = (int)br.ReadUInt32() + FileNameTableOffset;
|
|
}
|
|
|
|
private void ReadElements(BinaryReader br) {
|
|
uint numberOfElements;
|
|
uint[] startOffsets, endOffsets;
|
|
// Create array of elements
|
|
br.BaseStream.Position = FILE_ALLOCATION_TABLE_NUM_ELEMENTS_OFFSET;
|
|
Elements = new MemoryStream[numberOfElements = br.ReadUInt32()];
|
|
|
|
// Read offsets of each element
|
|
startOffsets = new uint[numberOfElements];
|
|
endOffsets = new uint[numberOfElements];
|
|
br.BaseStream.Position = FILE_ALLOCATION_TABLE_OFFSET + FILE_ALLOCATION_TABLE_HEADER_LENGTH;
|
|
for (int i = 0; i < numberOfElements; i++) {
|
|
startOffsets[i] = br.ReadUInt32();
|
|
endOffsets[i] = br.ReadUInt32();
|
|
}
|
|
// Read elements
|
|
for(int i = 0; i < numberOfElements; i++) {
|
|
br.BaseStream.Position = FileImageOffset + startOffsets[i] + FILE_IMAGE_HEADER_SIZE;
|
|
byte[] buffer = new byte[endOffsets[i] - startOffsets[i]];
|
|
br.Read(buffer, 0, (int)(endOffsets[i] - startOffsets[i]));
|
|
Elements[i] = new MemoryStream(buffer);
|
|
}
|
|
}
|
|
}
|
|
}
|