diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e989c02
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+XLPakTool/bin/
+XLPakTool/obj/
diff --git a/XLPakTool.sln b/XLPakTool.sln
new file mode 100644
index 0000000..30b6cfd
--- /dev/null
+++ b/XLPakTool.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28010.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XLPakTool", "XLPakTool\XLPakTool.csproj", "{30356FF6-4A19-4B70-A776-D69D2167238A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {30356FF6-4A19-4B70-A776-D69D2167238A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30356FF6-4A19-4B70-A776-D69D2167238A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30356FF6-4A19-4B70-A776-D69D2167238A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30356FF6-4A19-4B70-A776-D69D2167238A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C53225D8-FED9-4A94-B940-7FCB7F8DAEFD}
+ EndGlobalSection
+EndGlobal
diff --git a/XLPakTool/App.config b/XLPakTool/App.config
new file mode 100644
index 0000000..731f6de
--- /dev/null
+++ b/XLPakTool/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XLPakTool/Program.cs b/XLPakTool/Program.cs
new file mode 100644
index 0000000..0ff480b
--- /dev/null
+++ b/XLPakTool/Program.cs
@@ -0,0 +1,394 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace XLPakTool
+{
+ public class TreeDictionary
+ {
+ public string Path { get; set; }
+ public TreeDictionary Parent { get; set; }
+ public List Directories { get; set; }
+ public List Files { get; set; }
+
+ public TreeDictionary(string path)
+ {
+ Path = path;
+ Directories = new List();
+ Files = new List();
+ }
+ }
+
+ class Program
+ {
+ public static string GlobalPath = "/master/";
+ public static string FsPath;
+
+ static void Main(string[] args)
+ {
+ if (args.Length == 0)
+ {
+ Console.WriteLine("Start: XLPakTool.exe ");
+ return;
+ }
+
+ var gamePakPath = args[0];
+
+ Log("Info", "Create file system...");
+ if (XLPack.CreateFileSystem())
+ {
+ Log("Info", "Done");
+
+ Log("Info", "Connect log handler...");
+ XLPack.SetFileLogHandler("pack.log", LogHandler);
+ Log("Info", "Done");
+
+ MountFileSystem(gamePakPath);
+ Thread.Sleep(1000);
+
+ while (true)
+ {
+ Console.Write($"~{GlobalPath}$ ");
+ var command = Console.ReadLine();
+ var parse = CommandParser(command);
+
+ if (parse.Length > 0)
+ {
+ var cmd = parse[0];
+ var cmdArgs = new string[parse.Length - 1];
+ if (cmdArgs.Length > 0)
+ Array.Copy(parse, 1, cmdArgs, 0, cmdArgs.Length);
+
+ if (cmd == "quit")
+ break;
+ else
+ {
+ switch (cmd)
+ {
+ case "help":
+ Log("Info", "cd -> move at folders");
+ Log("Info", "ls -> get files");
+ Log("Info", "cp -> copy file from src to dest");
+ Log("Info", "rm -> remove path");
+ Console.WriteLine("--------------------------------");
+ Log("Info", "To export file(s)/dir:");
+ Log("Info", "cp /fs/");
+ Log("Info", "To import file(s)/dir:");
+ Log("Info", "cp /fs/ ");
+ break;
+ case "cd":
+ if (cmdArgs.Length == 0)
+ Log("Info", "cd ");
+ else
+ {
+ var cmdPath = cmdArgs[0];
+ var prePath = GlobalPath;
+ GlobalPath = AbsolutePath(cmdPath);
+ if (!GlobalPath.EndsWith("/") && !GlobalPath.EndsWith("\\"))
+ GlobalPath += "/";
+
+ if (!IsDirectory(GlobalPath))
+ GlobalPath = prePath;
+ }
+ break;
+ case "ls":
+ var path = GlobalPath;
+
+ if (cmdArgs.Length > 0)
+ {
+ path = AbsolutePath(cmdArgs[0]);
+ path += "/";
+ }
+
+ var files = GetFiles(path);
+ if (files.Count > 0)
+ foreach (var file in files)
+ {
+ if (IsDirectory(file))
+ Console.BackgroundColor = ConsoleColor.Blue;
+ Console.WriteLine(file.Replace(path, ""));
+ Console.ResetColor();
+ }
+ else
+ Console.WriteLine("------ EMPTY ------");
+ break;
+ case "cp":
+ if (cmdArgs.Length < 2)
+ Log("Info", "cp ");
+ else
+ {
+ var src = AbsolutePath(cmdArgs[0]);
+ var dest = AbsolutePath(cmdArgs[1]);
+
+ var exist = false;
+
+ if (src.StartsWith("/fs"))
+ {
+ var realyPath = src.Replace("/fs", FsPath);
+ exist = File.Exists(realyPath);
+ if (!exist)
+ exist = Directory.Exists(realyPath);
+ }
+ else
+ exist = IsPathExist(src);
+
+ Thread.Sleep(1000);
+
+ if (dest.StartsWith("/fs"))
+ {
+ var realyPath = dest.Replace("/fs", FsPath);
+ Directory.CreateDirectory(realyPath);
+ }
+
+ if (!exist)
+ Log("Warn", "Bad source path: {0}", src);
+ else
+ {
+ var result = false;
+ if (IsDirectory(src))
+ result = XLPack.CopyDir(src, dest);
+ else
+ result = XLPack.Copy(src, dest);
+
+ if (result)
+ Console.WriteLine("Done");
+ else
+ Console.WriteLine("Copy failed...");
+ }
+ }
+ break;
+ case "rm":
+ if (cmdArgs.Length == 0)
+ Log("Info", "rm ");
+ else
+ {
+ path = AbsolutePath(cmdArgs[0]);
+ if (IsDirectory(path))
+ {
+ if (XLPack.DeleteDir(path))
+ Console.WriteLine("Done");
+ else
+ Console.WriteLine("Remove failed...");
+ }
+ else
+ {
+ if (XLPack.FDelete(path))
+ Console.WriteLine("Done");
+ else
+ Console.WriteLine("Remove failed...");
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ Destroy();
+ }
+ else
+ Log("Error", "Cannot create file system");
+ }
+
+ private static void LogHandler(params string[] p)
+ {
+ foreach (string str in p)
+ Console.WriteLine(str);
+ }
+
+ private static void MountFileSystem(string path)
+ {
+ var pack = new FileInfo(path);
+
+ FsPath = pack.DirectoryName;
+
+ Log("Info", "Mount /fs ...");
+ XLPack.Mount("/fs", FsPath, true);
+ Log("Info", "Done");
+
+ Log("Info", "Mount /master ...");
+ XLPack.Mount("/master", path, true);
+ Log("Info", "Done");
+ }
+
+ public static async Task GetFileSystemStruct(TreeDictionary master)
+ {
+ var files = await GetFilesAsync(master.Path + "/");
+ foreach (var file in files)
+ {
+ if (await IsDirectoryAsync(file))
+ {
+ var folder = new TreeDictionary(file);
+ folder.Parent = master;
+ await GetFileSystemStruct(folder);
+ master.Directories.Add(folder);
+ }
+ else
+ master.Files.Add(file);
+ }
+ return master;
+ }
+
+ private static Task> GetFilesAsync(string path)
+ {
+ return Task.FromResult(GetFiles(path));
+ }
+
+ private static List GetFiles(string path)
+ {
+ var result = new List();
+
+ var file = path + "*";
+ XLPack.afs_finddata fd = new XLPack.afs_finddata();
+ int findHandle = XLPack.FindFirst(path, ref fd);
+ if (findHandle != -1)
+ {
+ do
+ {
+ string stringAnsi = Marshal.PtrToStringAnsi(XLPack.GetFileName(ref fd));
+ result.Add(path + stringAnsi);
+ }
+ while (XLPack.FindNext(findHandle, ref fd) != -1);
+ }
+ XLPack.FindClose(findHandle);
+ return result;
+ }
+
+ private static bool IsDirectory(string path)
+ {
+ if (XLPack.IsFileExist(path.ToCharArray()))
+ return false;
+ XLPack.afs_finddata fd = new XLPack.afs_finddata();
+ int first = XLPack.FindFirst(path, ref fd);
+ bool flag = first != -1;
+ XLPack.FindClose(first);
+ return flag;
+ }
+
+ private static Task IsDirectoryAsync(string path)
+ {
+ return Task.FromResult(IsDirectory(path));
+ }
+
+ private static bool IsPathExist(string path)
+ {
+ if (XLPack.IsFileExist(path.ToCharArray()))
+ return true;
+ XLPack.afs_finddata fd = new XLPack.afs_finddata();
+ int first = XLPack.FindFirst(path, ref fd);
+ var exist = first != -1;
+ XLPack.FindClose(first);
+ return exist;
+ }
+
+ private static bool Copy(string from, string to)
+ {
+ if (IsDirectory(from))
+ return XLPack.CopyDir(from, to);
+ return XLPack.Copy(from, to);
+ }
+
+ private static Task CopyAsync(string from, string to)
+ {
+ return Task.FromResult(Copy(from, to));
+ }
+
+ private static void Destroy()
+ {
+ DestroyFileSystem();
+ XLPack.DestroyFileLogHandler(IntPtr.Zero);
+ XLPack.DestroyFileSystem();
+ }
+
+ private static void DestroyFileSystem()
+ {
+ XLPack.Unmount("/master");
+ XLPack.Unmount("/fs");
+ }
+
+ public static string[] CommandParser(string str)
+ {
+ if (str == null || !(str.Length > 0)) return new string[0];
+ int idx = str.Trim().IndexOf(" ");
+ if (idx == -1) return new string[] { str };
+ int count = str.Length;
+ ArrayList list = new ArrayList();
+ while (count > 0)
+ {
+ if (str[0] == '"')
+ {
+ int temp = str.IndexOf("\"", 1, str.Length - 1);
+ while (str[temp - 1] == '\\')
+ {
+ temp = str.IndexOf("\"", temp + 1, str.Length - temp - 1);
+ }
+ idx = temp + 1;
+ }
+ if (str[0] == '\'')
+ {
+ int temp = str.IndexOf("\'", 1, str.Length - 1);
+ while (str[temp - 1] == '\\')
+ {
+ temp = str.IndexOf("\'", temp + 1, str.Length - temp - 1);
+ }
+ idx = temp + 1;
+ }
+ string s = str.Substring(0, idx);
+ int left = count - idx;
+ str = str.Substring(idx, left).Trim();
+ list.Add(s.Trim('"'));
+ count = str.Length;
+ idx = str.IndexOf(" ");
+ if (idx == -1)
+ {
+ string add = str.Trim('"', ' ');
+ if (add.Length > 0)
+ {
+ list.Add(add);
+ }
+ break;
+ }
+ }
+ return (string[])list.ToArray(typeof(string));
+ }
+
+ private static string ExtractFileDirectory(string path)
+ {
+ if (path == "/")
+ return path;
+
+ var index = path.LastIndexOfAny("/".ToCharArray());
+ return path.Substring(0, index);
+ }
+
+ public static string AbsolutePath(string path)
+ {
+ if (path.Length == 0)
+ return path;
+ if (path.StartsWith("/") || path.StartsWith("\\")) return path;
+ if (path.Length == 1 && path == ".") return GlobalPath;
+ var basePath = ExtractFileDirectory(GlobalPath);
+ var relativePath = path;
+ while (relativePath.Substring(0, 2) == "..")
+ {
+ basePath = ExtractFileDirectory(basePath);
+ if (relativePath.Length < 3)
+ return "";
+ else
+ relativePath = relativePath.Substring(3, relativePath.Length - 3);
+ if (relativePath.Length == 0)
+ return basePath;
+ }
+ return basePath + "/" + relativePath;
+ }
+
+ public static void Log(string level, string message, params string[] args)
+ {
+ Console.WriteLine($"[{level}] {string.Format(message, args)}");
+ }
+ }
+}
diff --git a/XLPakTool/Properties/AssemblyInfo.cs b/XLPakTool/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f74eb82
--- /dev/null
+++ b/XLPakTool/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Общие сведения об этой сборке предоставляются следующим набором
+// набора атрибутов. Измените значения этих атрибутов, чтобы изменить сведения,
+// связанные со сборкой.
+[assembly: AssemblyTitle("XLPakTool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("XLPakTool")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
+// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
+// COM, задайте атрибуту ComVisible значение TRUE для этого типа.
+[assembly: ComVisible(false)]
+
+// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
+[assembly: Guid("30356ff6-4a19-4b70-a776-d69d2167238a")]
+
+// Сведения о версии сборки состоят из следующих четырех значений:
+//
+// Основной номер версии
+// Дополнительный номер версии
+// Номер сборки
+// Редакция
+//
+// Можно задать все значения или принять номер сборки и номер редакции по умолчанию.
+// используя "*", как показано ниже:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/XLPakTool/XLPack.cs b/XLPakTool/XLPack.cs
new file mode 100644
index 0000000..d80386a
--- /dev/null
+++ b/XLPakTool/XLPack.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace XLPakTool
+{
+ internal class XLPack
+ {
+ [DllImport("xlpack.dll", EntryPoint = "?ApplyPatchPak@@YA_NPBD0@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool ApplyPatchPak([MarshalAs(UnmanagedType.LPStr)] string s1, [MarshalAs(UnmanagedType.LPStr)] string s2);
+
+ [DllImport("xlpack.dll", EntryPoint = "?Copy@@YA_NPBD0@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool Copy([MarshalAs(UnmanagedType.LPStr)] string from, [MarshalAs(UnmanagedType.LPStr)] string to);
+
+ [DllImport("xlpack.dll", EntryPoint = "?CopyDir@@YA_NPBD0@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool CopyDir([MarshalAs(UnmanagedType.LPStr)] string from, [MarshalAs(UnmanagedType.LPStr)] string to);
+
+ [DllImport("xlpack.dll", EntryPoint = "?CreateFileSystem@@YA_NXZ", CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool CreateFileSystem();
+
+ [DllImport("xlpack.dll", EntryPoint = "?DestroyFileLogHandler@@YAXPAX@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void DestroyFileLogHandler(IntPtr lp1);
+
+ [DllImport("xlpack.dll", EntryPoint = "?DestroyFileSystem@@YAXXZ", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void DestroyFileSystem();
+
+ [DllImport("xlpack.dll", EntryPoint = "?FDelete@@YA_NPBD@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool FDelete([MarshalAs(UnmanagedType.LPStr)] string where);
+
+ [DllImport("xlpack.dll", EntryPoint = "?FindClose@@YAHH@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int FindClose(int i);
+
+ [DllImport("xlpack.dll", EntryPoint = "?FindFirst@@YAHPBDPAUafs_finddata@@@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int FindFirst([MarshalAs(UnmanagedType.LPStr)] string file, ref afs_finddata fd);
+
+ [DllImport("xlpack.dll", EntryPoint = "?FindNext@@YAHHPAUafs_finddata@@@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int FindNext(int i, ref afs_finddata fd);
+
+ [DllImport("xlpack.dll", EntryPoint = "?GetFileName@@YAPBDPBUafs_finddata@@@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr GetFileName(ref afs_finddata fd);
+
+ [DllImport("xlpack.dll", EntryPoint = "?IsDirectory@@YA_NPBUafs_finddata@@@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern char IsDirectory(ref afs_finddata fd);
+
+ [DllImport("xlpack.dll", EntryPoint = "?IsFileExist@@YA_NPBD@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool IsFileExist(char[] file);
+
+ [DllImport("xlpack.dll", EntryPoint = "?Mount@@YAPAXPBD0_N@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr Mount([MarshalAs(UnmanagedType.LPStr)] string where, [MarshalAs(UnmanagedType.LPStr)] string which, [MarshalAs(UnmanagedType.Bool)] bool editable);
+
+ [DllImport("xlpack.dll", EntryPoint = "?SetFileLogHandler@@YAPAXPBDP6AX0ZZ@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr SetFileLogHandler([MarshalAs(UnmanagedType.LPStr)] string s, XlFunc f);
+
+ [DllImport("xlpack.dll", EntryPoint = "?Unmount@@YA_NPBD@Z", CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool Unmount([MarshalAs(UnmanagedType.LPStr)] string where);
+
+ [DllImport("xlpack.dll", EntryPoint = "?DeleteDir@@YA_NPBD@Z", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern bool DeleteDir([MarshalAs(UnmanagedType.LPStr)] string path);
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct afs_finddata
+ {
+ [FieldOffset(0)] public long data0;
+ [FieldOffset(8)] public long data1;
+ [FieldOffset(16)] public long data2;
+ [FieldOffset(24)] public long data3;
+ [FieldOffset(32)] public long data4;
+ [FieldOffset(40)] public long data5;
+ [FieldOffset(48)] public long data6;
+ [FieldOffset(56)] public long data7;
+ [FieldOffset(64)] public long data8;
+ [FieldOffset(72)] public long data9;
+ [FieldOffset(80)] public long data10;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct File
+ {
+ [FieldOffset(0)] public uint pntr;
+ [FieldOffset(4)] public uint cnt;
+ [FieldOffset(8)] public uint based; // base
+ [FieldOffset(12)] public uint flag;
+ [FieldOffset(16)] public uint file;
+ [FieldOffset(20)] public uint charbuf;
+ [FieldOffset(24)] public uint bufsize;
+ [FieldOffset(28)] public uint tmpfname;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct XlFileInfo
+ {
+ [FieldOffset(0)] public uint dwFileAttributes;
+ [FieldOffset(4)] public ulong ftCreationTime;
+ [FieldOffset(12)] public ulong ftLastAccessTime;
+ [FieldOffset(20)] public ulong ftLastWriteTime;
+ [FieldOffset(28)] public uint dwVolumeSerialNumber;
+ [FieldOffset(32)] public uint nFileSizeHigh;
+ [FieldOffset(36)] public uint nFileSizeLow;
+ [FieldOffset(40)] public uint nNumberOfLinks;
+ [FieldOffset(44)] public uint nFileIndexHigh;
+ [FieldOffset(48)] public uint nFileIndexLow;
+ }
+
+ public delegate void XlFunc(params string[] p);
+ }
+}
diff --git a/XLPakTool/XLPakTool.csproj b/XLPakTool/XLPakTool.csproj
new file mode 100644
index 0000000..b49d83b
--- /dev/null
+++ b/XLPakTool/XLPakTool.csproj
@@ -0,0 +1,59 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {30356FF6-4A19-4B70-A776-D69D2167238A}
+ Exe
+ XLPakTool
+ XLPakTool
+ v4.6.1
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
\ No newline at end of file