Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DotNet CoreApp Versión #5

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions SfPack.Dotnet/Program.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
/// <summary>
/// Extracts full path of the file or directory and the search pattern.
/// </summary>
/// <param name="path">Raw path.</param>
/// <returns>Array [Full path, Search pattern].</returns>
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;
}
}
}
33 changes: 33 additions & 0 deletions SfPack.Dotnet/SfPack.Dotnet.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup Condition="'$(BuildingInsideVisualStudio)' != 'true' AND ('$(RuntimeIdentifier)' == 'win-x64' OR '$(RuntimeIdentifier)' == 'linux-x64')">
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="1.0.0-alpha-*" />
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TrimUnusedDependencies>true</TrimUnusedDependencies>
</PropertyGroup>

<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' != 'true' AND ('$(RuntimeIdentifier)' == 'win-x64' OR '$(RuntimeIdentifier)' == 'linux-x64') AND '$(Configuration)' == 'Release'">
<!-- Disables Exception.ToString() -->
<IlcDisableReflection>true</IlcDisableReflection>
</PropertyGroup>

<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' != 'true' AND ('$(RuntimeIdentifier)' == 'win-x64' OR '$(RuntimeIdentifier)' == 'linux-x64')">
<RootAllApplicationAssemblies>false</RootAllApplicationAssemblies>
<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
</PropertyGroup>
</Project>
61 changes: 61 additions & 0 deletions SfPack.Dotnet/SfpEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Runtime.InteropServices;

namespace SfPack.Dotnet
{
/// <summary>
/// Generic struct of sfp file entry.
/// </summary>
[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; }

/// <summary>
/// Builds a sfp entry from its raw bytes.
/// </summary>
/// <param name="data">Raw bytes.</param>
/// <returns>Sfp entry.</returns>
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;
}
/// <summary>
/// Writes into the console human readable information about sfp file entry.
/// </summary>
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}");
}
}
}
216 changes: 216 additions & 0 deletions SfPack.Dotnet/SfpFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace SfPack.Dotnet
{
/// <summary>
/// Static class with utilities to process sfp files.
/// </summary>
public static class SfpFile
{
/// <summary>
/// Extracts the content of a sfp file from its path.
/// </summary>
/// <param name="filePath">Sfp file path.</param>
/// <param name="overwrite">Indicates whether the files should be overwritten.</param>
/// <param name="extractPath">Extraction path.</param>
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);
}
}

/// <summary>
/// Extract the content of a sfp file from a stream.
/// </summary>
/// <param name="strm">Sfp stream data.</param>
/// <param name="overwrite">Indicates whether the files should be overwritten.</param>
/// <param name="extractPath">Extraction path.</param>
private static void ExtractStream(Stream strm, Boolean overwrite, String extractPath)
{
SfpHeader header = ReadHeader(strm);
if (header.ArchiveSize == strm.Length) //1st Validation
{
IReadOnlyDictionary<Int64, String> nameTable = ReadNameTable(strm, header);
if (!nameTable.ContainsKey(0)) //2nd Validation
{
Dictionary<Int64, SfpEntry> entries = new Dictionary<Int64, SfpEntry>();
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.");

}
/// <summary>
/// Read from stream the sfp file header.
/// </summary>
/// <param name="strm">Sfp stream data.</param>
/// <returns>Sfp file header.</returns>
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);
}
/// <summary>
/// Read the table of names in the sfp file and index them by offset.
/// </summary>
/// <param name="strm">Sfp stream data.</param>
/// <param name="header">Sfp file header</param>
/// <returns>Sfp file name table indexed by offset.</returns>
private static IReadOnlyDictionary<Int64, String> ReadNameTable(Stream strm, SfpHeader header)
{
Dictionary<Int64, String> nameTable = new Dictionary<Int64, String>();
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<Byte> lista = data.ToList<Byte>();
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;
}
/// <summary>
/// Reads recursively all the entries contained in the sfp file.
/// </summary>
/// <param name="strm">Sfp stream data.</param>
/// <param name="offset_ent">Sfp entry offset.</param>
/// <param name="entries">Sfp entries indexed by offset.</param>
private unsafe static void ReadEntry(Stream strm, Int64 offset_ent, Dictionary<Int64, SfpEntry> 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);
}
/// <summary>
/// Creates the relative path of an entry in the sfp file.
/// </summary>
/// <param name="entries">Sfp entries indexed by offset.</param>
/// <param name="nameTable">Sfp file name table indexed by offset.</param>
/// <param name="offset">Sfp entry offset.</param>
/// <returns>Relative path of sfp entry.</returns>
private static String BuildPath(IReadOnlyDictionary<Int64, SfpEntry> entries, IReadOnlyDictionary<Int64, String> 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;
}
/// <summary>
/// Creates a file or directory which represent a sfp entry.
/// </summary>
/// <param name="strm">Sfp stream data.</param>
/// <param name="nameTable">Sfp file name table indexed by offset.</param>
/// <param name="entries">Sfp entries indexed by offset.</param>
/// <param name="overwrite">Indicates whether the files should be overwritten.</param>
/// <param name="extractPath">Extraction path.</param>
private static void ExtractFiles(Stream strm, IReadOnlyDictionary<Int64, String> nameTable, IReadOnlyDictionary<Int64, SfpEntry> entries, Boolean overwrite, String extractPath)
{
DirectoryInfo dir = null;
FileInfo file = null;
Byte[] data;

String path;
Int64 directoryCount = 0;
Int64 filesCount = 0;

foreach (KeyValuePair<Int64, SfpEntry> 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")}.");
}
}
}
54 changes: 54 additions & 0 deletions SfPack.Dotnet/SfpHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Runtime.InteropServices;

namespace SfPack.Dotnet
{
/// <summary>
/// Generic struct of sfp file header.
/// </summary>
[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; }

/// <summary>
/// Builds a sfp header from its raw bytes.
/// </summary>
/// <param name="data">Raw bytes.</param>
/// <returns>Sfp header.</returns>
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;
}
/// <summary>
/// Writes into the console human readable information about sfp file header.
/// </summary>
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}");
}
}
}
9 changes: 9 additions & 0 deletions SfPack.Dotnet/nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>