Skip to content

Commit

Permalink
Version 1.2.1.
Browse files Browse the repository at this point in the history
  • Loading branch information
MaikelChan committed Nov 16, 2019
1 parent 404e29a commit 1bb7c53
Show file tree
Hide file tree
Showing 7 changed files with 629 additions and 2 deletions.
25 changes: 25 additions & 0 deletions AFSPacker.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29509.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AFSPacker", "AFSPacker\AFSPacker.csproj", "{4026B135-B71F-4D0E-845F-63565E367DFA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4026B135-B71F-4D0E-845F-63565E367DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4026B135-B71F-4D0E-845F-63565E367DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4026B135-B71F-4D0E-845F-63565E367DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4026B135-B71F-4D0E-845F-63565E367DFA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FCD33589-24D5-409F-9CE5-61BEFF1A06E3}
EndGlobalSection
EndGlobal
354 changes: 354 additions & 0 deletions AFSPacker/AFS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;

namespace AFSPacker
{
class AFS
{
const uint HEADER_MAGIC_1 = 0x00534641; // AFS
const uint HEADER_MAGIC_2 = 0x20534641;
const string NULL_FILE = "#NULL#";

public void CreateAFS(string inputDirectory, string outputFile, string filesList = null, bool preserveFileNames = true)
{
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Packaging files...\n\n");

string[] inputFiles;

if (string.IsNullOrEmpty(filesList))
{
inputFiles = Directory.GetFiles(inputDirectory);
}
else
{
inputFiles = File.ReadAllLines(filesList);
}

using (FileStream fs1 = new FileStream(outputFile, FileMode.Create))
using (BinaryWriter bw = new BinaryWriter(fs1))
{
bw.Write(HEADER_MAGIC_1);
bw.Write((uint)inputFiles.Length);

// Generate TOC and FileNameDirectory

TableOfContents[] toc = new TableOfContents[inputFiles.Length];
FileAttributes[] attributes = new FileAttributes[inputFiles.Length];

uint currentOffset = Pad((uint)(8 + (8 * inputFiles.Length) + 8), 0x800); // Header + TOC + AttributeTable Offset and size

for (int n = 0; n < inputFiles.Length; n++)
{
if (inputFiles[n] == NULL_FILE)
{
toc[n].FileSize = 0;
toc[n].Offset = 0;

if (preserveFileNames)
{
//FileNameDirectory
attributes[n].FileName = string.Empty;
attributes[n].Year = 0;
attributes[n].Month = 0;
attributes[n].Day = 0;
attributes[n].Hour = 0;
attributes[n].Minute = 0;
attributes[n].Second = 0;
attributes[n].FileSize = 0;
}
}
else
{
FileInfo f = new FileInfo(inputFiles[n]);

//TOC
toc[n].FileSize = (uint)f.Length;
toc[n].Offset = currentOffset;

currentOffset += toc[n].FileSize;
currentOffset = Pad(currentOffset, 0x800);

if (preserveFileNames)
{
//FileNameDirectory
attributes[n].FileName = Path.GetFileName(inputFiles[n]);
attributes[n].Year = (ushort)f.LastWriteTime.Year;
attributes[n].Month = (ushort)f.LastWriteTime.Month;
attributes[n].Day = (ushort)f.LastWriteTime.Day;
attributes[n].Hour = (ushort)f.LastWriteTime.Hour;
attributes[n].Minute = (ushort)f.LastWriteTime.Minute;
attributes[n].Second = (ushort)f.LastWriteTime.Second;
attributes[n].FileSize = (uint)f.Length;
}
}

Console.CursorLeft = 0;
if (preserveFileNames) Console.Write($"Processing TOC and attributes... {n}/{inputFiles.Length - 1}");
else Console.Write($"Processing TOC... {n}/{inputFiles.Length - 1}");
}

Console.WriteLine();

//Write TOC to file
for (int n = 0; n < inputFiles.Length; n++)
{
bw.Write(toc[n].Offset);
bw.Write(toc[n].FileSize);

Console.CursorLeft = 0;
Console.Write($"Writing TOC... {n}/{inputFiles.Length - 1}");
}

Console.WriteLine();

uint attributeTableOffset = 0;
uint attributeTableSize = 0;

if (preserveFileNames)
{
//Write Filename Directory Offset and Size
attributeTableOffset = currentOffset;
attributeTableSize = (uint)(inputFiles.Length * 0x30);
fs1.Seek(toc[0].Offset - 8, SeekOrigin.Begin);
bw.Write(attributeTableOffset);
bw.Write(attributeTableSize);
}

//Write files data to file
for (int n = 0; n < inputFiles.Length; n++)
{
if (inputFiles[n] != NULL_FILE)
{
byte[] data = File.ReadAllBytes(inputFiles[n]);
fs1.Seek(toc[n].Offset, SeekOrigin.Begin);
fs1.Write(data, 0, data.Length);
}

Console.CursorLeft = 0;
Console.Write($"Writing files... {n}/{inputFiles.Length - 1}");
}

Console.WriteLine();

if (preserveFileNames)
{
//Write Filename Directory
fs1.Seek(attributeTableOffset, SeekOrigin.Begin);
for (int n = 0; n < inputFiles.Length; n++)
{
byte[] name = Encoding.Default.GetBytes(attributes[n].FileName);
fs1.Write(name, 0, name.Length);
fs1.Seek(0x20 - name.Length, SeekOrigin.Current);

bw.Write(attributes[n].Year);
bw.Write(attributes[n].Month);
bw.Write(attributes[n].Day);
bw.Write(attributes[n].Hour);
bw.Write(attributes[n].Minute);
bw.Write(attributes[n].Second);
bw.Write(attributes[n].FileSize);

Console.CursorLeft = 0;
Console.Write($"Writing attributes... {n}/{inputFiles.Length - 1}");
}
Console.WriteLine();
}

//Pad final 0s
long currentPosition = fs1.Position;
long eof = Pad((uint)fs1.Position, 0x800);
for (long n = currentPosition; n < eof; n++) bw.Write((byte)0);
}
}

public void ExtractAFS(string inputFile, string outputDirectory, string filesList = null)
{
bool areThereAttributes = true;

using (FileStream fs1 = new FileStream(inputFile, FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fs1))
{
uint magic = br.ReadUInt32();
if (magic != HEADER_MAGIC_1 && magic != HEADER_MAGIC_2) //If Magic is different than AFS
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error: Input file doesn't seem to be a valid AFS file.");
return;
}

Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Extracting files...\n\n");

uint numberOfFiles = br.ReadUInt32();

TableOfContents[] toc = new TableOfContents[numberOfFiles];
FileAttributes[] atrributes = new FileAttributes[numberOfFiles];

//Read TOC
for (int n = 0; n < numberOfFiles; n++)
{
toc[n].Offset = br.ReadUInt32();
toc[n].FileSize = br.ReadUInt32();

Console.CursorLeft = 0;
Console.Write($"Reading TOC... {n}/{numberOfFiles - 1}");
}

Console.WriteLine();

//Read Filename Directory Offset and Size
uint attributeTableOffset = 0;
uint attributeTableSize = 0;
while (fs1.Position < toc[0].Offset && attributeTableOffset == 0)
{
//fs1.Seek(TOC[0].Offset - 8, SeekOrigin.Begin);
attributeTableOffset = br.ReadUInt32();
attributeTableSize = br.ReadUInt32();
}

if (attributeTableOffset == 0) areThereAttributes = false;

string[] fileName = new string[numberOfFiles];

if (areThereAttributes)
{
//Read Attribute table
fs1.Seek(attributeTableOffset, SeekOrigin.Begin);

for (int n = 0; n < numberOfFiles; n++)
{
byte[] name = new byte[32];
fs1.Read(name, 0, name.Length);
fileName[n] = Encoding.Default.GetString(name).Replace("\0", "");

atrributes[n].Year = br.ReadUInt16();
atrributes[n].Month = br.ReadUInt16();
atrributes[n].Day = br.ReadUInt16();
atrributes[n].Hour = br.ReadUInt16();
atrributes[n].Minute = br.ReadUInt16();
atrributes[n].Second = br.ReadUInt16();
atrributes[n].FileSize = br.ReadUInt32();

Console.CursorLeft = 0;
Console.Write($"Reading attributes table... {n}/{numberOfFiles - 1}");
}

fileName = CheckForDuplicatedFilenames(fileName);
}
else
{
for (int n = 0; n < numberOfFiles; n++)
{
fileName[n] = n.ToString("00000000", CultureInfo.InvariantCulture);
}
}

Console.WriteLine();
Console.WriteLine();

//Extract files
if (!Directory.Exists(outputDirectory)) Directory.CreateDirectory(outputDirectory);

string[] filelist = new string[numberOfFiles];

for (int n = 0; n < numberOfFiles; n++)
{
if (toc[n].FileSize == 0 && toc[n].Offset == 0)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($"Warning: File \"{n}\" is a null file; Skipping.\n");
Console.ForegroundColor = ConsoleColor.White;

filelist[n] = NULL_FILE;

continue;
}

Console.CursorLeft = 0;
Console.WriteLine($"\nReading files... {n}/{numberOfFiles - 1}");

byte[] filedata = new byte[toc[n].FileSize];
fs1.Seek(toc[n].Offset, SeekOrigin.Begin);
fs1.Read(filedata, 0, filedata.Length);

string outputFile = Path.Combine(outputDirectory, fileName[n]);
if (File.Exists(outputFile))
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($"Warning: File \"{outputFile}\" already exists. Overwriting.");
Console.ForegroundColor = ConsoleColor.White;
}
File.WriteAllBytes(outputFile, filedata);

if (areThereAttributes)
{
try
{
DateTime date = new DateTime(atrributes[n].Year, atrributes[n].Month, atrributes[n].Day, atrributes[n].Hour, atrributes[n].Minute, atrributes[n].Second);
File.SetLastWriteTime(outputFile, date);
}
catch (ArgumentOutOfRangeException)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("Warning: Invalid date. Ignoring.");
Console.ForegroundColor = ConsoleColor.White;
}
}

filelist[n] = outputFile; //Save the list of files in order to have the original order
}

Console.WriteLine();

if (!string.IsNullOrEmpty(filesList)) File.WriteAllLines(filesList, filelist);
}
}

uint Pad(uint value, uint padBytes)
{
if ((value % padBytes) != 0) return value + (padBytes - (value % padBytes));
else return value;
}

string[] CheckForDuplicatedFilenames(string[] fileNames)
{
string[] output = new string[fileNames.Length];

for (int n = 0; n < fileNames.Length; n++)
{
int count = 0;

for (int o = 0; o < n; o++)
{
if (fileNames[n] == fileNames[o]) count++;
}

if (count == 0) output[n] = fileNames[n];
else output[n] = $"{Path.GetFileNameWithoutExtension(fileNames[n])} ({count}){Path.GetExtension(fileNames[n])}";
}

return output;
}

public struct TableOfContents
{
public uint Offset;
public uint FileSize;
}

public struct FileAttributes
{
public string FileName;
public ushort Year;
public ushort Month;
public ushort Day;
public ushort Hour;
public ushort Minute;
public ushort Second;
public uint FileSize;
}
}
}
Loading

0 comments on commit 1bb7c53

Please sign in to comment.