├── Hunex.sln └── HunexFileArchiveTool ├── App.config ├── Archive ├── FileEntry.cs └── Header.cs ├── HunexFileArchiveTool.csproj ├── Program.cs └── Properties └── AssemblyInfo.cs /Hunex.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34322.80 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HunexFileArchiveTool", "HunexFileArchiveTool\HunexFileArchiveTool.csproj", "{20320464-5379-4591-BDD6-4A959EC9503D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {20320464-5379-4591-BDD6-4A959EC9503D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {20320464-5379-4591-BDD6-4A959EC9503D}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {20320464-5379-4591-BDD6-4A959EC9503D}.Debug|x64.ActiveCfg = Debug|Any CPU 21 | {20320464-5379-4591-BDD6-4A959EC9503D}.Debug|x64.Build.0 = Debug|Any CPU 22 | {20320464-5379-4591-BDD6-4A959EC9503D}.Debug|x86.ActiveCfg = Debug|Any CPU 23 | {20320464-5379-4591-BDD6-4A959EC9503D}.Debug|x86.Build.0 = Debug|Any CPU 24 | {20320464-5379-4591-BDD6-4A959EC9503D}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {20320464-5379-4591-BDD6-4A959EC9503D}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {20320464-5379-4591-BDD6-4A959EC9503D}.Release|x64.ActiveCfg = Release|Any CPU 27 | {20320464-5379-4591-BDD6-4A959EC9503D}.Release|x64.Build.0 = Release|Any CPU 28 | {20320464-5379-4591-BDD6-4A959EC9503D}.Release|x86.ActiveCfg = Release|Any CPU 29 | {20320464-5379-4591-BDD6-4A959EC9503D}.Release|x86.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {E23FB474-FD45-419E-AF47-FC724599AC2F} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /HunexFileArchiveTool/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /HunexFileArchiveTool/Archive/FileEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace HunexFileArchiveTool.Archive 6 | { 7 | internal class FileEntry 8 | { 9 | public String FileName { get; set; } 10 | public UInt32 Offset { get; set; } 11 | public UInt32 Size { get; set; } 12 | public UInt32[] Reversed { get; set; } 13 | 14 | public FileEntry() { Reversed = new UInt32[6]; } 15 | public FileEntry(String filename, UInt32 offset, UInt32 size) 16 | { 17 | FileName = filename; 18 | Offset = offset; 19 | Size = size; 20 | Reversed = new UInt32[6]; 21 | } 22 | 23 | public FileEntry(BinaryReader reader) 24 | { 25 | this.FileName = Encoding.UTF8.GetString(reader.ReadBytes(0x60)).Trim('\0'); 26 | this.Offset = reader.ReadUInt32(); 27 | this.Size = reader.ReadUInt32(); 28 | Reversed = new UInt32[6]; 29 | for(int i = 0; i < 6; i++) 30 | Reversed[i] = reader.ReadUInt32(); 31 | } 32 | 33 | public void Write(BinaryWriter writer) 34 | { 35 | byte[] namebuf = new byte[0x60]; 36 | byte[] namebytes = Encoding.UTF8.GetBytes(this.FileName); 37 | Buffer.BlockCopy(namebytes, 0, namebuf, 0, namebytes.Length); 38 | writer.Write(namebuf); 39 | writer.Write(Offset); 40 | writer.Write(Size); 41 | for (int i = 0; i < 6; i++) 42 | writer.Write(Reversed[i]); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /HunexFileArchiveTool/Archive/Header.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace HunexFileArchiveTool.Archive 6 | { 7 | internal class Header 8 | { 9 | public UInt32 HEADER_SIZE = 0x10; 10 | public UInt32 ENTRY_SIZE = 0x80; 11 | 12 | // HUNEX Global Game Engine File Archive v1.0 13 | public String Magic = "HUNEXGGEFA10"; 14 | public UInt32 FileCount { get; set; } 15 | public UInt32 DataStartOffset => FileCount * ENTRY_SIZE + HEADER_SIZE; 16 | public void Write(BinaryWriter writer) 17 | { 18 | if (writer.BaseStream.Position != 0) 19 | writer.BaseStream.Position = 0; 20 | writer.Write(Encoding.ASCII.GetBytes(Magic)); 21 | writer.Write(FileCount); 22 | } 23 | public Header(uint numOfFiles) { FileCount = numOfFiles; } 24 | public Header(BinaryReader reader) 25 | { 26 | if(reader.BaseStream.Position != 0) 27 | reader.BaseStream.Position = 0; 28 | if (Encoding.ASCII.GetString(reader.ReadBytes(12)) != Magic) 29 | throw new Exception("Unknown file."); 30 | this.FileCount = reader.ReadUInt32(); 31 | } 32 | 33 | public int GetVersion() 34 | { 35 | return int.Parse(Magic.Substring(10, 1)); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /HunexFileArchiveTool/HunexFileArchiveTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {20320464-5379-4591-BDD6-4A959EC9503D} 8 | Exe 9 | HunexFileArchiveTool 10 | HunexFileArchiveTool 11 | v4.8 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /HunexFileArchiveTool/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using HunexFileArchiveTool.Archive; 7 | 8 | namespace HunexFileArchiveTool 9 | { 10 | internal class Program 11 | { 12 | static Header _hdr; 13 | static FileEntry[] _entries; 14 | static void Main(string[] args) 15 | { 16 | if(args.Length == 2) 17 | { 18 | switch (args[0]) 19 | { 20 | case "--extract": Extract(args[1]); break; 21 | case "--build": Build(args[1], args[1] + ".hfa"); break; 22 | case "--patch": break; 23 | default: PrintUsage(); break; 24 | } 25 | } 26 | else 27 | { 28 | PrintUsage(); 29 | return; 30 | } 31 | } 32 | 33 | static void PrintUsage() 34 | { 35 | Console.WriteLine("WITCH ON THE HOLY NIGHT HFA (Hunex) Archive Tool by LinkOFF"); 36 | Console.WriteLine(""); 37 | Console.WriteLine("Usage: "); 38 | Console.WriteLine(" --extract\tExtract all archive contents in the directory."); 39 | Console.WriteLine(" --build\tBuild a new archive from given directory."); 40 | //Console.WriteLine(" --patch\tReplace existed file inside archive."); 41 | } 42 | 43 | static void Build(string inputDir, string hfaFile) 44 | { 45 | string[] files = Directory.GetFiles(inputDir, "*.*", SearchOption.TopDirectoryOnly); 46 | if(files.Length == 0) 47 | { 48 | Console.WriteLine("Directory ({0}) is empty.", inputDir); 49 | return; 50 | } 51 | using(BinaryWriter writer = new BinaryWriter(File.Create(hfaFile))) 52 | { 53 | _hdr = new Header((uint)files.Length); 54 | _hdr.Write(writer); 55 | _entries = new FileEntry[_hdr.FileCount]; 56 | writer.BaseStream.Position = _hdr.DataStartOffset; 57 | for(int i = 0; i < _hdr.FileCount; i++) 58 | { 59 | Console.WriteLine("Writing: {0}", files[i]); 60 | byte[] data = File.ReadAllBytes(files[i]); 61 | FileEntry fileEntry = new FileEntry(); 62 | fileEntry.FileName = Path.GetFileName(files[i]).Substring(5); // trim index 63 | fileEntry.Size = (uint)data.Length; 64 | fileEntry.Offset = (uint)(writer.BaseStream.Position - _hdr.DataStartOffset); 65 | _entries[i] = fileEntry; 66 | writer.Write(AlignData(data)); 67 | } 68 | writer.BaseStream.Position = _hdr.HEADER_SIZE; 69 | foreach(var entry in _entries) 70 | entry.Write(writer); 71 | } 72 | } 73 | 74 | static void Extract(string folderPath) 75 | { 76 | string[] filePaths = Directory.GetFiles(folderPath); 77 | foreach (string file in filePaths) 78 | { 79 | string fileExtension = Path.GetExtension(file); 80 | 81 | if (fileExtension.Equals(".hfa", StringComparison.OrdinalIgnoreCase)) 82 | { 83 | Console.WriteLine("Unpack: " + Path.GetFileName(file)); 84 | 85 | using (BinaryReader reader = new BinaryReader(File.OpenRead(file))) 86 | { 87 | _hdr = new Header(reader); 88 | _entries = new FileEntry[_hdr.FileCount]; 89 | for (int i = 0; i < _hdr.FileCount; i++) 90 | _entries[i] = new FileEntry(reader); 91 | string outDir = Path.GetFileNameWithoutExtension(file); 92 | int index = 0; // There are files with the same names in the archive. And I want to keep the original sorting. 93 | foreach (var entry in _entries) 94 | { 95 | reader.BaseStream.Position = _hdr.DataStartOffset + entry.Offset; 96 | byte[] data = reader.ReadBytes((int)entry.Size); 97 | string path = outDir + Path.DirectorySeparatorChar + $"{index++.ToString("D4")}_" + entry.FileName; 98 | Console.WriteLine("Extracting: {0}", path); 99 | if (!Directory.Exists(Path.GetDirectoryName(path))) Directory.CreateDirectory(Path.GetDirectoryName(path)); 100 | File.WriteAllBytes(path, data); 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | static byte[] AlignData(byte[] data, int align = 0x8) 108 | { 109 | // Check if data must be aligned. 110 | if(data.Length % align != 0) 111 | { 112 | int len = align - (data.Length % align); 113 | byte[] buf = new byte[data.Length + len]; 114 | Buffer.BlockCopy(data, 0, buf, 0, data.Length); 115 | for (int i = data.Length; i < len; i++) 116 | buf[i] = 0xFF; 117 | } 118 | return data; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /HunexFileArchiveTool/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Общие сведения об этой сборке предоставляются следующим набором 6 | // набора атрибутов. Измените значения этих атрибутов для изменения сведений, 7 | // связанные с этой сборкой. 8 | [assembly: AssemblyTitle("HunexFileArchiveTool")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HunexFileArchiveTool")] 13 | [assembly: AssemblyCopyright("Copyright © 2023")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми 18 | // для компонентов COM. Если необходимо обратиться к типу в этой сборке через 19 | // из модели COM задайте для атрибута ComVisible этого типа значение true. 20 | [assembly: ComVisible(false)] 21 | 22 | // Следующий GUID представляет идентификатор typelib, если этот проект доступен из модели COM 23 | [assembly: Guid("20320464-5379-4591-bdd6-4a959ec9503d")] 24 | 25 | // Сведения о версии сборки состоят из указанных ниже четырех значений: 26 | // 27 | // Основной номер версии 28 | // Дополнительный номер версии 29 | // Номер сборки 30 | // Номер редакции 31 | // 32 | // Можно задать все значения или принять номера сборки и редакции по умолчанию 33 | // используя "*", как показано ниже: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | --------------------------------------------------------------------------------