From 9a98c5436a76addb86a24b423dfe3986f4841a38 Mon Sep 17 00:00:00 2001
From: LEI_Admin <31388928+MOO2Extractor@users.noreply.github.com>
Date: Sun, 27 Aug 2017 12:33:54 -0400
Subject: [PATCH] Initial Commit
Initial commit of LBX Extractor command line tool. This extracts individual files out of an LBX archive.
Known issue: filenames in these archives are so inconsistent, they've been commented out. Right now it just uses indices as file names.
---
App.config | 6 ++
LbxDecoder.csproj | 66 ++++++++++++
Program.cs | 201 +++++++++++++++++++++++++++++++++++++
Properties/AssemblyInfo.cs | 36 +++++++
4 files changed, 309 insertions(+)
create mode 100644 App.config
create mode 100644 LbxDecoder.csproj
create mode 100644 Program.cs
create mode 100644 Properties/AssemblyInfo.cs
diff --git a/App.config b/App.config
new file mode 100644
index 0000000..88fa402
--- /dev/null
+++ b/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LbxDecoder.csproj b/LbxDecoder.csproj
new file mode 100644
index 0000000..9343e79
--- /dev/null
+++ b/LbxDecoder.csproj
@@ -0,0 +1,66 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {5945E684-0F88-4F35-A6D0-9D8C79370150}
+ Exe
+ Properties
+ LbxDecoder
+ LbxExtractor
+ v4.5.2
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XCOPY "$(TargetDir)$(TargetName).exe" "K:\SteamLibrary\steamapps\common\Master of Orion 2\FV Tools" /S /E /V /Y
+
+
+
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..1d34e60
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LbxDecoder
+{
+ public class Program
+ {
+ private const bool ReverseEndianness = false;
+
+ public static void Main(string[] args)
+ {
+ Console.WriteLine("MOO2 LBX Extractor v1.0");
+ Console.WriteLine("Extracts an LBX archive into individual files.");
+ Console.WriteLine("Author: Louis Ingenthron");
+ Console.WriteLine("Last modified: 8/27/2017");
+ Console.WriteLine();
+
+ if (args.Length <= 0)
+ {
+ Console.WriteLine("Usage: LbxExtractor.exe \"Filename.lbx\"");
+ Console.WriteLine("Or just drag and drop an LBX file on to LbxExtractor.exe");
+ Console.WriteLine("Note: Additional files can be provided as additional arguments");
+ Console.WriteLine();
+ }
+ else
+ {
+ ReadAllFiles(args);
+ }
+
+ Console.WriteLine("Press any key to continue...");
+ Console.ReadKey();
+ }
+
+ public static void ReadAllFiles(String[] filenames)
+ {
+ foreach (String filename in filenames)
+ {
+ if (File.Exists(filename))
+ {
+ ReadFile(filename);
+ }
+ else
+ {
+ Console.WriteLine("CANNOT FIND FILE "+filename);
+ }
+ }
+ Console.WriteLine();
+ }
+
+ public static void ReadAllFiles(String LBXDirectory)
+ {
+ ReadAllFiles(Directory.GetFiles(LBXDirectory, "*.lbx", SearchOption.TopDirectoryOnly).ToArray());
+ }
+
+ private static void ReadFile(String LBXFileName)
+ {
+ char[] InvalidFilenameChars = Path.GetInvalidFileNameChars();
+ uint FileSize = (uint)new FileInfo(LBXFileName).Length;
+ ushort FileCount = 0;
+ using (BinaryReader br = new BinaryReader(File.OpenRead(LBXFileName), ASCIIEncoding.ASCII))
+ {
+ // Verify the format's magic word.
+ FileCount = br.ReadUInt16();
+ byte[] MagicWord = br.ReadBytes(4);
+ ushort Info = br.ReadUInt16();
+
+ if(FileCount < 1)
+ {
+ Console.WriteLine(Path.GetFileName(LBXFileName) + " contains no files.");
+ return;
+ }
+ if(MagicWord[0] != (byte)173 || MagicWord[1] != (byte)254 || MagicWord[2] != (byte)0 || MagicWord[3] != (byte)0)
+ {
+ Console.WriteLine(Path.GetFileName(LBXFileName) + " is not a SimTex LBX file.");
+ return;
+ }
+ Console.WriteLine(Path.GetFileName(LBXFileName) + " is a valid file with " + FileCount + " records");
+
+ // Header Structure
+ uint[] FileOffsets = new uint[FileCount];
+ String[] FileNames = new String[FileCount];
+ String[] FileDescriptions = new String[FileCount];
+
+ // Load the offsets.
+ for(int i=0;i FileOffsets[i])
+ {
+ // 512 is the start of the names/desc, 32 is their combined length.
+ EndOfNames = true;
+ }
+
+ if (!EndOfNames)
+ {
+ // There are more names to read.
+ FileNames[i] = new String(br.ReadChars(8));
+
+ br.ReadChar(); // Throw away the null-terminator.
+
+ FileDescriptions[i] = new String(br.ReadChars(22));
+
+ br.ReadChar(); // Throw away the null-terminator.
+ }
+ else
+ {
+ // We've reached the end of the names section, but there may be additional files, so give each a blank name.
+ FileNames[i] = "Unnamed ";
+ FileDescriptions[i] = "";
+ }
+ }
+
+ // Extract the files.
+ Console.WriteLine("Extracting from " + Path.GetFileName(LBXFileName) + "...");
+
+ String LBXFolder = Path.Combine(Path.GetDirectoryName(LBXFileName), Path.GetFileNameWithoutExtension(LBXFileName));
+ if (!Directory.Exists(LBXFolder))
+ Directory.CreateDirectory(LBXFolder);
+
+
+ for (int i = 0; i < FileCount - 1; i++)
+ {
+ // Determine the length of the current file by subtracting the start of the file from the next file.
+ uint FileLength = FileOffsets[i + 1] - FileOffsets[i];
+ br.BaseStream.Position = FileOffsets[i];
+
+ // Get the base name of the file (LBX files often reuse names in the same path).
+ String BaseName = FileNames[i].Trim();
+ for (int k = 0; k < BaseName.Length; k++)
+ {
+ if (InvalidFilenameChars.Contains(BaseName[k]))
+ {
+ BaseName = "";
+ break;
+ }
+ }
+ //if (String.IsNullOrEmpty(BaseName))
+ BaseName = i.ToString();
+
+ String descr = FileDescriptions[i];
+ for (int k = 0; k < descr.Length; k++)
+ {
+ if (InvalidFilenameChars.Contains(descr[k]))
+ {
+ descr = "Unknown";
+ break;
+ }
+ }
+ Console.WriteLine(" " + LBXFolder + "/" + BaseName + " - " + descr + " - " + FileLength + " bytes");
+
+
+ // Read the file data from the LBX.
+ byte[] FileContents = br.ReadBytes((int)FileLength);
+
+ // Check for reusing the same file name.
+ String OutputFileName;
+ int Addition = -1;
+ do
+ {
+ Addition = Addition + 1;
+ OutputFileName = LBXFolder + "/" + BaseName + ((Addition > 0) ? "-" + Addition : "");
+ }
+ while (File.Exists(OutputFileName));
+
+ // Output the contents of the file
+ File.WriteAllBytes(OutputFileName, FileContents);
+ }
+ }
+ Console.WriteLine(FileCount + " file(s) extracted.");
+ }
+
+ public static UInt16 ReverseBytes(UInt16 value)
+ {
+ return (UInt16)((value & 0xFFU) << 8 | (value & 0xFF00U) >> 8);
+ }
+
+ public static UInt32 ReverseBytes(UInt32 value)
+ {
+ return (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 |
+ (value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
+ }
+ }
+}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..843f0c3
--- /dev/null
+++ b/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("LbxDecoder")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("LbxDecoder")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[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("5945e684-0f88-4f35-a6d0-9d8c79370150")]
+
+// 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")]