├── 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 |
--------------------------------------------------------------------------------