diff --git a/README.md b/README.md
index 0d7da37..7728200 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,10 @@ This is the main library of the solution. Contains classes for IO and file form
This tool allows you to edit the audio content of an ACB file.
A more advanced version like CSB Builder is planned to be made soon.
+## [ACB Finder](https://github.com/blueskythlikesclouds/SonicAudioTools/tree/master/Source/AcbFinder)
+This tool allows you to find AWB files and link them back to the ACB, required in extracting certain ACB files.
+Useful for games where the AWB files may be renamed or hidden (like Phantasy Star Online 2)
+
## [ACB Injector](https://github.com/blueskythlikesclouds/SonicAudioTools/tree/master/Source/AcbInjector)
This tool allows you to inject audio file directly into ACB without repacking its AWB.
Useful for background music ACBs that use huge AWB files.
diff --git a/SonicAudioTools.sln b/SonicAudioTools.sln
index 70e88d2..0bac445 100644
--- a/SonicAudioTools.sln
+++ b/SonicAudioTools.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26430.16
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29806.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonicAudioLib", "Source\SonicAudioLib\SonicAudioLib.csproj", "{63138773-1F47-474C-9345-15EB6183ECC6}"
EndProject
@@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsbBuilder", "Source\CsbBui
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AcbInjector", "Source\AcbInjector\AcbInjector.csproj", "{4F68FD56-37A6-40D5-8C57-19067F7C2141}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AcbFinder", "Source\AcbFinder\AcbFinder.csproj", "{2F442A2E-B999-4492-B487-FA941A0E44F1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -44,8 +46,15 @@ Global
{4F68FD56-37A6-40D5-8C57-19067F7C2141}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F68FD56-37A6-40D5-8C57-19067F7C2141}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F68FD56-37A6-40D5-8C57-19067F7C2141}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F442A2E-B999-4492-B487-FA941A0E44F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F442A2E-B999-4492-B487-FA941A0E44F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F442A2E-B999-4492-B487-FA941A0E44F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F442A2E-B999-4492-B487-FA941A0E44F1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0E6DA77F-A8E6-4FB2-88F6-EBCA8F977464}
+ EndGlobalSection
EndGlobal
diff --git a/Source/AcbFinder/AcbFinder.csproj b/Source/AcbFinder/AcbFinder.csproj
new file mode 100644
index 0000000..c68b163
--- /dev/null
+++ b/Source/AcbFinder/AcbFinder.csproj
@@ -0,0 +1,59 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {2F442A2E-B999-4492-B487-FA941A0E44F1}
+ Exe
+ ACBFinder
+ ACBFinder
+ v4.7.2
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {63138773-1f47-474c-9345-15eb6183ecc6}
+ SonicAudioLib
+
+
+
+
\ No newline at end of file
diff --git a/Source/AcbFinder/App.config b/Source/AcbFinder/App.config
new file mode 100644
index 0000000..26f16c9
--- /dev/null
+++ b/Source/AcbFinder/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Source/AcbFinder/Program.cs b/Source/AcbFinder/Program.cs
new file mode 100644
index 0000000..88b0a54
--- /dev/null
+++ b/Source/AcbFinder/Program.cs
@@ -0,0 +1,145 @@
+using SonicAudioLib.CriMw;
+using SonicAudioLib.IO;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace AcbFinder
+{
+ internal static class Program
+ {
+ public static void Main(string[] args)
+ {
+ var awbsByHeaderHash = new Dictionary();
+ var acbsByAwbHeaderHash = new Dictionary();
+ var lines = new List();
+
+ string DirectoryForWork = "";
+ string outputDirectory = "";
+
+ if (args.Count() < 1)
+ {
+ DirectoryForWork = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
+ outputDirectory = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "output");
+ }
+ else
+ {
+ DirectoryForWork = args[0];
+ outputDirectory = Path.Combine(args[0], "output");
+ }
+
+ StreamWriter LogFile = new StreamWriter(Path.Combine(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "ACB_FinderLog.txt")));
+ Directory.CreateDirectory(outputDirectory);
+
+ var md5 = MD5.Create();
+
+ var buffer = new byte[4];
+ var di = Directory.GetFiles(DirectoryForWork, "*", SearchOption.AllDirectories);
+
+ Console.WriteLine("=== ACB Finder (" + DateTime.Now + ") ===");
+ LogFile.WriteLine("=== ACB Finder (" + DateTime.Now + ") ===");
+
+ Console.WriteLine("Found " + di.Length + " files in " + DirectoryForWork + "...");
+ LogFile.WriteLine("Found " + di.Length + " files in " + DirectoryForWork + "...");
+ int AWBsFound = 0;
+ foreach (var filePath in di)
+ {
+ using (var stream = File.OpenRead(filePath))
+ {
+ stream.Read(buffer, 0, 4);
+
+ var signature = Encoding.ASCII.GetString(buffer);
+ if (signature == "AFS2")
+ {
+ Console.WriteLine("Found an AWB file: " + filePath.Replace(DirectoryForWork, "").Replace("\\", ""));
+ LogFile.WriteLine("Found an AWB file: " + filePath.Replace(DirectoryForWork, "").Replace("\\", ""));
+ AWBsFound++;
+
+ stream.Read(buffer, 0, 4);
+ int entryCount = DataStream.ReadInt32(stream);
+
+ stream.Seek(4 + (entryCount * buffer[2]), SeekOrigin.Current);
+ int length = (buffer[1] == 2 ? DataStream.ReadInt16(stream) : DataStream.ReadInt32(stream)) + 2;
+
+ stream.Seek(0, SeekOrigin.Begin);
+ var header = new byte[length];
+ stream.Read(header, 0, length);
+
+ string hash = Convert.ToBase64String(md5.ComputeHash(header));
+ awbsByHeaderHash[hash] = filePath;
+ }
+ else if (signature == "@UTF")
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+
+ try
+ {
+ using (var reader = CriTableReader.Create(stream))
+ {
+ reader.Read();
+
+ string name = (((FileStream)reader.SourceStream).Name).Replace(DirectoryForWork, "").Replace("\\",""); //Properly get the filename using the stream and remove the directory from it
+ name = name.Remove(name.Length - 4, 4); //Remove the extension too, just in case?
+ File.Copy(filePath, Path.Combine(outputDirectory, name + ".acb"), true);
+
+ Console.WriteLine("Found and copied ACB: {0}", name);
+ LogFile.WriteLine("Found and copied ACB: {0}", name);
+
+ lines.Add($"{Path.GetFileName(filePath)}={name}.acb");
+
+ if (reader.GetLength("StreamAwbAfs2Header") != 0)
+ {
+ //using ( var reader2 = reader.GetTableReader( "StreamAwbAfs2Header" ) )
+ //{
+ // reader2.Read();
+
+ var header = reader.GetData("StreamAwbAfs2Header");
+ var hash = Convert.ToBase64String(md5.ComputeHash(header));
+
+ acbsByAwbHeaderHash[hash] = name;
+ //}
+ }
+ }
+ }
+ catch
+ {
+ Console.WriteLine("File could not be read correctly as an ACB: " + filePath.Replace(DirectoryForWork, "").Replace("\\", ""));
+ LogFile.WriteLine("File could not be read correctly as an ACB: " + filePath.Replace(DirectoryForWork, "").Replace("\\", ""));
+ continue;
+ }
+ }
+ }
+ }
+
+ foreach (var pair in awbsByHeaderHash)
+ {
+ if (!acbsByAwbHeaderHash.TryGetValue(pair.Key, out string name))
+ {
+ Console.WriteLine("AWB (" + pair.Key + ") with no ACB found.");
+ LogFile.WriteLine("AWB (" + pair.Key + ") with no ACB found.");
+ continue;
+ }
+
+ lines.Add($"{Path.GetFileName(pair.Value)}={name}.awb");
+
+ File.Copy(pair.Value, Path.Combine(outputDirectory, name + ".awb"), true);
+ Console.WriteLine("Copied awb {0}", name);
+ LogFile.WriteLine("Copied awb {0}", name);
+ }
+
+ foreach (var pair in acbsByAwbHeaderHash.Where(x => !awbsByHeaderHash.ContainsKey(x.Key)))
+ {
+ File.AppendAllText(Path.Combine(outputDirectory, "missing_awb.txt"), string.Format("{0}\n", pair.Value));
+ }
+ File.WriteAllLines(Path.Combine(outputDirectory, "file_list.txt"), lines);
+
+ Console.WriteLine("Process completed - Found " + AWBsFound + " AWB file(s). Press any key to exit.");
+ LogFile.WriteLine("== Process completed (" + DateTime.Now + ") - Found " + AWBsFound + " AWB file(s). Press any key to exit. ===");
+ LogFile.Close();
+ Console.ReadKey();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/AcbFinder/Properties/AssemblyInfo.cs b/Source/AcbFinder/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..441a154
--- /dev/null
+++ b/Source/AcbFinder/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ACBFinder")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ACBFinder")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2f442a2e-b999-4492-b487-fa941a0e44f1")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]