From 3706e91def262caf12679e6e96ddc2a2b686d579 Mon Sep 17 00:00:00 2001 From: FaithBeam <32502411+FaithBeam@users.noreply.github.com> Date: Sun, 8 Nov 2020 08:37:35 -0500 Subject: [PATCH] 1.0.0 Release. --- README.md | 40 ++++++- Sims.Far.sln | 2 +- Sims.Far/Far.cs | 211 +++++++++++++++++---------------- Sims.Far/Sims.Far.csproj | 6 +- Sims.Far/Sims.Far.xml | 32 +++++ Sims.Far/Sims.Far/Sims.Far.xml | 32 +++++ 6 files changed, 216 insertions(+), 107 deletions(-) create mode 100644 Sims.Far/Sims.Far.xml create mode 100644 Sims.Far/Sims.Far/Sims.Far.xml diff --git a/README.md b/README.md index e57b753..23ec34e 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ -**This is currently considered pre-release** - # Sims.Far A library to manipulate The Sims 1 .far files. -Currently only extracts .bmps from UIGraphics.far, and has only been used on The Sims 1 Complete Collection UIGraphics.far with CRC32: 8E03701F. - ## Installation You can install Sims.Far as a nupkg from nuget.org. ## Usage +Extracting UIGraphics.far to a relative UIGraphics folder: ```cs using Sims.Far; @@ -20,3 +17,38 @@ void main() far.Extract(@"UIGraphics\"); } ``` + +Extracting UIGraphics.far: +```cs +using Sims.Far; + +void main() +{ + var far = new Far(@"C:\Program Files (x86)\Maxis\The Sims\UIGraphics\UIGraphics.far"); + far.Extract(); +} +``` + +Extracting UIGraphics.far with an inclusive filter: +```cs +using Sims.Far; + +void main() +{ + var myFiles = new List { "Res_CPanel.h", @"Community\Bus_loadscreen_800x600.bmp" }; + var far = new Far(myFiles); + far.Extract(); +} +``` + +Extracting UIGraphics.far with an inclusive filter to a specified directory: +```cs +using Sims.Far; + +void main() +{ + var myFiles = new List { "Res_CPanel.h", @"Community\Bus_loadscreen_800x600.bmp" }; + var far = new Far("UIGraphics", myFiles); + far.Extract(); +} +``` diff --git a/Sims.Far.sln b/Sims.Far.sln index d034fa4..424b057 100644 --- a/Sims.Far.sln +++ b/Sims.Far.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30406.217 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sims.Far", "Sims.Far\Sims.Far.csproj", "{198AE96E-4AE9-4CA1-8803-D1607675D5B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sims.Far", "Sims.Far\Sims.Far.csproj", "{198AE96E-4AE9-4CA1-8803-D1607675D5B9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Sims.Far/Far.cs b/Sims.Far/Far.cs index de7a0c0..62dfda0 100644 --- a/Sims.Far/Far.cs +++ b/Sims.Far/Far.cs @@ -8,134 +8,143 @@ namespace Sims.Far { public class Far { - public readonly string far; - public List bmps = new List(); - public List paths = new List(); - public byte[] bytes; + private readonly string pathToFar; + public string Signature { get; set; } + public int Version { get; set; } + public int ManifestOffset { get; set; } + public List Files { get; set; } + public Manifest Manifest { get; set; } - public Far(string far) + public Far(string pathToFar) { - this.far = far; + this.pathToFar = pathToFar; ParseFar(); } private void ParseFar() { - bytes = File.ReadAllBytes(this.far); - int curByte = bytes[0]; - int nextByte = bytes[1]; - int sizeOfBITMAPINFOHEADER; - int sizeOfBmpBytes; - int endBmp = 0; - int i = 0; - for (; i < bytes.Length - 2; i++) + byte[] bytes = File.ReadAllBytes(this.pathToFar); + this.Signature = Encoding.UTF8.GetString(bytes, 0, 8); + this.Version = BitConverter.ToInt32(bytes, 8); + this.ManifestOffset = BitConverter.ToInt32(bytes, 12); + this.Files = new List(); + this.Manifest = new Manifest { - // Start of BMP header - if (curByte == 66 && nextByte == 77) - { - sizeOfBITMAPINFOHEADER = bytes[i - 1 + 14]; - // Header validation. BITMAPINFOHEADER will always be 40 so if it isn't, these bytes are not the start of a bmp. - if (sizeOfBITMAPINFOHEADER == 40) - { - sizeOfBmpBytes = BitConverter.ToInt32(new byte[] { bytes[i + 1], bytes[i + 2], bytes[i + 3], bytes[i + 4] }, 0); - endBmp = i - 1 + sizeOfBmpBytes; - this.bmps.Add(new Bmp { StartOffset = i - 1, EndOffset = endBmp }); - i = endBmp; - } - } - curByte = bytes[i]; - nextByte = bytes[i + 1]; - } - i = endBmp; - - // Bytes that represent acceptable ascii characters to create paths - var asciiChars = new List { 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 92, 95, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 }; - // Navigate to file names - while (!asciiChars.Contains(bytes[i]) && i < bytes.Length) { i++; } - - // Extensions that are found in the .far file. Currently only works with UIGraphics.far. - var extensions = new List { ".h", ".rt", ".bmp", ".tga", ".cur" }; - var sb = new StringBuilder(); - // Start at the end of all bmps - for (; i < bytes.Length; i++) + NumberOfFiles = BitConverter.ToInt32(bytes, this.ManifestOffset), + ManifestEntries = new List() + }; + + int curOffset = this.ManifestOffset; + for (int i = 0; i < this.Manifest.NumberOfFiles; i++) { - curByte = bytes[i]; - if (asciiChars.Contains(curByte)) - { - sb.Append(Convert.ToChar(curByte)); - } - else - { - // Skip strings that couldn't possible be a file - if (sb.Length > 5) - { - string path = ""; - var tmpPath = sb.ToString(); - // tmppath may still have dangling characters following the extension: .bmp8, .bmpR, etc. This replaces the tmppath's extension entirely. - foreach (var ext in extensions) - { - if (tmpPath.IndexOf(ext, StringComparison.OrdinalIgnoreCase) > 0) - { - path = tmpPath.Substring(0, tmpPath.IndexOf(".")) + ext; - break; - } - } - if (!string.IsNullOrWhiteSpace(path) && (path.IndexOf(".bmp", StringComparison.OrdinalIgnoreCase) > 0)) - paths.Add(path); - } - sb.Clear(); - } + var manifestEntry = ParseManifestEntry(bytes, curOffset + 4); + this.Manifest.ManifestEntries.Add(manifestEntry); + curOffset += 16 + manifestEntry.FilenameLength; + + var curFile = new byte[manifestEntry.FileLength1]; + Array.Copy(bytes, manifestEntry.FileOffset, curFile, 0, manifestEntry.FileLength1); + this.Files.Add(curFile); } - // This feels bad - paths.Add(sb.ToString()); - sb.Clear(); } + private static ManifestEntry ParseManifestEntry(byte[] bytes, int offset) + { + var manifestEntry = new ManifestEntry + { + FileLength1 = BitConverter.ToInt32(bytes, offset), + FileLength2 = BitConverter.ToInt32(bytes, offset += 4), + FileOffset = BitConverter.ToInt32(bytes, offset += 4), + FilenameLength = BitConverter.ToInt32(bytes, offset += 4), + }; + manifestEntry.Filename = Encoding.UTF8.GetString(bytes, offset += 4, manifestEntry.FilenameLength); + return manifestEntry; + } + + /// + /// Extract all files from the far file. + /// public void Extract() { - for (int j = 0; j < this.paths.Count; j++) + for (int i = 0; i < this.Manifest.NumberOfFiles; i++) + { + string dir = Path.GetDirectoryName(this.Manifest.ManifestEntries[i].Filename); + if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + File.WriteAllBytes(this.Manifest.ManifestEntries[i].Filename, this.Files[i]); + } + } + + /// + /// Extract files from the far file with an inclusive filter. Only files in this enumerable will be extracted. + /// + /// Inclusive filter. + public void Extract(IEnumerable filter) + { + for (int i = 0; i < this.Manifest.NumberOfFiles; i++) { - this.bmps[j].Path = this.paths[j]; - int size = this.bmps[j].EndOffset - this.bmps[j].StartOffset; - var curBmp = new byte[size]; - if (this.paths[j].Contains(@"\")) - Directory.CreateDirectory(@".\" + this.paths[j].Substring(0, this.paths[j].LastIndexOf(@"\"))); - Array.Copy(this.bytes, this.bmps[j].StartOffset, curBmp, 0, size); - File.WriteAllBytes(@".\" + this.bmps[j].Path, curBmp); + if (!filter.Contains(this.Manifest.ManifestEntries[i].Filename)) + continue; + + string dir = Path.GetDirectoryName(this.Manifest.ManifestEntries[i].Filename); + if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + File.WriteAllBytes(this.Manifest.ManifestEntries[i].Filename, this.Files[i]); } } + /// + /// Extract all files from the far file to a specified directory. + /// + /// The directory to extract files to. public void Extract(string outputDirectory) { - if (!outputDirectory.EndsWith(@"\")) outputDirectory += @"\"; - for (int j = 0; j < this.paths.Count; j++) + if (!outputDirectory.EndsWith(@"\")) + outputDirectory += @"\"; + + for (int i = 0; i < this.Manifest.NumberOfFiles; i++) { - this.bmps[j].Path = this.paths[j]; - int size = this.bmps[j].EndOffset - this.bmps[j].StartOffset; - var curBmp = new byte[size]; - if (this.paths[j].Contains(@"\")) - Directory.CreateDirectory(outputDirectory + this.paths[j].Substring(0, this.paths[j].LastIndexOf(@"\"))); - Array.Copy(this.bytes, this.bmps[j].StartOffset, curBmp, 0, size); - File.WriteAllBytes(outputDirectory + this.bmps[j].Path, curBmp); + string dir = Path.GetDirectoryName(this.Manifest.ManifestEntries[i].Filename); + if (!string.IsNullOrWhiteSpace(outputDirectory + dir) && !Directory.Exists(outputDirectory + dir)) + Directory.CreateDirectory(outputDirectory + dir); + File.WriteAllBytes(outputDirectory + this.Manifest.ManifestEntries[i].Filename, this.Files[i]); } } - public void Extract(string outputDirectory, IEnumerable filesToExtract) + /// + /// Extract files from the far file with an inclusive filter to the specified directory. + /// + /// The directory to extract files to. + /// Inclusive filter. + public void Extract(string outputDirectory, IEnumerable filter) { - if (!outputDirectory.EndsWith(@"\")) outputDirectory += @"\"; - for (int j = 0; j < this.paths.Count; j++) + if (!outputDirectory.EndsWith(@"\")) + outputDirectory += @"\"; + + for (int i = 0; i < this.Manifest.NumberOfFiles; i++) { - if (filesToExtract.Contains(this.paths[j], StringComparer.OrdinalIgnoreCase)) - { - this.bmps[j].Path = this.paths[j]; - int size = this.bmps[j].EndOffset - this.bmps[j].StartOffset; - var curBmp = new byte[size]; - if (this.paths[j].Contains(@"\")) - Directory.CreateDirectory(outputDirectory + this.paths[j].Substring(0, this.paths[j].LastIndexOf(@"\"))); - Array.Copy(this.bytes, this.bmps[j].StartOffset, curBmp, 0, size); - File.WriteAllBytes(outputDirectory + this.bmps[j].Path, curBmp); - } + if (!filter.Contains(this.Manifest.ManifestEntries[i].Filename)) + continue; + + string dir = Path.GetDirectoryName(this.Manifest.ManifestEntries[i].Filename); + if (!string.IsNullOrWhiteSpace(outputDirectory + dir) && !Directory.Exists(outputDirectory + dir)) + Directory.CreateDirectory(outputDirectory + dir); + File.WriteAllBytes(outputDirectory + this.Manifest.ManifestEntries[i].Filename, this.Files[i]); } } } + + public class Manifest + { + public int NumberOfFiles { get; set; } + public List ManifestEntries { get; set; } + } + + public class ManifestEntry + { + public int FileLength1 { get; set; } + public int FileLength2 { get; set; } + public int FileOffset { get; set; } + public int FilenameLength { get; set; } + public string Filename { get; set; } + } } diff --git a/Sims.Far/Sims.Far.csproj b/Sims.Far/Sims.Far.csproj index 6a4c4c6..56dd8a9 100644 --- a/Sims.Far/Sims.Far.csproj +++ b/Sims.Far/Sims.Far.csproj @@ -2,7 +2,7 @@ netcoreapp2.1;net40;netstandard2.0 - 0.2.0 + 1.0.0 FaithBeam true https://github.com/FaithBeam/Sims.Far @@ -10,6 +10,10 @@ LICENSE.txt + + Sims.Far\Sims.Far.xml + + True diff --git a/Sims.Far/Sims.Far.xml b/Sims.Far/Sims.Far.xml new file mode 100644 index 0000000..7640126 --- /dev/null +++ b/Sims.Far/Sims.Far.xml @@ -0,0 +1,32 @@ + + + + Sims.Far + + + + + Extract all files from the far file. + + + + + Extract files from the far file with an inclusive filter. Only files in this enumerable will be extracted. + + Inclusive filter. + + + + Extract all files from the far file to a specified directory. + + The directory to extract files to. + + + + Extract files from the far file with an inclusive filter to the specified directory. + + The directory to extract files to. + Inclusive filter. + + + diff --git a/Sims.Far/Sims.Far/Sims.Far.xml b/Sims.Far/Sims.Far/Sims.Far.xml new file mode 100644 index 0000000..7640126 --- /dev/null +++ b/Sims.Far/Sims.Far/Sims.Far.xml @@ -0,0 +1,32 @@ + + + + Sims.Far + + + + + Extract all files from the far file. + + + + + Extract files from the far file with an inclusive filter. Only files in this enumerable will be extracted. + + Inclusive filter. + + + + Extract all files from the far file to a specified directory. + + The directory to extract files to. + + + + Extract files from the far file with an inclusive filter to the specified directory. + + The directory to extract files to. + Inclusive filter. + + +