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 @@ + + + + + + + + +