From 3649d5d9bbb479ba840587123eba0c0eedd3942d Mon Sep 17 00:00:00 2001
From: josephmoresena <44370115+josephmoresena@users.noreply.github.com>
Date: Wed, 10 Apr 2019 18:09:56 -0500
Subject: [PATCH 1/2] =?UTF-8?q?DotNet=20CoreApp=20Versi=C3=B3n?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A built on pure C# using .Net Core.
Can be a candidate for CoreRT on Win-x64 and Linux-x64 platforms
---
SfPack.Dotnet/Program.cs | 65 +++++++++
SfPack.Dotnet/SfPack.Dotnet.csproj | 33 +++++
SfPack.Dotnet/SfpEntry.cs | 61 ++++++++
SfPack.Dotnet/SfpFile.cs | 216 +++++++++++++++++++++++++++++
SfPack.Dotnet/SfpHeader.cs | 54 ++++++++
5 files changed, 429 insertions(+)
create mode 100644 SfPack.Dotnet/Program.cs
create mode 100644 SfPack.Dotnet/SfPack.Dotnet.csproj
create mode 100644 SfPack.Dotnet/SfpEntry.cs
create mode 100644 SfPack.Dotnet/SfpFile.cs
create mode 100644 SfPack.Dotnet/SfpHeader.cs
diff --git a/SfPack.Dotnet/Program.cs b/SfPack.Dotnet/Program.cs
new file mode 100644
index 0000000..aac8fbf
--- /dev/null
+++ b/SfPack.Dotnet/Program.cs
@@ -0,0 +1,65 @@
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace SfPack.Dotnet
+{
+ class Program
+ {
+ static void Main(String[] args)
+ {
+ String[] path;
+ switch (args.Length)
+ {
+ case 0:
+ Console.WriteLine("Please specify a path of file or directory.");
+ break;
+ case 1:
+ //Process a file or all *.sfp into a directory.
+ path = GetPathExtension(args[0]);
+ if (!String.IsNullOrEmpty(path[0]))
+ if (Directory.Exists(path[0]))
+ foreach (FileInfo file in new DirectoryInfo(path[0]).GetFiles(path[1]))
+ SfpFile.ExtractFile(file.FullName);
+ else
+ SfpFile.ExtractFile(path[0]);
+ else
+ Console.WriteLine("The file or directory is not valid.");
+ break;
+ default:
+ Console.WriteLine("Call has some invalid arguments.");
+ break;
+ }
+ }
+ ///
+ /// Extracts full path of the file or directory and the search pattern.
+ ///
+ /// Raw path.
+ /// Array [Full path, Search pattern].
+ private static String[] GetPathExtension(String path)
+ {
+ String[] ext = new String[2];
+
+ if (Regex.IsMatch(path, "^\".+\"$"))
+ ext[0] = path.Substring(1, path.Length - 2);
+ else
+ ext[0] = path;
+ ext[1] = "*.sfp";
+ try
+ {
+ ext[0] = Regex.Replace(ext[0], @"^.+(\*(.\w+){0,1})$", (m) =>
+ {
+ ext[1] = m.Groups[1].Value;
+ return m.Value.Replace(ext[1], "");
+ });
+ ext[0] = Path.GetFullPath(ext[0]);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Exception: {ex.Message}.");
+ ext[0] = null;
+ }
+ return ext;
+ }
+ }
+}
diff --git a/SfPack.Dotnet/SfPack.Dotnet.csproj b/SfPack.Dotnet/SfPack.Dotnet.csproj
new file mode 100644
index 0000000..b426dce
--- /dev/null
+++ b/SfPack.Dotnet/SfPack.Dotnet.csproj
@@ -0,0 +1,33 @@
+
+
+
+ Exe
+ netcoreapp2.2
+
+
+
+ true
+
+
+
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+ false
+ false
+ false
+ Speed
+ Size
+ true
+
+
diff --git a/SfPack.Dotnet/SfpEntry.cs b/SfPack.Dotnet/SfpEntry.cs
new file mode 100644
index 0000000..0fa5777
--- /dev/null
+++ b/SfPack.Dotnet/SfpEntry.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SfPack.Dotnet
+{
+ ///
+ /// Generic struct of sfp file entry.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SfpEntry
+ {
+ public Int32 Magic { get; private set; }
+ public Int64 NameOffset { get; private set; }
+ public Int32 Unknown1 { get; private set; }
+ public Int64 ParentOffset { get; private set; }
+ public Int32 IsDir { get; private set; }
+ public Int64 FileLength { get; private set; }
+ public Int64 ModifiedTime { get; private set; }
+ public Int64 CreatedTime { get; private set; }
+ public Int64 Unknown2 { get; private set; }
+ public Int64 Unknown3 { get; private set; }
+ public Int64 StartOffset { get; private set; }
+ public Int32 DataLength { get; private set; }
+
+ ///
+ /// Builds a sfp entry from its raw bytes.
+ ///
+ /// Raw bytes.
+ /// Sfp entry.
+ internal static unsafe SfpEntry CreateFromBytes(Byte[] data)
+ {
+ SfpEntry _directory;
+
+ if (data == null || data.Length != sizeof(SfpEntry))
+ throw new Exception("INVALID SFP ENTRY DATA");
+
+ fixed (Byte* map = &data[0])
+ _directory = *(SfpEntry*)map;
+
+ return _directory;
+ }
+ ///
+ /// Writes into the console human readable information about sfp file entry.
+ ///
+ internal void Print()
+ {
+ Console.WriteLine($"magic: {Magic}");
+ Console.WriteLine($"nameOffset: {NameOffset}");
+ Console.WriteLine($"unk1: {Unknown1}");
+ Console.WriteLine($"parentOffset: {ParentOffset}");
+ Console.WriteLine($"isDir: {IsDir}");
+ Console.WriteLine($"fileLength: {FileLength}");
+ Console.WriteLine($"modifiedTime: {ModifiedTime}");
+ Console.WriteLine($"createdTime: {CreatedTime}");
+ Console.WriteLine($"unk2: {Unknown2}");
+ Console.WriteLine($"unk3: {Unknown3}");
+ Console.WriteLine($"startOffset: {StartOffset}");
+ Console.WriteLine($"dataLength: {DataLength}");
+ }
+ }
+}
diff --git a/SfPack.Dotnet/SfpFile.cs b/SfPack.Dotnet/SfpFile.cs
new file mode 100644
index 0000000..fb3d704
--- /dev/null
+++ b/SfPack.Dotnet/SfpFile.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace SfPack.Dotnet
+{
+ ///
+ /// Static class with utilities to process sfp files.
+ ///
+ public static class SfpFile
+ {
+ ///
+ /// Extracts the content of a sfp file from its path.
+ ///
+ /// Sfp file path.
+ /// Indicates whether the files should be overwritten.
+ /// Extraction path.
+ public static void ExtractFile(String filePath, Boolean overwrite = true, String extractPath = null)
+ {
+ FileInfo file;
+ DirectoryInfo dir;
+ file = new FileInfo(filePath);
+ Console.WriteLine($"{file.FullName} {(!file.Exists ? "no " : "")}found.");
+ if (file.Exists)
+ {
+ if (String.IsNullOrWhiteSpace(extractPath))
+ extractPath = file.FullName.Replace(file.Extension, "");
+ else
+ {
+ dir = new DirectoryInfo(extractPath);
+ if (!dir.Exists)
+ dir.Create();
+ extractPath = new DirectoryInfo(extractPath).FullName;
+ }
+ using (FileStream fs = file.OpenRead())
+ ExtractStream(fs, overwrite, extractPath);
+ }
+ }
+
+ ///
+ /// Extract the content of a sfp file from a stream.
+ ///
+ /// Sfp stream data.
+ /// Indicates whether the files should be overwritten.
+ /// Extraction path.
+ private static void ExtractStream(Stream strm, Boolean overwrite, String extractPath)
+ {
+ SfpHeader header = ReadHeader(strm);
+ if (header.ArchiveSize == strm.Length) //1st Validation
+ {
+ IReadOnlyDictionary nameTable = ReadNameTable(strm, header);
+ if (!nameTable.ContainsKey(0)) //2nd Validation
+ {
+ Dictionary entries = new Dictionary();
+ ReadEntry(strm, header.FirstEntryOffset, entries);
+ ExtractFiles(strm, nameTable, entries, overwrite, extractPath);
+ }
+ else
+ Console.WriteLine("Selected file is not a valid sfp. An error found in the file name table.");
+ }
+ else
+ Console.WriteLine("Selected file is not a valid sfp. An error found in the file header.");
+
+ }
+ ///
+ /// Read from stream the sfp file header.
+ ///
+ /// Sfp stream data.
+ /// Sfp file header.
+ private static unsafe SfpHeader ReadHeader(Stream strm)
+ {
+ Byte[] data = new Byte[sizeof(SfpHeader)];
+ strm.Seek(0, SeekOrigin.Begin);
+ strm.Read(data, 0, data.Length);
+ return SfpHeader.CreateFromBytes(data);
+ }
+ ///
+ /// Read the table of names in the sfp file and index them by offset.
+ ///
+ /// Sfp stream data.
+ /// Sfp file header
+ /// Sfp file name table indexed by offset.
+ private static IReadOnlyDictionary ReadNameTable(Stream strm, SfpHeader header)
+ {
+ Dictionary nameTable = new Dictionary();
+ Byte[] data = new Byte[header.DataOffset - header.NameTableOffset];
+ strm.Seek(header.NameTableOffset, SeekOrigin.Begin);
+ strm.Read(data, 0, data.Length);
+
+ Int32 pos_ini = 0;
+ Int32 pos_fin = 0;
+ List lista = data.ToList();
+ foreach(Byte bytChar in lista)
+ {
+ pos_fin++;
+ if (bytChar == 0)
+ {
+ if(pos_fin - pos_ini > 1)
+ nameTable.Add(header.NameTableOffset + (Int64)pos_ini, Encoding.ASCII.GetString(lista.GetRange(pos_ini, pos_fin - pos_ini - 1).ToArray()));
+ pos_ini = pos_fin;
+ }
+ }
+ return nameTable;
+ }
+ ///
+ /// Reads recursively all the entries contained in the sfp file.
+ ///
+ /// Sfp stream data.
+ /// Sfp entry offset.
+ /// Sfp entries indexed by offset.
+ private unsafe static void ReadEntry(Stream strm, Int64 offset_ent, Dictionary entries)
+ {
+ SfpEntry entry;
+ Byte[] data;
+ strm.Seek(offset_ent, SeekOrigin.Begin);
+ data = new Byte[sizeof(SfpEntry)];
+ strm.Read(data, 0, data.Length);
+ entry = SfpEntry.CreateFromBytes(data);
+ entries.Add(offset_ent, entry);
+ if (entry.IsDir == 1)
+ for (Int64 offset = entry.StartOffset; offset < (entry.StartOffset + entry.DataLength); offset += data.Length)
+ ReadEntry(strm, offset, entries);
+ }
+ ///
+ /// Creates the relative path of an entry in the sfp file.
+ ///
+ /// Sfp entries indexed by offset.
+ /// Sfp file name table indexed by offset.
+ /// Sfp entry offset.
+ /// Relative path of sfp entry.
+ private static String BuildPath(IReadOnlyDictionary entries, IReadOnlyDictionary nameTable, Int64 offset)
+ {
+ SfpEntry entry = entries[offset];
+ String path = "";
+ do
+ {
+ if(entry.NameOffset != 0)
+ path = $"{nameTable[entry.NameOffset]}{path}";
+ offset = entry.ParentOffset;
+ entry = entries[offset];
+ path = $"{Path.DirectorySeparatorChar}{path}";
+ } while (offset != entry.ParentOffset);
+ return path;
+ }
+ ///
+ /// Creates a file or directory which represent a sfp entry.
+ ///
+ /// Sfp stream data.
+ /// Sfp file name table indexed by offset.
+ /// Sfp entries indexed by offset.
+ /// Indicates whether the files should be overwritten.
+ /// Extraction path.
+ private static void ExtractFiles(Stream strm, IReadOnlyDictionary nameTable, IReadOnlyDictionary entries, Boolean overwrite, String extractPath)
+ {
+ DirectoryInfo dir = null;
+ FileInfo file = null;
+ Byte[] data;
+
+ String path;
+ Int64 directoryCount = 0;
+ Int64 filesCount = 0;
+
+ foreach (KeyValuePair pair in entries)
+ {
+ path = $"{extractPath}{BuildPath(entries, nameTable, pair.Key)}";
+ try
+ {
+ if (pair.Value.IsDir == 1)
+ {
+ dir = new DirectoryInfo(path);
+ if (!dir.Exists)
+ dir.Create();
+ directoryCount++;
+ dir = null;
+ }
+ else if (pair.Value.IsDir == 0)
+ {
+ file = new FileInfo(path);
+ if (file.Exists && overwrite)
+ {
+ file.Delete();
+ file.Refresh();
+ }
+ if (!file.Exists)
+ {
+ using (FileStream fs = file.Create())
+ using (BinaryWriter wrt = new BinaryWriter(fs))
+ {
+ data = new Byte[pair.Value.DataLength];
+ strm.Seek(pair.Value.StartOffset, SeekOrigin.Begin);
+ strm.Read(data, 0, data.Length);
+ wrt.Write(data);
+ data = null;
+ }
+ filesCount++;
+ }
+ file = null;
+ }
+ else
+ {
+ throw new Exception("Invalid directory flag value");
+ }
+ Console.WriteLine($"{path} {(pair.Value.IsDir == 0 ? $".. {pair.Value.DataLength} bytes.":"")}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Exception: {ex.Message}.");
+ Console.WriteLine($"{path} could not be processed.");
+ }
+ }
+ Console.WriteLine($"{filesCount} file{(filesCount == 1? "":"s")} extracted into {directoryCount} folder{(directoryCount == 1 ? "" : "s")}.");
+ }
+ }
+}
diff --git a/SfPack.Dotnet/SfpHeader.cs b/SfPack.Dotnet/SfpHeader.cs
new file mode 100644
index 0000000..d6f47ae
--- /dev/null
+++ b/SfPack.Dotnet/SfpHeader.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SfPack.Dotnet
+{
+ ///
+ /// Generic struct of sfp file header.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SfpHeader
+ {
+ public Int32 Magic { get; private set; }
+ public Int32 Version { get; private set; }
+ public Int64 Unknown1 { get; private set; }
+ public Int64 FirstEntryOffset { get; private set; }
+ public Int64 NameTableOffset { get; private set; }
+ public Int64 DataOffset { get; private set; }
+ public Int64 ArchiveSize { get; private set; }
+ public Int64 PackageLabelOffset { get; private set; }
+ public Int64 Unknown3 { get; private set; }
+
+ ///
+ /// Builds a sfp header from its raw bytes.
+ ///
+ /// Raw bytes.
+ /// Sfp header.
+ internal static unsafe SfpHeader CreateFromBytes(Byte[] data)
+ {
+ SfpHeader _header;
+
+ if (data == null || data.Length != sizeof(SfpHeader))
+ throw new Exception("INVALID SFP HEADER DATA");
+
+ fixed (Byte* map = &data[0])
+ _header = *(SfpHeader*)map;
+
+ return _header;
+ }
+ ///
+ /// Writes into the console human readable information about sfp file header.
+ ///
+ internal void Print()
+ {
+ Console.WriteLine($"magic: {Magic}");
+ Console.WriteLine($"version: {Version}");
+ Console.WriteLine($"unk1: {Unknown1}");
+ Console.WriteLine($"firstDirOffset: {FirstEntryOffset}");
+ Console.WriteLine($"dataOffset: {DataOffset}");
+ Console.WriteLine($"archiveSize: {ArchiveSize}");
+ Console.WriteLine($"packageLabelOffset: {PackageLabelOffset}");
+ Console.WriteLine($"unk3: {Unknown3}");
+ }
+ }
+}
From 7796b1db48852376a2eeef29f3881df9706e21fe Mon Sep 17 00:00:00 2001
From: josephmoresena <44370115+josephmoresena@users.noreply.github.com>
Date: Wed, 24 Apr 2019 09:15:24 -0500
Subject: [PATCH 2/2] Create nuget.config
Missing nuget.config
---
SfPack.Dotnet/nuget.config | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 SfPack.Dotnet/nuget.config
diff --git a/SfPack.Dotnet/nuget.config b/SfPack.Dotnet/nuget.config
new file mode 100644
index 0000000..59f9c3a
--- /dev/null
+++ b/SfPack.Dotnet/nuget.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+