diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..114a799 --- /dev/null +++ b/.gitignore @@ -0,0 +1,357 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/ArchiveInterop/ArchiveInterop.csproj b/ArchiveInterop/ArchiveInterop.csproj new file mode 100644 index 0000000..aa8a0bb --- /dev/null +++ b/ArchiveInterop/ArchiveInterop.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {FDE41CE6-91FB-4B01-A63B-16340BDD5ABF} + Library + Properties + ArchiveInterop + ArchiveInterop + v4.8 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + {029695cd-967e-4f9e-bd72-85a087571d38} + NormalMapConverter + + + + + + + + \ No newline at end of file diff --git a/ArchiveInterop/Asset.cs b/ArchiveInterop/Asset.cs new file mode 100644 index 0000000..fe3e0be --- /dev/null +++ b/ArchiveInterop/Asset.cs @@ -0,0 +1,56 @@ +using System.IO; + +namespace ArchiveInterop +{ + /// + /// Contains metadata that aids with the BSA packing process. + /// + public class Asset + { + public string EntryStr { get; set; } + public string RealPath { get; set; } + public bool IsDDS { get; set; } + public bool IsExtended { get; set; } + + // These fields get set during packing + public uint Size { get; set; } + public uint OriginalSize { get; set; } + public uint Offset { get; set; } + + public string FileName => Path.GetFileName(EntryStr); + public string FileNameNoExtension => Path.ChangeExtension(FileName, null); + public string Extension => Path.GetExtension(FileName); + public ulong Hash => this.IsDDS ? OblivionBSAHash.GetPS3(FileName) : OblivionBSAHash.GetPC(FileNameNoExtension, Extension); + public bool IsNormalMap => this.IsDDS && FileNameNoExtension.EndsWith("_n"); + + /// + /// Creates asset class with entry string and real filesystem path fields set. + /// + /// Path to be written in BSA. + /// Real filesystem path to find asset. + public Asset(string entryStr, string realPath) + { + this.EntryStr = entryStr; + this.RealPath = realPath; + + // Checks DDS-related things about the asset + if (this.Extension.Equals(".dds")) + { + this.IsDDS = true; + + using (var reader = new BinaryReader(File.Open(this.RealPath, FileMode.Open, FileAccess.Read, FileShare.Read))) + { + int entryStrLen = entryStr.Length; + + // Goes to where the extended entry string data start may be + reader.BaseStream.Position = reader.BaseStream.Length - entryStrLen - 1; + + if (reader.ReadByte() == entryStrLen && new string(reader.ReadChars(entryStrLen)).Equals(entryStr)) + { + this.IsExtended = true; + } + } + } + } + } +} \ No newline at end of file diff --git a/ArchiveInterop/BSA.cs b/ArchiveInterop/BSA.cs new file mode 100644 index 0000000..e821014 --- /dev/null +++ b/ArchiveInterop/BSA.cs @@ -0,0 +1,283 @@ +using ArchiveInterop.Classes; +using NormalMapConverter.DirectDrawSurfaceUtilities; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Zlib; + +namespace ArchiveInterop +{ + public static class BSA + { + /// + /// Writes BSA for Oblivion PS3. + /// + /// Real filesystem path to write BSA to. + /// List of to write in BSA. + /// True or false if assets in BSA should be compressed. + /// True or false if PS3 file flags should be used. + /// True or false if DDS texture file data should be extended. + /// True or false if normal maps should be converted to PS3. + public static void Write(string path, List assetList, bool compress, bool usePS3FileFlags, bool extendDDS, bool convertNormalMaps) + { + var folderDict = new Dictionary>(); + var extList = new List(); + uint totalFolderNameLength = 0; + uint totalFileNameLength = 0; + + // Sets up all assets to be in folderDict + foreach (Asset asset in assetList) + { + string folderName = Path.GetDirectoryName(asset.EntryStr); + + if (!folderDict.ContainsKey(folderName)) + { + folderDict.Add(folderName, new List()); + totalFolderNameLength += (uint)folderName.Length + 1; // Includes null terminator, not prefixed length sbyte + } + + totalFileNameLength += (uint)asset.FileName.Length + 1; // Includes null terminator + folderDict[folderName].Add(asset); + if (!extList.Contains(asset.Extension)) extList.Add(asset.Extension); + } + + // Begins writing of archive + using (var writer = new BinaryWriter(File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read))) + { + writer.Write(0x00415342); // "BSA" + 0x00, archive magic + writer.Write(103); // Version + writer.Write(36); // Folder records offset + writer.Write(compress ? 0x00000707 : 0x00000703); + writer.Write(folderDict.Count()); + writer.Write(assetList.Count()); + writer.Write(totalFolderNameLength); + writer.Write(totalFileNameLength); + writer.Write(BSA.GetFileFlags(extList, usePS3FileFlags)); // Texture archive file flags + + var folderOrdList = new List(); // Keeps order of folders, with folder names + var folderOffList = new List(); + + // Loops through the folder dictionary, while also organizing folders to follow Oblivion BSA rules + foreach (KeyValuePair> folder in folderDict.OrderBy(folder => OblivionBSAHash.GetPC(folder.Key))) + { + folderOrdList.Add(folder.Key); + folder.Value.Sort((a, b) => a.Hash.CompareTo(b.Hash)); // Assets in folder sorted to follow Oblivion BSA rules + + writer.Write(OblivionBSAHash.GetPC(folder.Key)); + writer.Write(folder.Value.Count()); + writer.Write(-1); // Temporary, file record offset + } + + foreach (string folder in folderOrdList) + { + folderOffList.Add((uint)writer.BaseStream.Position); + writer.Write((byte)(folder.Length + 1)); + writer.Write(folder.ToCharArray()); + writer.Write((byte)0); + + foreach (Asset asset in folderDict[folder]) + { + writer.Write(asset.Hash); + writer.Write((long)-1); // Temporary, asset size + offset + } + } + + foreach (string folder in folderOrdList) + { + foreach (Asset asset in folderDict[folder]) + { + writer.WriteNullTerminatedString(asset.FileName); + } + } + + foreach (string folder in folderOrdList) + { + foreach (Asset asset in folderDict[folder]) + { + asset.Offset = (uint)writer.BaseStream.Position; + byte[] data; + + if (convertNormalMaps && asset.IsDDS && asset.IsNormalMap) + { + string tempPath = Path.GetTempFileName(); + bool converted = NormalMap.ConvertToPS3(asset.RealPath, tempPath); + + data = File.ReadAllBytes(converted ? tempPath : asset.RealPath); + File.Delete(tempPath); // Cleans up + } + else + { + data = File.ReadAllBytes(asset.RealPath); + } + + bool extendAsset = extendDDS && asset.IsDDS && !asset.IsExtended; + + if (compress) + { + asset.OriginalSize = extendAsset ? (uint)data.Length + (uint)asset.EntryStr.Length + 1 : (uint)data.Length; + + writer.Write(asset.OriginalSize); + writer.Write(BSA.GetCompressedZlibData(data, asset, extendAsset)); + } + else + { + asset.Size = (uint)data.Length; + writer.Write(data); + + if (extendAsset) + { + asset.Size += (uint)asset.EntryStr.Length + 1; // +1 is accounting for null terminator + writer.Write(asset.EntryStr); + } + } + } + } + + writer.BaseStream.Position = 36; // Folder record offset + foreach (uint off in folderOffList) + { + writer.BaseStream.Position += 12; // Skips hash and file count + writer.Write(off + totalFileNameLength); + } + + foreach (string folder in folderOrdList) + { + writer.BaseStream.Position += folder.Length + 2; + + foreach (Asset asset in folderDict[folder]) + { + writer.BaseStream.Position += 8; // Skips hash + + writer.Write(asset.Size); + writer.Write(asset.Offset); + } + } + } + } + + /// + /// Compressed data using the zlib compression library. + /// + /// Data to be compressed. + /// Asset class that is associated with that data. + /// True or false if file data should be extendeed + /// Compressed data + private static byte[] GetCompressedZlibData(byte[] data, Asset asset, bool extendData) + { + if (data == null || data.Length == 0) + return data; + + if (extendData) + { + // Length byte + entry string + byte[] extensionData = (new byte[1] { (byte)asset.EntryStr.Length }).Concat(Encoding.ASCII.GetBytes(asset.EntryStr.ToLower())).ToArray(); + data = data.Concat(extensionData).ToArray(); + } + + using (var inStream = new MemoryStream(data)) + { + var outStream = new MemoryStream(); + var compressStream = new ZlibStream(outStream, System.IO.Compression.CompressionLevel.Optimal, true); + + int bufferSize; + var buffer = new byte[4096]; + + while ((bufferSize = inStream.Read(buffer, 0, buffer.Length)) > 0) + { + compressStream.Write(buffer, 0, bufferSize); + } + + compressStream.Close(); + byte[] outStreamData = outStream.ToArray(); + + asset.Size = (uint)outStreamData.Length + 4; + return outStreamData; + } + } + + /// + /// Generates file flags for BSA and returns them. + /// + /// List of file extensions. + /// True or false if PS3 file flags should be used instead of PC. + /// File flags for BSA header. + private static uint GetFileFlags(List extensionList, bool usePS3FileFlags) + { + uint fileFlags = usePS3FileFlags ? 0xCDCD0000 : 0x00000000; + + foreach (string ext in extensionList) + { + switch (ext.Substring(1)) + { + default: // Miscellaneous + { + fileFlags |= 1 << 9; + break; + } + + case "nif": + { + fileFlags |= 1 << 0; + break; + } + + case "dds": + { + fileFlags |= 1 << 1; + break; + } + + case "xml": + { + fileFlags |= 1 << 2; + break; + } + + case "wav": + { + fileFlags |= 1 << 3; + break; + } + + case "mp3": + { + fileFlags |= 1 << 4; + break; + } + + case "txt": + case "html": + case "bat": + case "scc": + { + fileFlags |= 1 << 5; + break; + } + + case "spt": + case "stg": + { + fileFlags |= 1 << 6; + break; + } + + case "tex": + case "fnt": + { + fileFlags |= 1 << 7; + break; + } + + case "ctl": + { + fileFlags |= 1 << 8; + break; + } + } + } + + return fileFlags; + } + } +} \ No newline at end of file diff --git a/ArchiveInterop/Classes/BinaryWriterExtensions.cs b/ArchiveInterop/Classes/BinaryWriterExtensions.cs new file mode 100644 index 0000000..a47555d --- /dev/null +++ b/ArchiveInterop/Classes/BinaryWriterExtensions.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace ArchiveInterop.Classes +{ + /// + /// Provides static extension method for adding functionality to BinaryWriter streams. + /// + public static class BinaryWriterExtensions + { + /// + /// Writes string in null-terminated format in BinaryWriter stream. + /// + /// Strea to write in. + /// String to write. + public static void WriteNullTerminatedString(this BinaryWriter writer, string str) + { + writer.Write((str + '\0').ToCharArray()); + } + } +} \ No newline at end of file diff --git a/ArchiveInterop/OblivionBSAHash.cs b/ArchiveInterop/OblivionBSAHash.cs new file mode 100644 index 0000000..c2c0be9 --- /dev/null +++ b/ArchiveInterop/OblivionBSAHash.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; + +namespace ArchiveInterop +{ + /// + /// Provides static methods for hashing strings for PS3 and PC Oblivion BSAs. + /// + public class OblivionBSAHash + { + /// + /// Based on code from UESP wiki. + /// Hashes string based on PS3 hashing algorithm, should only be used on texture (DirectDraw Surface) files. + /// + /// String to be hashed. + /// Hashed string as ulong + public static ulong GetPS3(string str) + { + string name = Path.ChangeExtension(str, null).ToLower(); + string ext = Path.GetExtension(str).ToLower(); + var lastChar = (byte)(name.Length == 0 ? 0x00 : name[name.Length - 1]); // Last character of "name" string + + var hashBytes = new byte[] + { + lastChar, + lastChar, + (byte)name.Length, + (byte)(name.Length == 0 ? 0x00 : name[0]) + }; + + // Extra handling if second-to-last character is underscore + if (name.Length > 2 && name[name.Length - 2].Equals('_')) + { + name = name.Remove(name.Length - 2); // "_X" must be removed from the filename + hashBytes[2] -= 2; // Reduces name length byte by 2 + + hashBytes[1] = (byte)(name.Length == 0 ? 0x00 : name[name.Length - 1]); + hashBytes[0] ^= 0x80; + } + else + { + hashBytes[0] = 0x80; + } + + hashBytes[1] ^= 0x80; + uint hash1 = BitConverter.ToUInt32(hashBytes, 0); + + uint hash2 = 0; + for (int i = 1; i < name.Length - 1; i++) + { + hash2 = hash2 * 0x1003F + (byte)name[i]; + } + + uint hash3 = 0; + for (int i = 0; i < ext.Length; i++) + { + hash3 = hash3 * 0x1003F + (byte)ext[i]; + } + + return (((ulong)(hash2 + hash3)) << 32) + hash1; + } + + /// + /// From UESP wiki. + /// Hashes string based on PC hashing algorithm. + /// + /// Name of file/str + /// Extension if hashing a file name + /// Hashed name and extension as ulong + public static ulong GetPC(string name, string ext = "") + { + var hashBytes = new byte[] + { + (byte)(name.Length == 0 ? 0x00 : name[name.Length - 1]), + (byte)(name.Length < 3 ? 0x00 : name[name.Length - 2]), + (byte)name.Length, + (byte)(name.Length == 0 ? 0x00 : name[0]) + }; + + uint hash1 = BitConverter.ToUInt32(hashBytes, 0); + + switch (ext) + { + case ".kf": + hash1 |= 0x80; + break; + + case ".nif": + hash1 |= 0x8000; + break; + + case ".dds": + hash1 |= 0x8080; + break; + + case ".wav": + hash1 |= 0x80000000; + break; + } + + uint hash2 = 0; + for (int i = 1; i < name.Length - 2; i++) + { + hash2 = hash2 * 0x1003F + (byte)name[i]; + } + + uint hash3 = 0; + for (int i = 0; i < ext.Length; i++) + { + hash3 = hash3 * 0x1003F + (byte)ext[i]; + } + + return (((ulong)(hash2 + hash3)) << 32) + hash1; + } + } +} \ No newline at end of file diff --git a/ArchiveInterop/Properties/AssemblyInfo.cs b/ArchiveInterop/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1871eba --- /dev/null +++ b/ArchiveInterop/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("ArchiveInterop")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BethesdaSoftworksArchive OblivionPS3")] +[assembly: AssemblyCopyright("Copyright © 2020 SockNastre")] +[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("fde41ce6-91fb-4b01-a63b-16340bdd5abf")] + +// 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")] diff --git a/ArchiveInterop/Zlib/Adler32.cs b/ArchiveInterop/Zlib/Adler32.cs new file mode 100644 index 0000000..8a210cb --- /dev/null +++ b/ArchiveInterop/Zlib/Adler32.cs @@ -0,0 +1,60 @@ +using System; + +namespace Zlib +{ + public class Adler32 + { + #region "Variables globales" + private UInt32 a = 1; + private UInt32 b = 0; + private const int _base = 65521; + private const int _nmax = 5550; + private int pend = 0; + #endregion + #region "Metodos publicos" + public void Update(byte data) + { + if (pend >= _nmax) updateModulus(); + a += data; + b += a; + pend++; + } + public void Update(byte[] data) + { + Update(data, 0, data.Length); + } + public void Update(byte[] data, int offset, int length) + { + int nextJToComputeModulus = _nmax - pend; + for (int j = 0; j < length; j++) { + if (j == nextJToComputeModulus) { + updateModulus(); + nextJToComputeModulus = j + _nmax; + } + unchecked { + a += data[j + offset]; + } + b += a; + pend++; + } + } + public void Reset() + { + a = 1; + b = 0; + pend = 0; + } + private void updateModulus() + { + a %= _base; + b %= _base; + pend = 0; + } + public UInt32 GetValue() + { + if (pend > 0) updateModulus(); + return (b << 16) | a; + } + #endregion + } +} \ No newline at end of file diff --git a/ArchiveInterop/Zlib/LICENSE.htm b/ArchiveInterop/Zlib/LICENSE.htm new file mode 100644 index 0000000..7aa538d --- /dev/null +++ b/ArchiveInterop/Zlib/LICENSE.htm @@ -0,0 +1,251 @@ +? + +The Code Project Open License (CPOL) + + + + +

The Code Project Open License (CPOL) 1.02

+
+ +
+
+ +

Preamble

+

+ This License governs Your use of the Work. This License is intended to allow developers + to use the Source Code and Executable Files provided as part of the Work in any + application in any form. +

+

+ The main points subject to the terms of the License are:

+
    +
  • Source Code and Executable Files can be used in commercial applications;
  • +
  • Source Code and Executable Files can be redistributed; and
  • +
  • Source Code can be modified to create derivative works.
  • +
  • No claim of suitability, guarantee, or any warranty whatsoever is provided. The software is + provided "as-is".
  • +
  • The Article accompanying the Work may not be distributed or republished without the + Author's consent
  • +
+ +

+ This License is entered between You, the individual or other entity reading or otherwise + making use of the Work licensed pursuant to this License and the individual or other + entity which offers the Work under the terms of this License ("Author").

+ +

License

+

+ THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CODE PROJECT OPEN + LICENSE ("LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE + LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT + LAW IS PROHIBITED.

+

+ BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HEREIN, YOU ACCEPT AND AGREE TO BE + BOUND BY THE TERMS OF THIS LICENSE. THE AUTHOR GRANTS YOU THE RIGHTS CONTAINED HEREIN + IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. IF YOU DO NOT + AGREE TO ACCEPT AND BE BOUND BY THE TERMS OF THIS LICENSE, YOU CANNOT MAKE ANY + USE OF THE WORK.

+ +
    +
  1. Definitions. + +
      +
    1. "Articles" means, collectively, all articles written by Author + which describes how the Source Code and Executable Files for the Work may be used + by a user.
    2. +
    3. "Author" means the individual or entity that offers the Work under the terms + of this License.
    4. +
    5. "Derivative Work" means a work based upon the Work or upon the + Work and other pre-existing works.
    6. +
    7. "Executable Files" refer to the executables, binary files, configuration + and any required data files included in the Work.
    8. +
    9. "Publisher" means the provider of the website, magazine, CD-ROM, DVD or other + medium from or by which the Work is obtained by You.
    10. +
    11. "Source Code" refers to the collection of source code and configuration files + used to create the Executable Files.
    12. +
    13. "Standard Version" refers to such a Work if it has not been modified, or + has been modified in accordance with the consent of the Author, such consent being + in the full discretion of the Author.
    14. +
    15. "Work" refers to the collection of files distributed by the Publisher, including + the Source Code, Executable Files, binaries, data files, documentation, whitepapers + and the Articles.
    16. +
    17. "You" is you, an individual or entity wishing to use the Work and exercise + your rights under this License. +
    18. +
    +
  2. + +
  3. Fair Use/Fair Use Rights. Nothing in this License is intended to + reduce, limit, or restrict any rights arising from fair use, fair dealing, first + sale or other limitations on the exclusive rights of the copyright owner under copyright + law or other applicable laws. +
  4. + +
  5. License Grant. Subject to the terms and conditions of this License, + the Author hereby grants You a worldwide, royalty-free, non-exclusive, perpetual + (for the duration of the applicable copyright) license to exercise the rights in + the Work as stated below: + +
      +
    1. You may use the standard version of the Source Code or Executable Files in Your + own applications.
    2. +
    3. You may apply bug fixes, portability fixes and other modifications obtained from + the Public Domain or from the Author. A Work modified in such a way shall still + be considered the standard version and will be subject to this License.
    4. +
    5. You may otherwise modify Your copy of this Work (excluding the Articles) in any + way to create a Derivative Work, provided that You insert a prominent notice in + each changed file stating how, when and where You changed that file.
    6. +
    7. You may distribute the standard version of the Executable Files and Source Code + or Derivative Work in aggregate with other (possibly commercial) programs as part + of a larger (possibly commercial) software distribution.
    8. +
    9. The Articles discussing the Work published in any form by the author may not be + distributed or republished without the Author's consent. The author retains + copyright to any such Articles. You may use the Executable Files and Source Code + pursuant to this License but you may not repost or republish or otherwise distribute + or make available the Articles, without the prior written consent of the Author.
    10. +
    + + Any subroutines or modules supplied by You and linked into the Source Code or Executable + Files of this Work shall not be considered part of this Work and will not be subject + to the terms of this License. +
  6. + +
  7. Patent License. Subject to the terms and conditions of this License, + each Author hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, + irrevocable (except as stated in this section) patent license to make, have made, use, import, + and otherwise transfer the Work.
  8. + +
  9. Restrictions. The license granted in Section 3 above is expressly + made subject to and limited by the following restrictions: + +
      +
    1. You agree not to remove any of the original copyright, patent, trademark, and + attribution notices and associated disclaimers that may appear in the Source Code + or Executable Files.
    2. +
    3. You agree not to advertise or in any way imply that this Work is a product of Your + own.
    4. +
    5. The name of the Author may not be used to endorse or promote products derived from + the Work without the prior written consent of the Author.
    6. +
    7. You agree not to sell, lease, or rent any part of the Work. This does not restrict + you from including the Work or any part of the Work inside a larger software + distribution that itself is being sold. The Work by itself, though, cannot be sold, + leased or rented.
    8. +
    9. You may distribute the Executable Files and Source Code only under the terms of + this License, and You must include a copy of, or the Uniform Resource Identifier + for, this License with every copy of the Executable Files or Source Code You distribute + and ensure that anyone receiving such Executable Files and Source Code agrees that + the terms of this License apply to such Executable Files and/or Source Code. You + may not offer or impose any terms on the Work that alter or restrict the terms of + this License or the recipients' exercise of the rights granted hereunder. You + may not sublicense the Work. You must keep intact all notices that refer to this + License and to the disclaimer of warranties. You may not distribute the Executable + Files or Source Code with any technological measures that control access or use + of the Work in a manner inconsistent with the terms of this License.
    10. +
    11. You agree not to use the Work for illegal, immoral or improper purposes, or on pages + containing illegal, immoral or improper material. The Work is subject to applicable + export laws. You agree to comply with all such laws and regulations that may apply + to the Work after Your receipt of the Work. +
    12. +
    +
  10. + +
  11. Representations, Warranties and Disclaimer. THIS WORK IS PROVIDED + "AS IS", "WHERE IS" AND "AS AVAILABLE", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES + OR CONDITIONS OR GUARANTEES. YOU, THE USER, ASSUME ALL RISK IN ITS USE, INCLUDING + COPYRIGHT INFRINGEMENT, PATENT INFRINGEMENT, SUITABILITY, ETC. AUTHOR EXPRESSLY + DISCLAIMS ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS, INCLUDING + WITHOUT LIMITATION, WARRANTIES OR CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY + OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT, + OR THAT THE WORK (OR ANY PORTION THEREOF) IS CORRECT, USEFUL, BUG-FREE OR FREE OF + VIRUSES. YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE + WORKS. +
  12. + +
  13. Indemnity. You agree to defend, indemnify and hold harmless the Author and + the Publisher from and against any claims, suits, losses, damages, liabilities, + costs, and expenses (including reasonable legal or attorneys’ fees) resulting from + or relating to any use of the Work by You. +
  14. + +
  15. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE + LAW, IN NO EVENT WILL THE AUTHOR OR THE PUBLISHER BE LIABLE TO YOU ON ANY LEGAL + THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES + ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK OR OTHERWISE, EVEN IF THE AUTHOR + OR THE PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +
  16. + +
  17. Termination. + +
      +
    1. This License and the rights granted hereunder will terminate automatically upon + any breach by You of any term of this License. Individuals or entities who have + received Derivative Works from You under this License, however, will not have their + licenses terminated provided such individuals or entities remain in full compliance + with those licenses. Sections 1, 2, 6, 7, 8, 9, 10 and 11 will survive any termination + of this License.
    2. + +
    3. If You bring a copyright, trademark, patent or any other infringement claim against + any contributor over infringements You claim are made by the Work, your License + from such contributor to the Work ends automatically.
    4. + +
    5. Subject to the above terms and conditions, this License is perpetual (for the duration + of the applicable copyright in the Work). Notwithstanding the above, the Author + reserves the right to release the Work under different license terms or to stop + distributing the Work at any time; provided, however that any such election will + not serve to withdraw this License (or any other license that has been, or is required + to be, granted under the terms of this License), and this License will continue + in full force and effect unless terminated as stated above. +
    6. +
    +
  18. + +
  19. Publisher. The parties hereby confirm that the Publisher shall + not, under any circumstances, be responsible for and shall not have any liability + in respect of the subject matter of this License. The Publisher makes no warranty + whatsoever in connection with the Work and shall not be liable to You or any party + on any legal theory for any damages whatsoever, including without limitation any + general, special, incidental or consequential damages arising in connection to this + license. The Publisher reserves the right to cease making the Work available to + You at any time without notice
  20. + +
  21. Miscellaneous + +
      +
    1. This License shall be governed by the laws of the location of the head office of + the Author or if the Author is an individual, the laws of location of the principal + place of residence of the Author.
    2. +
    3. If any provision of this License is invalid or unenforceable under applicable law, + it shall not affect the validity or enforceability of the remainder of the terms + of this License, and without further action by the parties to this License, such + provision shall be reformed to the minimum extent necessary to make such provision + valid and enforceable.
    4. +
    5. No term or provision of this License shall be deemed waived and no breach consented + to unless such waiver or consent shall be in writing and signed by the party to + be charged with such waiver or consent.
    6. +
    7. This License constitutes the entire agreement between the parties with respect to + the Work licensed herein. There are no understandings, agreements or representations + with respect to the Work not specified herein. The Author shall not be bound by + any additional provisions that may appear in any communication from You. This License + may not be modified without the mutual written agreement of the Author and You. +
    8. +
    + +
  22. +
+ +
+
+ + + \ No newline at end of file diff --git a/ArchiveInterop/Zlib/LICENSE.txt b/ArchiveInterop/Zlib/LICENSE.txt new file mode 100644 index 0000000..80eb485 --- /dev/null +++ b/ArchiveInterop/Zlib/LICENSE.txt @@ -0,0 +1,58 @@ +https://www.codeproject.com/info/cpol10.aspx + +Preamble +This License governs Your use of the Work. This License is intended to allow developers to use the Source Code and Executable Files provided as part of the Work in any application in any form. + +The main points subject to the terms of the License are: + +Source Code and Executable Files can be used in commercial applications; +Source Code and Executable Files can be redistributed; and +Source Code can be modified to create derivative works. +No claim of suitability, guarantee, or any warranty whatsoever is provided. The software is provided "as-is". +The Article(s) accompanying the Work may not be distributed or republished without the Author's consent +This License is entered between You, the individual or other entity reading or otherwise making use of the Work licensed pursuant to this License and the individual or other entity which offers the Work under the terms of this License ("Author"). + +License +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CODE PROJECT OPEN LICENSE ("LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HEREIN, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE AUTHOR GRANTS YOU THE RIGHTS CONTAINED HEREIN IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. IF YOU DO NOT AGREE TO ACCEPT AND BE BOUND BY THE TERMS OF THIS LICENSE, YOU CANNOT MAKE ANY USE OF THE WORK. + +Definitions. +"Articles" means, collectively, all articles written by Author which describes how the Source Code and Executable Files for the Work may be used by a user. +"Author" means the individual or entity that offers the Work under the terms of this License. +"Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works. +"Executable Files" refer to the executables, binary files, configuration and any required data files included in the Work. +"Publisher" means the provider of the website, magazine, CD-ROM, DVD or other medium from or by which the Work is obtained by You. +"Source Code" refers to the collection of source code and configuration files used to create the Executable Files. +"Standard Version" refers to such a Work if it has not been modified, or has been modified in accordance with the consent of the Author, such consent being in the full discretion of the Author. +"Work" refers to the collection of files distributed by the Publisher, including the Source Code, Executable Files, binaries, data files, documentation, whitepapers and the Articles. +"You" is you, an individual or entity wishing to use the Work and exercise your rights under this License. +Fair Use/Fair Use Rights. Nothing in this License is intended to reduce, limit, or restrict any rights arising from fair use, fair dealing, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. +License Grant. Subject to the terms and conditions of this License, the Author hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: +You may use the standard version of the Source Code or Executable Files in Your own applications. +You may apply bug fixes, portability fixes and other modifications obtained from the Public Domain or from the Author. A Work modified in such a way shall still be considered the standard version and will be subject to this License. +You may otherwise modify Your copy of this Work (excluding the Articles) in any way to create a Derivative Work, provided that You insert a prominent notice in each changed file stating how, when and where You changed that file. +You may distribute the standard version of the Executable Files and Source Code or Derivative Work in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution. +The Articles discussing the Work published in any form by the author may not be distributed or republished without the Author's consent. The author retains copyright to any such Articles. You may use the Executable Files and Source Code pursuant to this License but you may not repost or republish or otherwise distribute or make available the Articles, without the prior written consent of the Author. +Any subroutines or modules supplied by You and linked into the Source Code or Executable Files of this Work shall not be considered part of this Work and will not be subject to the terms of this License. +Patent License. Subject to the terms and conditions of this License, each Author hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, import, and otherwise transfer the Work. +Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: +You agree not to remove any of the original copyright, patent, trademark, and attribution notices and associated disclaimers that may appear in the Source Code or Executable Files. +You agree not to advertise or in any way imply that this Work is a product of Your own. +The name of the Author may not be used to endorse or promote products derived from the Work without the prior written consent of the Author. +You agree not to sell, lease, or rent any part of the Work. This does not restrict you from including the Work or any part of the Work inside a larger software distribution that itself is being sold. The Work by itself, though, cannot be sold, leased or rented. +You may distribute the Executable Files and Source Code only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy of the Executable Files or Source Code You distribute and ensure that anyone receiving such Executable Files and Source Code agrees that the terms of this License apply to such Executable Files and/or Source Code. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute the Executable Files or Source Code with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License. +You agree not to use the Work for illegal, immoral or improper purposes, or on pages containing illegal, immoral or improper material. The Work is subject to applicable export laws. You agree to comply with all such laws and regulations that may apply to the Work after Your receipt of the Work. +Representations, Warranties and Disclaimer. THIS WORK IS PROVIDED "AS IS", "WHERE IS" AND "AS AVAILABLE", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OR CONDITIONS OR GUARANTEES. YOU, THE USER, ASSUME ALL RISK IN ITS USE, INCLUDING COPYRIGHT INFRINGEMENT, PATENT INFRINGEMENT, SUITABILITY, ETC. AUTHOR EXPRESSLY DISCLAIMS ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS, INCLUDING WITHOUT LIMITATION, WARRANTIES OR CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY OR FITNESS FOR A PARTICULAR PURPOSE, OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT, OR THAT THE WORK (OR ANY PORTION THEREOF) IS CORRECT, USEFUL, BUG-FREE OR FREE OF VIRUSES. YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. +Indemnity. You agree to defend, indemnify and hold harmless the Author and the Publisher from and against any claims, suits, losses, damages, liabilities, costs, and expenses (including reasonable legal or attorneys’ fees) resulting from or relating to any use of the Work by You. +Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL THE AUTHOR OR THE PUBLISHER BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK OR OTHERWISE, EVEN IF THE AUTHOR OR THE PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +Termination. +This License and the rights granted hereunder will terminate automatically upon any breach by You of any term of this License. Individuals or entities who have received Derivative Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 6, 7, 8, 9, 10 and 11 will survive any termination of this License. +If You bring a copyright, trademark, patent or any other infringement claim against any contributor over infringements You claim are made by the Work, your License from such contributor to the Work ends automatically. +Subject to the above terms and conditions, this License is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, the Author reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. +Publisher. The parties hereby confirm that the Publisher shall not, under any circumstances, be responsible for and shall not have any liability in respect of the subject matter of this License. The Publisher makes no warranty whatsoever in connection with the Work and shall not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. The Publisher reserves the right to cease making the Work available to You at any time without notice +Miscellaneous +This License shall be governed by the laws of the location of the head office of the Author or if the Author is an individual, the laws of location of the principal place of residence of the Author. +If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this License, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. +No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. +This License constitutes the entire agreement between the parties with respect to the Work licensed herein. There are no understandings, agreements or representations with respect to the Work not specified herein. The Author shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Author and You. diff --git a/ArchiveInterop/Zlib/ZLIBStream.cs b/ArchiveInterop/Zlib/ZLIBStream.cs new file mode 100644 index 0000000..2c92968 --- /dev/null +++ b/ArchiveInterop/Zlib/ZLIBStream.cs @@ -0,0 +1,371 @@ +using System; +using System.IO; +using System.IO.Compression; + +namespace Zlib +{ + public sealed class ZlibStream : Stream + { + #region "Variables globales" + private CompressionMode mCompressionMode = CompressionMode.Compress; + private CompressionLevel mCompressionLevel = CompressionLevel.NoCompression; + private bool mLeaveOpen = false; + private Adler32 adler32 = new Adler32(); + private DeflateStream mDeflateStream; + private Stream mRawStream; + private bool mClosed = false; + private byte[] mCRC = null; + #endregion + #region "Constructores" + /// + /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y nivel de compresión especificados. + /// + /// Secuencia que se va a comprimir + /// Nivel de compresión + public ZlibStream(Stream stream, CompressionLevel compressionLevel) : this(stream, compressionLevel, false) + { + } + /// + /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y modo de compresión especificados. + /// + /// Secuencia que se va a comprimir o descomprimir + /// Modo de compresión + public ZlibStream(Stream stream, CompressionMode compressionMode) : this(stream, compressionMode, false) + { + } + /// + /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y nivel de compresión especificados y, opcionalmente, deja la secuencia abierta. + /// + /// Secuencia que se va a comprimir + /// Nivel de compresión + /// Indica si se debe de dejar la secuencia abierta después de comprimir la secuencia + public ZlibStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen) + { + this.mCompressionMode = CompressionMode.Compress; + this.mCompressionLevel = compressionLevel; + this.mLeaveOpen = leaveOpen; + this.mRawStream = stream; + this.InicializarStream(); + } + /// + /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y modo de compresión especificados y, opcionalmente, deja la secuencia abierta. + /// + /// Secuencia que se va a comprimir o descomprimir + /// Modo de compresión + /// Indica si se debe de dejar la secuencia abierta después de comprimir o descomprimir la secuencia + public ZlibStream(Stream stream, CompressionMode compressionMode, bool leaveOpen) + { + this.mCompressionMode = compressionMode; + this.mCompressionLevel = CompressionLevel.Fastest; + this.mLeaveOpen = leaveOpen; + this.mRawStream = stream; + this.InicializarStream(); + } + #endregion + #region "Propiedades sobreescritas" + public override bool CanRead + { + get + { + return ((this.mCompressionMode == CompressionMode.Decompress) && (this.mClosed != true)); + } + } + public override bool CanWrite + { + get + { + return ((this.mCompressionMode == CompressionMode.Compress) && (this.mClosed != true)); + } + } + public override bool CanSeek + { + get + { + return false; + } + } + public override long Length + { + get + { + throw new NotImplementedException(); + } + } + public override long Position + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + #endregion + #region "Metodos sobreescritos" + public override int ReadByte() + { + int result = 0; + + if (this.CanRead == true) + { + result = this.mDeflateStream.ReadByte(); + + //Comprobamos si se ha llegado al final del stream + if (result == -1) + { + this.ReadCRC(); + } + else + { + this.adler32.Update(Convert.ToByte(result)); + } + } + else + { + throw new InvalidOperationException(); + } + + return result; + } + + public override int Read(byte[] buffer, int offset, int count) + { + int result = 0; + + if (this.CanRead == true) + { + result = this.mDeflateStream.Read(buffer, offset, count); + + //Comprobamos si hemos llegado al final del stream + if ((result < 1) && (count > 0)) + { + this.ReadCRC(); + } + else + { + this.adler32.Update(buffer, offset, result); + } + } + else + { + throw new InvalidOperationException(); + } + + return result; + } + + public override void WriteByte(byte value) + { + if (this.CanWrite == true) + { + this.mDeflateStream.WriteByte(value); + this.adler32.Update(value); + } + else + { + throw new InvalidOperationException(); + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (this.CanWrite == true) + { + this.mDeflateStream.Write(buffer, offset, count); + this.adler32.Update(buffer, offset, count); + } + else + { + throw new InvalidOperationException(); + } + } + + public override void Close() + { + if (this.mClosed == false) + { + this.mClosed = true; + if (this.mCompressionMode == CompressionMode.Compress) + { + this.Flush(); + this.mDeflateStream.Close(); + + this.mCRC = BitConverter.GetBytes(adler32.GetValue()); + + if (BitConverter.IsLittleEndian == true) + { + Array.Reverse(this.mCRC); + } + + this.mRawStream.Write(this.mCRC, 0, this.mCRC.Length); + } + else + { + this.mDeflateStream.Close(); + if (this.mCRC == null) + { + this.ReadCRC(); + } + } + + if (this.mLeaveOpen == false) + { + this.mRawStream.Close(); + } + } + else + { + throw new InvalidOperationException("Stream already closed"); + } + } + + public override void Flush() + { + if (this.mDeflateStream != null) + { + this.mDeflateStream.Flush(); + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + #endregion + #region "Metodos publicos" + /// + /// Comprueba si el stream esta en formato ZLib + /// + /// Stream a comprobar + /// Retorna True en caso de que el stream sea en formato ZLib y False en caso contrario u error + public static bool IsZLibStream(Stream stream) + { + bool bResult = false; + int CMF = 0; + int Flag = 0; + ZlibHeader header; + + //Comprobamos si la secuencia esta en la posición 0, de no ser así, lanzamos una excepción + if (stream.Position != 0) + { + throw new ArgumentOutOfRangeException("Sequence must be at position 0"); + } + + //Comprobamos si podemos realizar la lectura de los dos bytes que conforman la cabecera + if (stream.CanRead == true) + { + CMF = stream.ReadByte(); + Flag = stream.ReadByte(); + try + { + header = ZlibHeader.DecodeHeader(CMF, Flag); + bResult = header.IsSupportedZLibStream; + } + catch + { + //Nada + } + } + + return bResult; + } + /// + /// Lee los últimos 4 bytes del stream ya que es donde está el CRC + /// + private void ReadCRC() + { + this.mCRC = new byte[4]; + this.mRawStream.Seek(-4, SeekOrigin.End); + if (this.mRawStream.Read(this.mCRC, 0, 4) < 4) + { + throw new EndOfStreamException(); + } + + if (BitConverter.IsLittleEndian == true) + { + Array.Reverse(this.mCRC); + } + + uint crcAdler = this.adler32.GetValue(); + uint crcStream = BitConverter.ToUInt32(this.mCRC, 0); + + if (crcStream != crcAdler) + { + throw new Exception("CRC mismatch"); + } + } + #endregion + #region "Metodos privados" + /// + /// Inicializa el stream + /// + private void InicializarStream() + { + switch (this.mCompressionMode) + { + case CompressionMode.Compress: + { + this.InicializarZLibHeader(); + this.mDeflateStream = new DeflateStream(this.mRawStream, this.mCompressionLevel, true); + break; + } + case CompressionMode.Decompress: + { + if (ZlibStream.IsZLibStream(this.mRawStream) == false) + { + throw new InvalidDataException(); + } + this.mDeflateStream = new DeflateStream(this.mRawStream, CompressionMode.Decompress, true); + break; + } + } + } + /// + /// Inicializa el encabezado del stream en formato ZLib + /// + private void InicializarZLibHeader() + { + byte[] bytesHeader; + + //Establecemos la configuración de la cabecera + var header = new ZlibHeader + { + CompressionMethod = 8, //Deflate + CompressionInfo = 7, + FDict = false //Sin diccionario + }; + + switch (this.mCompressionLevel) + { + case CompressionLevel.NoCompression: + { + header.FLevel = FLevel.Faster; + break; + } + case CompressionLevel.Fastest: + { + header.FLevel = FLevel.Default; + break; + } + case CompressionLevel.Optimal: + { + header.FLevel = FLevel.Optimal; + break; + } + } + + bytesHeader = header.EncodeZlibHeader(); + + this.mRawStream.WriteByte(bytesHeader[0]); + this.mRawStream.WriteByte(bytesHeader[1]); + } + #endregion + } +} \ No newline at end of file diff --git a/ArchiveInterop/Zlib/ZLibHeader.cs b/ArchiveInterop/Zlib/ZLibHeader.cs new file mode 100644 index 0000000..e303036 --- /dev/null +++ b/ArchiveInterop/Zlib/ZLibHeader.cs @@ -0,0 +1,174 @@ +using System; + +namespace Zlib +{ + public enum FLevel + { + Faster = 0, + Fast = 1, + Default = 2, + Optimal = 3, + } + public sealed class ZlibHeader + { + #region "Variables globales" + private bool mIsSupportedZLibStream; + private byte mCompressionMethod; //CMF 0-3 + private byte mCompressionInfo; //CMF 4-7 + private byte mFCheck; //Flag 0-4 (Check bits for CMF and FLG) + private bool mFDict; //Flag 5 (Preset dictionary) + private FLevel mFLevel; //Flag 6-7 (Compression level) + #endregion + #region "Propiedades" + public bool IsSupportedZLibStream + { + get + { + return this.mIsSupportedZLibStream; + } + set + { + this.mIsSupportedZLibStream = value; + } + } + public byte CompressionMethod + { + get + { + return this.mCompressionMethod; + } + set + { + if (value > 15) + { + throw new ArgumentOutOfRangeException("Argument cannot be greater than 15"); + } + this.mCompressionMethod = value; + } + } + public byte CompressionInfo + { + get + { + return this.mCompressionInfo; + } + set + { + if (value > 15) + { + throw new ArgumentOutOfRangeException("Argument cannot be greater than 15"); + } + this.mCompressionInfo = value; + } + } + public byte FCheck + { + get + { + return this.mFCheck; + } + set + { + if (value > 31) + { + throw new ArgumentOutOfRangeException("Argument cannot be greater than 31"); + } + this.mFCheck = value; + } + } + public bool FDict + { + get + { + return this.mFDict; + } + set + { + this.mFDict = value; + } + } + public FLevel FLevel + { + get + { + return this.mFLevel; + } + set + { + this.mFLevel = value; + } + } + #endregion + #region "Constructor" + public ZlibHeader() + { + + } + #endregion + #region "Metodos privados" + private void RefreshFCheck() + { + byte byteFLG = 0x00; + + byteFLG = (byte)(Convert.ToByte(this.FLevel) << 1); + byteFLG |= Convert.ToByte(this.FDict); + + this.FCheck = Convert.ToByte(31 - Convert.ToByte((this.GetCMF() * 256 + byteFLG) % 31)); + } + private byte GetCMF() + { + byte byteCMF = 0x00; + + byteCMF = (byte)(this.CompressionInfo << 4); + byteCMF |= (byte)(this.CompressionMethod); + + return byteCMF; + } + private byte GetFLG() + { + byte byteFLG = 0x00; + + byteFLG = (byte)(Convert.ToByte(this.FLevel) << 6); + byteFLG |= (byte)(Convert.ToByte(this.FDict) << 5); + byteFLG |= this.FCheck; + + return byteFLG; + } + #endregion + #region "Metodos publicos" + public byte[] EncodeZlibHeader() + { + byte[] result = new byte[2]; + + this.RefreshFCheck(); + + result[0] = this.GetCMF(); + result[1] = 0x9C; // this.GetFLG(); - Modified because for the usage of this tool the compression level byte was inproper + + return result; + } + #endregion + #region "Metodos estáticos" + public static ZlibHeader DecodeHeader(int pCMF, int pFlag) + { + var result = new ZlibHeader(); + + //Ensure that parameters are bytes + pCMF = pCMF & 0x0FF; + pFlag = pFlag & 0x0FF; + + //Decode bytes + result.CompressionInfo = Convert.ToByte((pCMF & 0xF0) >> 4); + result.CompressionMethod = Convert.ToByte(pCMF & 0x0F); + + result.FCheck = Convert.ToByte(pFlag & 0x1F); + result.FDict = Convert.ToBoolean(Convert.ToByte((pFlag & 0x20) >> 5)); + result.FLevel = (FLevel)Convert.ToByte((pFlag & 0xC0) >> 6); + + result.IsSupportedZLibStream = (result.CompressionMethod == 8) && (result.CompressionInfo == 7) && (((pCMF * 256 + pFlag) % 31 == 0)) && (result.FDict == false); + + return result; + } + #endregion + } +} \ No newline at end of file diff --git a/BethesdaSoftworksArchive OblivionPS3.sln b/BethesdaSoftworksArchive OblivionPS3.sln new file mode 100644 index 0000000..fcd71fc --- /dev/null +++ b/BethesdaSoftworksArchive OblivionPS3.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiveInterop", "ArchiveInterop\ArchiveInterop.csproj", "{FDE41CE6-91FB-4B01-A63B-16340BDD5ABF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackerGUI", "PackerGUI\PackerGUI.csproj", "{5DC0FC15-3CCD-41C6-8F90-4DE8041F9C5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackerCLI", "PackerCLI\PackerCLI.csproj", "{DC80F378-C6D7-41F2-B97A-8A62087AC259}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NormalMapConverter", "NormalMapConverter\NormalMapConverter.csproj", "{029695CD-967E-4F9E-BD72-85A087571D38}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FDE41CE6-91FB-4B01-A63B-16340BDD5ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDE41CE6-91FB-4B01-A63B-16340BDD5ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDE41CE6-91FB-4B01-A63B-16340BDD5ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDE41CE6-91FB-4B01-A63B-16340BDD5ABF}.Release|Any CPU.Build.0 = Release|Any CPU + {5DC0FC15-3CCD-41C6-8F90-4DE8041F9C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DC0FC15-3CCD-41C6-8F90-4DE8041F9C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DC0FC15-3CCD-41C6-8F90-4DE8041F9C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DC0FC15-3CCD-41C6-8F90-4DE8041F9C5E}.Release|Any CPU.Build.0 = Release|Any CPU + {DC80F378-C6D7-41F2-B97A-8A62087AC259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC80F378-C6D7-41F2-B97A-8A62087AC259}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC80F378-C6D7-41F2-B97A-8A62087AC259}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC80F378-C6D7-41F2-B97A-8A62087AC259}.Release|Any CPU.Build.0 = Release|Any CPU + {029695CD-967E-4F9E-BD72-85A087571D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {029695CD-967E-4F9E-BD72-85A087571D38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {029695CD-967E-4F9E-BD72-85A087571D38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {029695CD-967E-4F9E-BD72-85A087571D38}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {71DFA861-6B88-4D09-8AD3-3B1132AA2128} + EndGlobalSection +EndGlobal diff --git a/NormalMapConverter/App.config b/NormalMapConverter/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/NormalMapConverter/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/NormalMapConverter/DirectDrawSurfaceUtilities/DirectDrawSurface.cs b/NormalMapConverter/DirectDrawSurfaceUtilities/DirectDrawSurface.cs new file mode 100644 index 0000000..dea91ba --- /dev/null +++ b/NormalMapConverter/DirectDrawSurfaceUtilities/DirectDrawSurface.cs @@ -0,0 +1,43 @@ +using System.IO; + +namespace NormalMapConverter.DirectDrawSurfaceUtilities +{ + /// + /// Provides static method for checking validity of DirectDraw Surface files. + /// + public static class DirectDrawSurface + { + /// + /// Magic number of every file. + /// + private const int Magic = 0x20534444; + + /// + /// Size of the metadata in header for DXT1, DXT3, DXT5, and A8R8B8G8 DirectDraw Surface files. + /// + private const int MetaSize = 0x7C; + + /// + /// Size of header for DXT1, DXT3, DXT5, and A8R8B8G8 DirectDraw Surface files; this includes magic number. + /// + private const int HeaderSize = MetaSize + 4; + + /// + /// Checks if file is valid DirectDraw Surface texture file. + /// + /// Real filesystem path to file. + /// True or false if file is valid. + public static bool IsValid(string path) + { + using (var reader = new BinaryReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { + if (reader.ReadInt32() != DirectDrawSurface.Magic || reader.ReadInt32() != DirectDrawSurface.MetaSize || reader.BaseStream.Length < DirectDrawSurface.HeaderSize) + { + return false; + } + + return true; + } + } + } +} \ No newline at end of file diff --git a/NormalMapConverter/DirectDrawSurfaceUtilities/NormalMap.cs b/NormalMapConverter/DirectDrawSurfaceUtilities/NormalMap.cs new file mode 100644 index 0000000..7577dc8 --- /dev/null +++ b/NormalMapConverter/DirectDrawSurfaceUtilities/NormalMap.cs @@ -0,0 +1,90 @@ +using ImageMagick; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NormalMapConverter.DirectDrawSurfaceUtilities +{ + /// + /// Provides static method for converting PC normal maps to PS3. + /// + public static class NormalMap + { + /// + /// Converts PC Oblivion normal map to PS3 variant. + /// + /// Path to source normal map. + /// Where to save new normal map. + /// True or false if the function was able to convert. + public static bool ConvertToPS3(string path, string outputPath) + { + if (!DirectDrawSurface.IsValid(path)) + { + Console.WriteLine("ERROR: Invalid DDS texture file."); + return false; + } + + var normalMap = new MagickImage(path); + if (normalMap.IsConvertedToPS3()) + { + Console.WriteLine("ERROR: Channels already converted."); + return false; + } + + List> channelList = normalMap.Separate().ToList(); + var newChannelCollection = new MagickImageCollection(); + + // Turns blue channel null for later usage, because blue channel is never used + channelList[2].MakeNull(); + + // A8R8G8B8 and DXT3 (both support alpha) will convert to DXT5 + if (normalMap.HasAlpha) + { + newChannelCollection.Add(channelList[3]); // ALPHA + newChannelCollection.Add(channelList[1]); // GREEN + newChannelCollection.Add(channelList[2]); // NULL + newChannelCollection.Add(channelList[0]); // RED + } + else + { + newChannelCollection.Add(channelList[2]); // NULL + newChannelCollection.Add(channelList[1]); // GREEN + newChannelCollection.Add(channelList[0]); // RED + } + + var convertedImage = new MagickImage(newChannelCollection.Combine()) + { + HasAlpha = normalMap.HasAlpha, + Depth = normalMap.Depth + }; + + convertedImage.Write(outputPath, MagickFormat.Dds); + return true; + } + + /// + /// Checks if normal map is already converted to PS3 format. + /// + /// A DXT1/DXT3/DXT5/A8R8G8B8 DDS format normal map MagickImage. + /// True or false if the normal map has been converted to PS3 already. + private static bool IsConvertedToPS3(this MagickImage dds) + { + List> clonedChannelsList = new MagickImage(dds.Clone()).Separate().ToList(); + clonedChannelsList[1].MakeNull(); // Makes green channel null for upcoming comparison, because green channel is untouched + int nullCheckIndex = dds.HasAlpha ? 2 : 0; + + return clonedChannelsList[nullCheckIndex].Equals(clonedChannelsList[1]); + } + + /// + /// Makes channel null (all black). + /// + /// Channel to be nullified. + private static void MakeNull(this IMagickImage channel) + { + channel.ColorFuzz = (Percentage)100; + channel.Settings.FillColor = MagickColors.Black; + channel.Draw(new DrawableAlpha(0, 0, PaintMethod.Floodfill)); + } + } +} \ No newline at end of file diff --git a/NormalMapConverter/NormalMapConverter.csproj b/NormalMapConverter/NormalMapConverter.csproj new file mode 100644 index 0000000..f62bb4f --- /dev/null +++ b/NormalMapConverter/NormalMapConverter.csproj @@ -0,0 +1,63 @@ + + + + + Debug + AnyCPU + {029695CD-967E-4F9E-BD72-85A087571D38} + Exe + NormalMapConverter + NormalMapConverter + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Magick.NET-Q16-AnyCPU.7.21.0\lib\net40\Magick.NET-Q16-AnyCPU.dll + + + ..\packages\Magick.NET.Core.4.0.0\lib\net40\Magick.NET.Core.dll + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NormalMapConverter/Program.cs b/NormalMapConverter/Program.cs new file mode 100644 index 0000000..cdc55bd --- /dev/null +++ b/NormalMapConverter/Program.cs @@ -0,0 +1,114 @@ +using NormalMapConverter.DirectDrawSurfaceUtilities; +using System; +using System.IO; +using System.Linq; + +namespace NormalMapConverter +{ + class Program + { + static void Main(string[] args) + { + // If there are no arguments passed then it acts as if help command is ran but through an invalid usage + if (args.Count() == 0) + { + Program.ShowHelpText(true); + return; + } + + switch (args[0].ToLower()) + { + default: + { + // Allows for drag & drop of file/folders to be converted + if (args.Count() == 1) + { + if (File.Exists(args[0])) + { + string input = args[0]; + args = new string[3]; + args[0] = string.Empty; // Can equal blank string value + + args[1] = "-in"; + args[2] = input; + + goto case "-c"; + } + } + + Program.ShowHelpText(true); + return; + } + + case "-h": + case "-help": + { + Program.ShowHelpText(); + return; + } + + case "-c": + case "-convert": + { + string input = null; + string output = null; + + for (uint i = 1; i < args.Count(); i++) + { + string option = args[i]; + + switch (option) + { + default: + { + Program.ShowHelpText(true); + return; + } + + case "-in": + { + i++; + input = args[i]; + + break; + } + + case "-out": + { + i++; + output = args[i]; + + break; + } + } + } + + if (string.IsNullOrEmpty(input)) + { + Program.ShowHelpText(true); + return; + } + + Console.WriteLine("Converting " + Path.GetFileName(input) + "..."); + NormalMap.ConvertToPS3(input, string.IsNullOrEmpty(output) ? input : output); + + Console.WriteLine("\nDone!\n"); + break; + } + } + } + + private static void ShowHelpText(bool isInvalidUsage = false) + { + if (isInvalidUsage) + { + Console.WriteLine("Invalid usage\n"); + } + + Console.WriteLine("NormalMapConverter\nCopyright (c) 2020 SockNastre\nVersion: 1.0.0.0\n\n" + + "Magick.NET\nCopyright 2013-2020 Dirk Lemstra\nLicense (Apache 2.0): https://github.com/dlemstra/Magick.NET/blob/master/License.txt \n\n" + + new string('-', 50) + "\n\nUsage: NormalMapConverter.exe \n\nCommands:\n-convert (-c)\n-help (-h)\n\n" + + "Convert Options:\n-in\n-out\n\nExamples:\n\nNormalMapConverter.exe -c -in \"C:\\oblivionPC_n.dds\" -out \"C:\\oblivionPS3_n.dds\"\n\n"); + } + } +} \ No newline at end of file diff --git a/NormalMapConverter/Properties/AssemblyInfo.cs b/NormalMapConverter/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4cfbd33 --- /dev/null +++ b/NormalMapConverter/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("NormalMapConverter")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BethesdaSoftworksArchive OblivionPS3")] +[assembly: AssemblyCopyright("Copyright © 2020 SockNastre")] +[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("029695cd-967e-4f9e-bd72-85a087571d38")] + +// 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")] diff --git a/NormalMapConverter/packages.config b/NormalMapConverter/packages.config new file mode 100644 index 0000000..5452234 --- /dev/null +++ b/NormalMapConverter/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/PackerCLI/App.config b/PackerCLI/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/PackerCLI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PackerCLI/PackerCLI.csproj b/PackerCLI/PackerCLI.csproj new file mode 100644 index 0000000..6e417a2 --- /dev/null +++ b/PackerCLI/PackerCLI.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {DC80F378-C6D7-41F2-B97A-8A62087AC259} + Exe + PackerCLI + BSA OblivionPS3 Packer Cli + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {fde41ce6-91fb-4b01-a63b-16340bdd5abf} + ArchiveInterop + + + + \ No newline at end of file diff --git a/PackerCLI/Program.cs b/PackerCLI/Program.cs new file mode 100644 index 0000000..196f516 --- /dev/null +++ b/PackerCLI/Program.cs @@ -0,0 +1,167 @@ +using ArchiveInterop; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace PackerCLI +{ + class Program + { + static void Main(string[] args) + { + // If there are no arguments passed then it acts as if help command is ran + if (args.Count() == 0) + { + Program.ShowHelpText(); + return; + } + + switch (args[0].ToLower()) + { + default: + { + if (args.Count() == 1 && Directory.Exists(args[0])) + { + // If a single existing folder is inputted, the tool will autopack that + // The following codes sets up that process + + string inputDir = args[0]; + args = new string[6]; + args[0] = string.Empty; // Can equal blank string value + args[1] = "-useps3settings"; + + args[2] = "-i"; + args[3] = inputDir; + + args[4] = "-o"; + args[5] = Path.GetDirectoryName(inputDir) + "\\output.bsa"; + + goto case "-p"; + } + + Program.ShowHelpText(true); + return; + } + + case "-h": + case "-help": + { + Program.ShowHelpText(); + return; + } + + case "-p": + case "-pack": + { + string inputDir = string.Empty; + string outputPath = string.Empty; + + bool compress = false; + bool usePS3FileFlags = false; + bool extendDDS = false; + bool convertNormalMaps = false; + + for (uint i = 1; i < args.Count(); i++) + { + string option = args[i].ToLower(); + + switch (option) + { + default: + { + Program.ShowHelpText(true); + return; + } + + case "-compress": + { + compress = true; + break; + } + + case "-useps3fileflags": + { + usePS3FileFlags = true; + break; + } + + case "-extenddds": + { + extendDDS = true; + break; + } + + case "-convertnormals": + { + convertNormalMaps = true; + break; + } + + case "-useps3settings": + { + usePS3FileFlags = true; + extendDDS = true; + convertNormalMaps = true; + break; + } + + case "-i": + case "-indir": + { + i++; + inputDir = args[i]; + break; + } + + case "-o": + case "-out": + { + i++; + outputPath = args[i]; + break; + } + } + } + + if (string.IsNullOrEmpty(inputDir) || string.IsNullOrEmpty(outputPath)) + { + Program.ShowHelpText(true); + return; + } + + Console.WriteLine("Reading and verifying files..."); + var assetList = new List(); + + foreach (string file in Directory.GetFiles(inputDir, "*", SearchOption.AllDirectories)) + { + var assetFile = new Asset(file.Substring(inputDir.Length + 1), file); + assetList.Add(assetFile); + } + + Console.WriteLine("Packing..."); + BSA.Write(outputPath, assetList, compress, usePS3FileFlags, extendDDS, convertNormalMaps); + + Console.WriteLine("\nDone!\n"); + break; + } + } + } + + private static void ShowHelpText(bool isInvalidUsage = false) + { + if (isInvalidUsage) + { + Console.WriteLine("Invalid usage\n"); + } + + Console.WriteLine("BethesdaSoftworksArchive OblivionPS3 Packer Cli\nCopyright (c) 2020 SockNastre\nVersion: 1.0.0.0\n\n" + + ".NET Zlib Implementation\nLink: https://www.codeproject.com/Tips/830793/NET-ZLib-Implementation \nLicense (CPOL): https://www.codeproject.com/info/cpol10.aspx \n\n" + + "Magick.NET\nCopyright 2013-2020 Dirk Lemstra\nLicense (Apache 2.0): https://github.com/dlemstra/Magick.NET/blob/master/License.txt \n\n" + new string('-', 50) + + "\n\nUsage: \"BSA OblivionPS3 Packer Cli.exe\" \n\nCommands:\n-pack (-p)\n-help (-h)\n\n" + + "Pack Options:\n-useps3settings\n-useps3fileflags (PS3)\n-extenddds (PS3)\n-convertnormals (PS3)\n-compress\n-indir (-i)\n-out (-o)\n\n" + + "Examples:\n\n\"BSA OblivionPS3 Packer Cli.exe\" -pack -useps3settings -compress -i \"C:\\Data\" -o \"C:\\ps3output.bsa\"\n" + + new string(' ', 33) + "-pack -compress -i \"C:\\Data\" -o \"C:\\output.bsa\"\n\n"); + } + } +} \ No newline at end of file diff --git a/PackerCLI/Properties/AssemblyInfo.cs b/PackerCLI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fb499c5 --- /dev/null +++ b/PackerCLI/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("BethesdaSoftworksArchive OblivionPS3 Packer Cli")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BethesdaSoftworksArchive OblivionPS3")] +[assembly: AssemblyCopyright("Copyright © 2020 SockNastre")] +[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("dc80f378-c6d7-41f2-b97a-8a62087ac259")] + +// 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")] diff --git a/PackerGUI/About.Designer.cs b/PackerGUI/About.Designer.cs new file mode 100644 index 0000000..257711a --- /dev/null +++ b/PackerGUI/About.Designer.cs @@ -0,0 +1,108 @@ +namespace PackerGUI +{ + partial class About + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(About)); + this.richTextBoxCredits = new System.Windows.Forms.RichTextBox(); + this.labelAboutInformation = new System.Windows.Forms.Label(); + this.pictureBoxApplicationIcon = new System.Windows.Forms.PictureBox(); + this.buttonOkay = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxApplicationIcon)).BeginInit(); + this.SuspendLayout(); + // + // richTextBoxCredits + // + this.richTextBoxCredits.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.richTextBoxCredits.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.richTextBoxCredits.Location = new System.Drawing.Point(11, 93); + this.richTextBoxCredits.Name = "richTextBoxCredits"; + this.richTextBoxCredits.ReadOnly = true; + this.richTextBoxCredits.Size = new System.Drawing.Size(400, 228); + this.richTextBoxCredits.TabIndex = 35; + this.richTextBoxCredits.Text = resources.GetString("richTextBoxCredits.Text"); + // + // labelAboutInformation + // + this.labelAboutInformation.AutoSize = true; + this.labelAboutInformation.Location = new System.Drawing.Point(81, 12); + this.labelAboutInformation.Name = "labelAboutInformation"; + this.labelAboutInformation.Size = new System.Drawing.Size(233, 39); + this.labelAboutInformation.TabIndex = 34; + this.labelAboutInformation.Text = "BethesdaSoftworksArchive OblivionPS3 Packer\r\nVersion: 1.0.0.0\r\nCopyright© 2020 " + + "SockNastre"; + // + // pictureBoxApplicationIcon + // + this.pictureBoxApplicationIcon.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; + this.pictureBoxApplicationIcon.Location = new System.Drawing.Point(11, 12); + this.pictureBoxApplicationIcon.Name = "pictureBoxApplicationIcon"; + this.pictureBoxApplicationIcon.Size = new System.Drawing.Size(64, 68); + this.pictureBoxApplicationIcon.TabIndex = 33; + this.pictureBoxApplicationIcon.TabStop = false; + // + // buttonOkay + // + this.buttonOkay.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonOkay.Location = new System.Drawing.Point(337, 327); + this.buttonOkay.Name = "buttonOkay"; + this.buttonOkay.Size = new System.Drawing.Size(75, 23); + this.buttonOkay.TabIndex = 32; + this.buttonOkay.Text = "OK"; + this.buttonOkay.UseVisualStyleBackColor = true; + this.buttonOkay.Click += new System.EventHandler(this.buttonOkay_Click); + // + // About + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(423, 358); + this.Controls.Add(this.richTextBoxCredits); + this.Controls.Add(this.labelAboutInformation); + this.Controls.Add(this.pictureBoxApplicationIcon); + this.Controls.Add(this.buttonOkay); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "About"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "About BethesdaSoftworksArchive OblivionPS3 Packer"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxApplicationIcon)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.RichTextBox richTextBoxCredits; + private System.Windows.Forms.Label labelAboutInformation; + private System.Windows.Forms.PictureBox pictureBoxApplicationIcon; + private System.Windows.Forms.Button buttonOkay; + } +} \ No newline at end of file diff --git a/PackerGUI/About.cs b/PackerGUI/About.cs new file mode 100644 index 0000000..037d0ce --- /dev/null +++ b/PackerGUI/About.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows.Forms; + +namespace PackerGUI +{ + public partial class About : Form + { + public About() + { + InitializeComponent(); + } + + private void buttonOkay_Click(object sender, EventArgs e) + { + this.Close(); + } + } +} \ No newline at end of file diff --git a/PackerGUI/About.resx b/PackerGUI/About.resx new file mode 100644 index 0000000..7603535 --- /dev/null +++ b/PackerGUI/About.resx @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + [Resources] + +Project: .NET Zlib Implementation +Link: https://www.codeproject.com/Tips/830793/NET-ZLib-Implementation +License (CPOL): https://www.codeproject.com/info/cpol10.aspx + +Project: Magick.NET +Copyright 2013-2020 Dirk Lemstra +License (Apache 2.0): https://github.com/dlemstra/Magick.NET/blob/master/License.txt + +[Thanks] + +AlexxEG for helping me understand buffers. + +Spawnkiller for helping me understand how games handle textures and helping me with mesh-related work. + +Josip Medved for the OpenFolderDialog. + + + \ No newline at end of file diff --git a/PackerGUI/App.config b/PackerGUI/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/PackerGUI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PackerGUI/Classes/Ini.cs b/PackerGUI/Classes/Ini.cs new file mode 100644 index 0000000..c4e34c5 --- /dev/null +++ b/PackerGUI/Classes/Ini.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace PackerGUI.Classes +{ + /* + * Source: IniLib\Ini.cs + * By: SockNastre + * + * A prerelease Ini reading/writing class taken from IniLib for the purpose + * of this tool. + */ + + public class Ini + { + public Dictionary> Data = new Dictionary>(); + + public Ini() { } + public Ini(string path) + { + this.ReadFromFile(path); + } + + public void ReadFromFile(string path) + { + string[] lines = File.ReadAllLines(path); + + for (int i = 0; i < lines.Count(); i++) + { + string line = lines[i]; + + // Blank lines are ignored + if (!string.IsNullOrEmpty(line)) + { + // Lines starting with ';'(comments) are ignored and '[' is an identifier for a section + if (!line[0].Equals(';') && line[0].Equals('[')) + { + Dictionary sectionData = ReadSectionData(lines.Skip(i + 1).ToArray()); + Data.Add(new string(line.Skip(1).Take(line.Count() - 2).ToArray()), sectionData); + } + } + } + } + + public void WriteToFile(string path) + { + var iniSb = new StringBuilder(); + + foreach (KeyValuePair> sectionPropertyDictPair in Data) + { + iniSb.AppendLine('[' + sectionPropertyDictPair.Key + ']'); + + // Reading keys + foreach (KeyValuePair propertyPair in sectionPropertyDictPair.Value) + { + iniSb.AppendLine(propertyPair.Key + '=' + propertyPair.Value); + } + + // Adds blank line to end of section + iniSb.AppendLine(); + } + + File.WriteAllText(path, iniSb.ToString()); + } + + private Dictionary ReadSectionData(string[] remainingLines) + { + var propertyDict = new Dictionary(); + + for (int i = 0; i < remainingLines.Count(); i++) + { + string line = remainingLines[i]; + + // Blank lines are ignored + if (!string.IsNullOrEmpty(line)) + { + // '[' is an identifier for a section + if (line[0].Equals('[')) + break; + + // Lines starting with ';'(comments) are ignored + if (!line[0].Equals(';')) + { + this.AddPropertyPair(propertyDict, line); + } + } + } + + return propertyDict; + } + + private void AddPropertyPair(Dictionary propertyDict, string line) + { + var keySb = new StringBuilder(); + var valSb = new StringBuilder(); + + bool isKeyRead = false; // Indicates to start reading value + + for (int i = 0; i < line.Count(); i++) + { + char c = line[i]; + + if (c.Equals('=')) + { + isKeyRead = true; + } + else + { + if (isKeyRead) + { + // Goes onto reading value + valSb.Append(c); + } + else + { + // Keeps reading key + keySb.Append(c); + } + } + } + + propertyDict.Add(keySb.ToString(), valSb.ToString()); + } + } +} \ No newline at end of file diff --git a/PackerGUI/Classes/ListViewUtils.cs b/PackerGUI/Classes/ListViewUtils.cs new file mode 100644 index 0000000..e4de114 --- /dev/null +++ b/PackerGUI/Classes/ListViewUtils.cs @@ -0,0 +1,106 @@ +using ArchiveInterop; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PackerGUI.Classes +{ + /// + /// From Stack Overflow thread. + /// Provides static methods, structs, and fields for enhancing entire list selection capability in a ListView. + /// + public static class ListViewSelectionUtils + { + private const int LVM_FIRST = 0x1000; + private const int LVM_SETITEMSTATE = LVM_FIRST + 43; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct LVITEM + { + public int mask; + public int iItem; + public int iSubItem; + public int state; + public int stateMask; + [MarshalAs(UnmanagedType.LPTStr)] + public string pszText; + public int cchTextMax; + public int iImage; + public IntPtr lParam; + public int iIndent; + public int iGroupId; + public int cColumns; + public IntPtr puColumns; + }; + + [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] + public static extern IntPtr SendMessageLVItem(IntPtr hWnd, int msg, int wParam, ref LVITEM lvi); + + /// + /// Select all rows in the given ListView. + /// + /// The ListView whose items are to be selected. + public static void SelectAll(this ListView list) + { + ListViewSelectionUtils.SetItemState(list, -1, 2, 2); + } + + /// + /// Deselect all rows in the given ListView. + /// + /// The ListView whose items are to be deselected. + public static void DeselectAll(this ListView list) + { + ListViewSelectionUtils.SetItemState(list, -1, 2, 0); + } + + /// + /// Set the item state on the given item. + /// + /// The ListView whose item's state is to be changed. + /// The index of the item to be changed. + /// Which bits of the value are to be set. + /// The value to be set. + public static void SetItemState(ListView list, int itemIndex, int mask, int value) + { + var lvItem = new LVITEM + { + stateMask = mask, + state = value + }; + + ListViewSelectionUtils.SendMessageLVItem(list.Handle, LVM_SETITEMSTATE, itemIndex, ref lvItem); + } + } + + /// + /// Provided static method for utilizing a ListView with the BSA packing process. + /// + public static class ListViewExtensions + { + /// + /// Converts items from to a list of . + /// + /// ListView to be converted. + /// List generated from ListView. + public static List ConvertItemsToAssetList(this ListView listView) + { + ListView.ListViewItemCollection itemCollection = listView.Items; + var assetList = new List(); + + foreach (ListViewItem item in itemCollection) + { + var asset = new Asset(item.Text.ToLower(), item.SubItems[1].Text); + + if (File.Exists(asset.RealPath)) + { + assetList.Add(asset); + } + } + + return assetList; + } + } +} \ No newline at end of file diff --git a/PackerGUI/Classes/OpenFolderDialog.cs b/PackerGUI/Classes/OpenFolderDialog.cs new file mode 100644 index 0000000..ead980f --- /dev/null +++ b/PackerGUI/Classes/OpenFolderDialog.cs @@ -0,0 +1,244 @@ +// Copyright (c) 2011 Josip Medved http://www.jmedved.com + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +internal class OpenFolderDialog : IDisposable +{ + /// + /// Gets/sets folder in which dialog will be open. + /// + public string InitialFolder { get; set; } + + /// + /// Gets/sets directory in which dialog will be open if there is no recent directory available. + /// + public string DefaultFolder { get; set; } + + /// + /// Gets selected folder. + /// + public string Folder { get; private set; } + + internal DialogResult ShowDialog(IWin32Window owner) + { + if (Environment.OSVersion.Version.Major >= 6) + { + return ShowVistaDialog(owner); + } + else + { + return ShowLegacyDialog(owner); + } + } + + private DialogResult ShowVistaDialog(IWin32Window owner) + { + var frm = (NativeMethods.IFileDialog)(new NativeMethods.FileOpenDialogRCW()); + uint options; + frm.GetOptions(out options); + options |= NativeMethods.FOS_PICKFOLDERS | NativeMethods.FOS_FORCEFILESYSTEM | NativeMethods.FOS_NOVALIDATE | NativeMethods.FOS_NOTESTFILECREATE | NativeMethods.FOS_DONTADDTORECENT; + frm.SetOptions(options); + if (this.InitialFolder != null) + { + NativeMethods.IShellItem directoryShellItem; + var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + if (NativeMethods.SHCreateItemFromParsingName(this.InitialFolder, IntPtr.Zero, ref riid, out directoryShellItem) == NativeMethods.S_OK) + { + frm.SetFolder(directoryShellItem); + } + } + if (this.DefaultFolder != null) + { + NativeMethods.IShellItem directoryShellItem; + var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + if (NativeMethods.SHCreateItemFromParsingName(this.DefaultFolder, IntPtr.Zero, ref riid, out directoryShellItem) == NativeMethods.S_OK) + { + frm.SetDefaultFolder(directoryShellItem); + } + } + + if (frm.Show(owner.Handle) == NativeMethods.S_OK) + { + NativeMethods.IShellItem shellItem; + if (frm.GetResult(out shellItem) == NativeMethods.S_OK) + { + IntPtr pszString; + if (shellItem.GetDisplayName(NativeMethods.SIGDN_FILESYSPATH, out pszString) == NativeMethods.S_OK) + { + if (pszString != IntPtr.Zero) + { + try + { + this.Folder = Marshal.PtrToStringAuto(pszString); + return DialogResult.OK; + } + finally + { + Marshal.FreeCoTaskMem(pszString); + } + } + } + } + } + return DialogResult.Cancel; + } + + private DialogResult ShowLegacyDialog(IWin32Window owner) + { + using (var frm = new SaveFileDialog()) + { + frm.CheckFileExists = false; + frm.CheckPathExists = true; + frm.CreatePrompt = false; + frm.Filter = "|" + Guid.Empty.ToString(); + frm.FileName = "any"; + if (this.InitialFolder != null) { frm.InitialDirectory = this.InitialFolder; } + frm.OverwritePrompt = false; + frm.Title = "Select Folder"; + frm.ValidateNames = false; + if (frm.ShowDialog(owner) == DialogResult.OK) + { + this.Folder = Path.GetDirectoryName(frm.FileName); + return DialogResult.OK; + } + else + { + return DialogResult.Cancel; + } + } + } + + public void Dispose() { } //just to have possibility of Using statement. +} + +internal static class NativeMethods +{ + + #region Constants + + public const uint FOS_PICKFOLDERS = 0x00000020; + public const uint FOS_FORCEFILESYSTEM = 0x00000040; + public const uint FOS_NOVALIDATE = 0x00000100; + public const uint FOS_NOTESTFILECREATE = 0x00010000; + public const uint FOS_DONTADDTORECENT = 0x02000000; + + public const uint S_OK = 0x0000; + + public const uint SIGDN_FILESYSPATH = 0x80058000; + + #endregion + + #region COM + + [ComImport, ClassInterface(ClassInterfaceType.None), TypeLibType(TypeLibTypeFlags.FCanCreate), Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")] + internal class FileOpenDialogRCW { } + + + [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IFileDialog + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig()] + uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow + + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypeIndex([In] uint iFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileTypeIndex(out uint piFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Unadvise([In] uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOptions([In] uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetOptions(out uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Close([MarshalAs(UnmanagedType.Error)] uint hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint ClearClientData(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } + + + [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItem + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } + + #endregion + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); + +} \ No newline at end of file diff --git a/PackerGUI/MainForm.Designer.cs b/PackerGUI/MainForm.Designer.cs new file mode 100644 index 0000000..c77f4a0 --- /dev/null +++ b/PackerGUI/MainForm.Designer.cs @@ -0,0 +1,303 @@ +namespace PackerGUI +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.menuStripMain = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.archiveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.addFilesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.addFolderToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.removeFilesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.selectAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.listViewAssets = new System.Windows.Forms.ListView(); + this.columnHeaderFileEntryString = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeaderInternalFilePath = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.openFileDialogAsset = new System.Windows.Forms.OpenFileDialog(); + this.saveFileDialogBSA = new System.Windows.Forms.SaveFileDialog(); + this.menuStripMain.SuspendLayout(); + this.SuspendLayout(); + // + // menuStripMain + // + this.menuStripMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem, + this.archiveToolStripMenuItem, + this.helpToolStripMenuItem}); + this.menuStripMain.Location = new System.Drawing.Point(0, 0); + this.menuStripMain.Name = "menuStripMain"; + this.menuStripMain.Size = new System.Drawing.Size(499, 24); + this.menuStripMain.TabIndex = 7; + this.menuStripMain.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.newToolStripMenuItem, + this.openToolStripMenuItem, + this.toolStripSeparator1, + this.saveToolStripMenuItem, + this.saveAsToolStripMenuItem, + this.toolStripSeparator2, + this.exitToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); + this.fileToolStripMenuItem.Text = "&File"; + // + // newToolStripMenuItem + // + this.newToolStripMenuItem.Name = "newToolStripMenuItem"; + this.newToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N))); + this.newToolStripMenuItem.Size = new System.Drawing.Size(235, 22); + this.newToolStripMenuItem.Text = "&New"; + this.newToolStripMenuItem.Click += new System.EventHandler(this.newToolStripMenuItem_Click); + // + // openToolStripMenuItem + // + this.openToolStripMenuItem.Enabled = false; + this.openToolStripMenuItem.Name = "openToolStripMenuItem"; + this.openToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); + this.openToolStripMenuItem.Size = new System.Drawing.Size(235, 22); + this.openToolStripMenuItem.Text = "&Open (Not Supported)"; + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(232, 6); + // + // saveToolStripMenuItem + // + this.saveToolStripMenuItem.Enabled = false; + this.saveToolStripMenuItem.Name = "saveToolStripMenuItem"; + this.saveToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); + this.saveToolStripMenuItem.Size = new System.Drawing.Size(235, 22); + this.saveToolStripMenuItem.Text = "&Save"; + this.saveToolStripMenuItem.Click += new System.EventHandler(this.saveToolStripMenuItem_Click); + // + // saveAsToolStripMenuItem + // + this.saveAsToolStripMenuItem.Enabled = false; + this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; + this.saveAsToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.S))); + this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(235, 22); + this.saveAsToolStripMenuItem.Text = "Save &As..."; + this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(232, 6); + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.F4))); + this.exitToolStripMenuItem.Size = new System.Drawing.Size(235, 22); + this.exitToolStripMenuItem.Text = "&Exit"; + this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); + // + // archiveToolStripMenuItem + // + this.archiveToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.addFilesToolStripMenuItem, + this.addFolderToolStripMenuItem, + this.toolStripSeparator3, + this.removeFilesToolStripMenuItem, + this.selectAllToolStripMenuItem, + this.toolStripSeparator4, + this.settingsToolStripMenuItem}); + this.archiveToolStripMenuItem.Name = "archiveToolStripMenuItem"; + this.archiveToolStripMenuItem.Size = new System.Drawing.Size(59, 20); + this.archiveToolStripMenuItem.Text = "&Archive"; + // + // addFilesToolStripMenuItem + // + this.addFilesToolStripMenuItem.Name = "addFilesToolStripMenuItem"; + this.addFilesToolStripMenuItem.Size = new System.Drawing.Size(175, 22); + this.addFilesToolStripMenuItem.Text = "Add &File(s)"; + this.addFilesToolStripMenuItem.Click += new System.EventHandler(this.addFilesToolStripMenuItem_Click); + // + // addFolderToolStripMenuItem + // + this.addFolderToolStripMenuItem.Name = "addFolderToolStripMenuItem"; + this.addFolderToolStripMenuItem.Size = new System.Drawing.Size(175, 22); + this.addFolderToolStripMenuItem.Text = "Add F&older"; + this.addFolderToolStripMenuItem.Click += new System.EventHandler(this.addFolderToolStripMenuItem_Click); + // + // toolStripSeparator3 + // + this.toolStripSeparator3.Name = "toolStripSeparator3"; + this.toolStripSeparator3.Size = new System.Drawing.Size(172, 6); + // + // removeFilesToolStripMenuItem + // + this.removeFilesToolStripMenuItem.Enabled = false; + this.removeFilesToolStripMenuItem.Name = "removeFilesToolStripMenuItem"; + this.removeFilesToolStripMenuItem.ShortcutKeyDisplayString = ""; + this.removeFilesToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Delete; + this.removeFilesToolStripMenuItem.Size = new System.Drawing.Size(175, 22); + this.removeFilesToolStripMenuItem.Text = "&Remove File(s)"; + this.removeFilesToolStripMenuItem.Click += new System.EventHandler(this.removeFilesToolStripMenuItem_Click); + // + // selectAllToolStripMenuItem + // + this.selectAllToolStripMenuItem.Name = "selectAllToolStripMenuItem"; + this.selectAllToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.A))); + this.selectAllToolStripMenuItem.Size = new System.Drawing.Size(175, 22); + this.selectAllToolStripMenuItem.Text = "Select A&ll"; + this.selectAllToolStripMenuItem.Click += new System.EventHandler(this.selectAllToolStripMenuItem_Click); + // + // toolStripSeparator4 + // + this.toolStripSeparator4.Name = "toolStripSeparator4"; + this.toolStripSeparator4.Size = new System.Drawing.Size(172, 6); + // + // settingsToolStripMenuItem + // + this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; + this.settingsToolStripMenuItem.Size = new System.Drawing.Size(175, 22); + this.settingsToolStripMenuItem.Text = "&Settings"; + this.settingsToolStripMenuItem.Click += new System.EventHandler(this.settingsToolStripMenuItem_Click); + // + // helpToolStripMenuItem + // + this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.aboutToolStripMenuItem}); + this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; + this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20); + this.helpToolStripMenuItem.Text = "&Help"; + // + // aboutToolStripMenuItem + // + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(355, 22); + this.aboutToolStripMenuItem.Text = "&About BethesdaSoftworksArchive OblivionPS3 Packer"; + this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); + // + // listViewAssets + // + this.listViewAssets.AllowDrop = true; + this.listViewAssets.BackColor = System.Drawing.Color.White; + this.listViewAssets.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.listViewAssets.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeaderFileEntryString, + this.columnHeaderInternalFilePath}); + this.listViewAssets.Dock = System.Windows.Forms.DockStyle.Fill; + this.listViewAssets.FullRowSelect = true; + this.listViewAssets.HideSelection = false; + this.listViewAssets.Location = new System.Drawing.Point(0, 24); + this.listViewAssets.Name = "listViewAssets"; + this.listViewAssets.Size = new System.Drawing.Size(499, 277); + this.listViewAssets.TabIndex = 8; + this.listViewAssets.UseCompatibleStateImageBehavior = false; + this.listViewAssets.View = System.Windows.Forms.View.Details; + this.listViewAssets.SelectedIndexChanged += new System.EventHandler(this.listViewAssets_SelectedIndexChanged); + this.listViewAssets.DragDrop += new System.Windows.Forms.DragEventHandler(this.listViewAssets_DragDrop); + this.listViewAssets.DragEnter += new System.Windows.Forms.DragEventHandler(this.listViewAssets_DragEnter); + // + // columnHeaderFileEntryString + // + this.columnHeaderFileEntryString.Text = "File Entry String"; + this.columnHeaderFileEntryString.Width = 205; + // + // columnHeaderInternalFilePath + // + this.columnHeaderInternalFilePath.Text = "File Disk Path"; + this.columnHeaderInternalFilePath.Width = 250; + // + // openFileDialogAsset + // + this.openFileDialogAsset.Filter = "All Files (*.*)|*.*"; + this.openFileDialogAsset.Multiselect = true; + this.openFileDialogAsset.Title = "Select GNF File to Import"; + // + // saveFileDialogBSA + // + this.saveFileDialogBSA.Filter = "BethesdaSoftworksArchive File (*.bsa)|*.bsa|All Files (*.*)|*.*"; + this.saveFileDialogBSA.Title = "Select Where to Save"; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(499, 301); + this.Controls.Add(this.listViewAssets); + this.Controls.Add(this.menuStripMain); + this.Name = "MainForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "BethesdaSoftworksArchive OblivionPS3 Packer"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); + this.menuStripMain.ResumeLayout(false); + this.menuStripMain.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip menuStripMain; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + public System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem; + public System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem archiveToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem addFilesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem addFolderToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + public System.Windows.Forms.ToolStripMenuItem removeFilesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem selectAllToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + public System.Windows.Forms.ListView listViewAssets; + private System.Windows.Forms.ColumnHeader columnHeaderFileEntryString; + private System.Windows.Forms.ColumnHeader columnHeaderInternalFilePath; + public System.Windows.Forms.OpenFileDialog openFileDialogAsset; + private System.Windows.Forms.SaveFileDialog saveFileDialogBSA; + } +} + diff --git a/PackerGUI/MainForm.cs b/PackerGUI/MainForm.cs new file mode 100644 index 0000000..f26bee5 --- /dev/null +++ b/PackerGUI/MainForm.cs @@ -0,0 +1,302 @@ +using ArchiveInterop; +using PackerGUI.Classes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace PackerGUI +{ + public partial class MainForm : Form + { + private Ini SettingsIni; + private string ArchiveSavePath; + private bool IsArchiveSaved = true; + private bool IsPackingCurrently = false; + + public MainForm() + { + InitializeComponent(); + + string iniPath = AppDomain.CurrentDomain.BaseDirectory + "\\BSA OblivionPS3 Packer.ini"; + Ini settingsIni; + + // All code following is for managing the ini configuration file used by the tool + if (File.Exists(iniPath)) + { + settingsIni = new Ini(iniPath); + + if (this.IsIniValid(settingsIni)) + { + this.SettingsIni = settingsIni; + return; + } + else + { + File.Delete(iniPath); + } + } + + // When all else fails, code comes here and just remakes the ini file + settingsIni = new Ini(); + settingsIni.Data.Add("Archive", new Dictionary { { "CompressAssets", "False" }, { "UsePS3FileFlags", "True" } }); + settingsIni.Data.Add("DDS", new Dictionary { { "ExtendData", "True" }, { "ConvertNormalMaps", "True" } }); + settingsIni.WriteToFile(iniPath); + + this.SettingsIni = settingsIni; + } + + private void MainForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (IsPackingCurrently) + { + DialogResult abortMessage = MessageBox.Show("Archive not finished packing, are you sure you want to exit?", + string.Empty, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); + + e.Cancel = abortMessage == DialogResult.No; + } + else if (!IsArchiveSaved) + { + DialogResult exitMessage = MessageBox.Show("Archive not saved, are you sure you want to exit?", + string.Empty, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); + + e.Cancel = exitMessage == DialogResult.No; + } + + // Saves ini file + string iniPath = AppDomain.CurrentDomain.BaseDirectory + "\\BSA OblivionPS3 Packer.ini"; + this.SettingsIni.WriteToFile(iniPath); + } + + private void newToolStripMenuItem_Click(object sender, EventArgs e) + { + this.ResetForm(); + } + + private void saveToolStripMenuItem_Click(object sender, EventArgs e) + { + List assetList = listViewAssets.ConvertItemsToAssetList(); + this.PackBSA(this.ArchiveSavePath, assetList); + } + + private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) + { + if (saveFileDialogBSA.ShowDialog() == DialogResult.OK) + { + this.ArchiveSavePath = saveFileDialogBSA.FileName; + saveToolStripMenuItem.Enabled = true; + + saveToolStripMenuItem.PerformClick(); + } + } + + private void exitToolStripMenuItem_Click(object sender, EventArgs e) + { + this.Close(); + } + + private void addFilesToolStripMenuItem_Click(object sender, EventArgs e) + { + if (openFileDialogAsset.ShowDialog() == DialogResult.OK) + { + listViewAssets.BeginUpdate(); + this.AddAssets(openFileDialogAsset.FileNames); + + listViewAssets.EndUpdate(); + this.SetSaveButtonsEnableState(); + } + } + + private void addFolderToolStripMenuItem_Click(object sender, EventArgs e) + { + var folderDialog = new OpenFolderDialog(); + + if (folderDialog.ShowDialog(this) == DialogResult.OK) + { + listViewAssets.BeginUpdate(); + this.AddDirectoryAssets(folderDialog.Folder); + + listViewAssets.EndUpdate(); + this.SetSaveButtonsEnableState(); + } + } + + private void removeFilesToolStripMenuItem_Click(object sender, EventArgs e) + { + if (IsPackingCurrently) + return; + + foreach (ListViewItem item in listViewAssets.SelectedItems) + { + listViewAssets.Items.RemoveAt(item.Index); + } + + this.SetSaveButtonsEnableState(); + } + + private void selectAllToolStripMenuItem_Click(object sender, EventArgs e) + { + listViewAssets.SelectAll(); + } + + private void settingsToolStripMenuItem_Click(object sender, EventArgs e) + { + var settingsForm = new Settings(this.SettingsIni); + settingsForm.ShowDialog(); + } + + private void aboutToolStripMenuItem_Click(object sender, EventArgs e) + { + var aboutForm = new About(); + aboutForm.ShowDialog(); + } + + private void listViewAssets_DragDrop(object sender, DragEventArgs e) + { + if (IsPackingCurrently) + return; + + listViewAssets.BeginUpdate(); + + string dir = ((string[])e.Data.GetData(DataFormats.FileDrop))[0]; + if (Directory.Exists(dir)) + { + this.AddDirectoryAssets(dir); + } + else + { + var assets = (string[])e.Data.GetData(DataFormats.FileDrop); + this.AddAssets(assets); + } + + listViewAssets.EndUpdate(); + this.SetSaveButtonsEnableState(); + } + + private void listViewAssets_DragEnter(object sender, DragEventArgs e) + { + e.Effect = IsPackingCurrently ? DragDropEffects.None : DragDropEffects.Copy; + } + + private void listViewAssets_SelectedIndexChanged(object sender, EventArgs e) + { + removeFilesToolStripMenuItem.Enabled = listViewAssets.SelectedItems.Count > 0 ? true : false; + } + + private bool IsIniValid(Ini ini) + { + try + { + string val = ini.Data["Archive"]["CompressAssets"]; + bool.Parse(val); + + val = ini.Data["Archive"]["UsePS3FileFlags"]; + bool.Parse(val); + + val = ini.Data["DDS"]["ExtendData"]; + bool.Parse(val); + + val = ini.Data["DDS"]["ConvertNormalMaps"]; + bool.Parse(val); + + // If the above code works without exceptions, the ini is valid for this tool + return true; + } + catch + { + return false; + } + } + + private void ResetForm() + { + this.ArchiveSavePath = string.Empty; + this.IsArchiveSaved = true; + saveToolStripMenuItem.Enabled = false; + saveAsToolStripMenuItem.Enabled = false; + removeFilesToolStripMenuItem.Enabled = false; + listViewAssets.Items.Clear(); + } + + private void SetSaveButtonsEnableState() + { + if (listViewAssets.Items.Count > 0) + { + saveToolStripMenuItem.Enabled = !string.IsNullOrEmpty(this.ArchiveSavePath); + saveAsToolStripMenuItem.Enabled = true; + } + else + { + saveToolStripMenuItem.Enabled = false; + saveAsToolStripMenuItem.Enabled = false; + IsArchiveSaved = true; + } + } + + private void AddAsset(string path, string entryStr) + { + if (listViewAssets.FindItemWithText(entryStr) != null) + return; + + var item = new ListViewItem + { + Text = entryStr + }; + + item.SubItems.Add(path); + listViewAssets.Items.Add(item); + } + + private void AddAssets(string[] assetPaths) + { + int initialItemCount = listViewAssets.Items.Count; + bool initialArchiveSavedState = this.IsArchiveSaved; + + foreach (string asset in assetPaths) + { + this.AddAsset(asset, Path.GetFileName(asset)); + } + + // If the ListView Item count is the same, nothing was added so save state is the same + this.IsArchiveSaved = initialItemCount == listViewAssets.Items.Count ? initialArchiveSavedState : false; + } + + private void AddDirectoryAssets(string dir) + { + int initialItemCount = listViewAssets.Items.Count; + bool initialArchiveSavedState = this.IsArchiveSaved; + + int subLength = dir.Length + 1; // +1 accounts for an extra backslash + + foreach (string file in Directory.GetFiles(dir, "*", SearchOption.AllDirectories)) + { + this.AddAsset(file, file.Substring(subLength)); + } + + // If the ListView Item count is the same, nothing was added so save state is the same + this.IsArchiveSaved = initialItemCount == listViewAssets.Items.Count ? initialArchiveSavedState : false; + } + + private async void PackBSA(string path, List assetList) + { + string formText = this.Text; + this.Text = "Packing..."; + this.IsPackingCurrently = true; + menuStripMain.Enabled = false; + + bool compress = bool.Parse(SettingsIni.Data["Archive"]["CompressAssets"]); + bool usePS3FileFlags = bool.Parse(SettingsIni.Data["Archive"]["UsePS3FileFlags"]); + bool extendDDS = bool.Parse(SettingsIni.Data["DDS"]["ExtendData"]); + bool convertNormalMaps = bool.Parse(SettingsIni.Data["DDS"]["ConvertNormalMaps"]); + + await Task.Run(() => BSA.Write(path, assetList, compress, usePS3FileFlags, extendDDS, convertNormalMaps)); + + this.IsArchiveSaved = true; + this.IsPackingCurrently = false; + menuStripMain.Enabled = true; + this.Text = formText; + MessageBox.Show("Done!", string.Empty, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + } + } +} \ No newline at end of file diff --git a/PackerGUI/MainForm.resx b/PackerGUI/MainForm.resx new file mode 100644 index 0000000..896d507 --- /dev/null +++ b/PackerGUI/MainForm.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 153, 17 + + + 310, 17 + + \ No newline at end of file diff --git a/PackerGUI/PackerGUI.csproj b/PackerGUI/PackerGUI.csproj new file mode 100644 index 0000000..3f2ef19 --- /dev/null +++ b/PackerGUI/PackerGUI.csproj @@ -0,0 +1,110 @@ + + + + + Debug + AnyCPU + {5DC0FC15-3CCD-41C6-8F90-4DE8041F9C5E} + WinExe + PackerGUI + BSA OblivionPS3 Packer + v4.8 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + About.cs + + + + + + Form + + + MainForm.cs + + + + + Form + + + Settings.cs + + + About.cs + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + Settings.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {fde41ce6-91fb-4b01-a63b-16340bdd5abf} + ArchiveInterop + + + + \ No newline at end of file diff --git a/PackerGUI/Program.cs b/PackerGUI/Program.cs new file mode 100644 index 0000000..f2399f1 --- /dev/null +++ b/PackerGUI/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace PackerGUI +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/PackerGUI/Properties/AssemblyInfo.cs b/PackerGUI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..67dc4c8 --- /dev/null +++ b/PackerGUI/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("BethesdaSoftworksArchive OblivionPS3 Packer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BethesdaSoftworksArchive OblivionPS3")] +[assembly: AssemblyCopyright("Copyright © 2020 SockNastre")] +[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("5dc0fc15-3ccd-41c6-8f90-4de8041f9c5e")] + +// 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")] diff --git a/PackerGUI/Properties/Resources.Designer.cs b/PackerGUI/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a5cf46b --- /dev/null +++ b/PackerGUI/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PackerGUI.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PackerGUI.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/PackerGUI/Properties/Resources.resx b/PackerGUI/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/PackerGUI/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/PackerGUI/Properties/Settings.Designer.cs b/PackerGUI/Properties/Settings.Designer.cs new file mode 100644 index 0000000..b2085b0 --- /dev/null +++ b/PackerGUI/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PackerGUI.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/PackerGUI/Properties/Settings.settings b/PackerGUI/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/PackerGUI/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/PackerGUI/Settings.Designer.cs b/PackerGUI/Settings.Designer.cs new file mode 100644 index 0000000..60bd8d3 --- /dev/null +++ b/PackerGUI/Settings.Designer.cs @@ -0,0 +1,168 @@ +namespace PackerGUI +{ + partial class Settings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.groupBoxArchiveSettings = new System.Windows.Forms.GroupBox(); + this.checkBoxUsePS3FileFlags = new System.Windows.Forms.CheckBox(); + this.checkBoxCompressAssets = new System.Windows.Forms.CheckBox(); + this.buttonResetSettings = new System.Windows.Forms.Button(); + this.buttonOK = new System.Windows.Forms.Button(); + this.groupBoxDirectDrawSurfaceSettings = new System.Windows.Forms.GroupBox(); + this.checkBoxConvertNormalMaps = new System.Windows.Forms.CheckBox(); + this.checkBoxExtendDDS = new System.Windows.Forms.CheckBox(); + this.groupBoxArchiveSettings.SuspendLayout(); + this.groupBoxDirectDrawSurfaceSettings.SuspendLayout(); + this.SuspendLayout(); + // + // groupBoxArchiveSettings + // + this.groupBoxArchiveSettings.Controls.Add(this.checkBoxUsePS3FileFlags); + this.groupBoxArchiveSettings.Controls.Add(this.checkBoxCompressAssets); + this.groupBoxArchiveSettings.Location = new System.Drawing.Point(14, 10); + this.groupBoxArchiveSettings.Name = "groupBoxArchiveSettings"; + this.groupBoxArchiveSettings.Size = new System.Drawing.Size(378, 71); + this.groupBoxArchiveSettings.TabIndex = 4; + this.groupBoxArchiveSettings.TabStop = false; + this.groupBoxArchiveSettings.Text = "Archive Settings"; + // + // checkBoxUsePS3FileFlags + // + this.checkBoxUsePS3FileFlags.AutoSize = true; + this.checkBoxUsePS3FileFlags.Checked = true; + this.checkBoxUsePS3FileFlags.CheckState = System.Windows.Forms.CheckState.Checked; + this.checkBoxUsePS3FileFlags.Location = new System.Drawing.Point(10, 42); + this.checkBoxUsePS3FileFlags.Name = "checkBoxUsePS3FileFlags"; + this.checkBoxUsePS3FileFlags.Size = new System.Drawing.Size(159, 17); + this.checkBoxUsePS3FileFlags.TabIndex = 3; + this.checkBoxUsePS3FileFlags.Text = "Use PS3 file flags (0xCDCD)"; + this.checkBoxUsePS3FileFlags.UseVisualStyleBackColor = true; + // + // checkBoxCompressAssets + // + this.checkBoxCompressAssets.AutoSize = true; + this.checkBoxCompressAssets.Location = new System.Drawing.Point(10, 19); + this.checkBoxCompressAssets.Name = "checkBoxCompressAssets"; + this.checkBoxCompressAssets.Size = new System.Drawing.Size(105, 17); + this.checkBoxCompressAssets.TabIndex = 1; + this.checkBoxCompressAssets.Text = "Compress assets"; + this.checkBoxCompressAssets.UseVisualStyleBackColor = true; + // + // buttonResetSettings + // + this.buttonResetSettings.Location = new System.Drawing.Point(12, 171); + this.buttonResetSettings.Name = "buttonResetSettings"; + this.buttonResetSettings.Size = new System.Drawing.Size(105, 23); + this.buttonResetSettings.TabIndex = 6; + this.buttonResetSettings.Text = "Reset to Default"; + this.buttonResetSettings.UseVisualStyleBackColor = true; + this.buttonResetSettings.Click += new System.EventHandler(this.buttonResetSettings_Click); + // + // buttonOK + // + this.buttonOK.Location = new System.Drawing.Point(317, 171); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(75, 23); + this.buttonOK.TabIndex = 5; + this.buttonOK.Text = "OK"; + this.buttonOK.UseVisualStyleBackColor = true; + this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click); + // + // groupBoxDirectDrawSurfaceSettings + // + this.groupBoxDirectDrawSurfaceSettings.Controls.Add(this.checkBoxConvertNormalMaps); + this.groupBoxDirectDrawSurfaceSettings.Controls.Add(this.checkBoxExtendDDS); + this.groupBoxDirectDrawSurfaceSettings.Location = new System.Drawing.Point(14, 87); + this.groupBoxDirectDrawSurfaceSettings.Name = "groupBoxDirectDrawSurfaceSettings"; + this.groupBoxDirectDrawSurfaceSettings.Size = new System.Drawing.Size(378, 71); + this.groupBoxDirectDrawSurfaceSettings.TabIndex = 7; + this.groupBoxDirectDrawSurfaceSettings.TabStop = false; + this.groupBoxDirectDrawSurfaceSettings.Text = "DirectDraw Surface (.DDS) Settings"; + // + // checkBoxConvertNormalMaps + // + this.checkBoxConvertNormalMaps.AutoSize = true; + this.checkBoxConvertNormalMaps.Checked = true; + this.checkBoxConvertNormalMaps.CheckState = System.Windows.Forms.CheckState.Checked; + this.checkBoxConvertNormalMaps.Location = new System.Drawing.Point(10, 42); + this.checkBoxConvertNormalMaps.Name = "checkBoxConvertNormalMaps"; + this.checkBoxConvertNormalMaps.Size = new System.Drawing.Size(183, 17); + this.checkBoxConvertNormalMaps.TabIndex = 2; + this.checkBoxConvertNormalMaps.Text = "Convert normal maps (PC -> PS3)"; + this.checkBoxConvertNormalMaps.UseVisualStyleBackColor = true; + // + // checkBoxExtendDDS + // + this.checkBoxExtendDDS.AutoSize = true; + this.checkBoxExtendDDS.Checked = true; + this.checkBoxExtendDDS.CheckState = System.Windows.Forms.CheckState.Checked; + this.checkBoxExtendDDS.Location = new System.Drawing.Point(10, 19); + this.checkBoxExtendDDS.Name = "checkBoxExtendDDS"; + this.checkBoxExtendDDS.Size = new System.Drawing.Size(83, 17); + this.checkBoxExtendDDS.TabIndex = 1; + this.checkBoxExtendDDS.Text = "Extend data"; + this.checkBoxExtendDDS.UseVisualStyleBackColor = true; + // + // Settings + // + this.AcceptButton = this.buttonOK; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(404, 202); + this.Controls.Add(this.groupBoxDirectDrawSurfaceSettings); + this.Controls.Add(this.groupBoxArchiveSettings); + this.Controls.Add(this.buttonResetSettings); + this.Controls.Add(this.buttonOK); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.HelpButton = true; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "Settings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Settings"; + this.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(this.Settings_HelpButtonClicked); + this.groupBoxArchiveSettings.ResumeLayout(false); + this.groupBoxArchiveSettings.PerformLayout(); + this.groupBoxDirectDrawSurfaceSettings.ResumeLayout(false); + this.groupBoxDirectDrawSurfaceSettings.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox groupBoxArchiveSettings; + private System.Windows.Forms.CheckBox checkBoxCompressAssets; + private System.Windows.Forms.Button buttonResetSettings; + private System.Windows.Forms.Button buttonOK; + private System.Windows.Forms.GroupBox groupBoxDirectDrawSurfaceSettings; + private System.Windows.Forms.CheckBox checkBoxExtendDDS; + private System.Windows.Forms.CheckBox checkBoxConvertNormalMaps; + private System.Windows.Forms.CheckBox checkBoxUsePS3FileFlags; + } +} \ No newline at end of file diff --git a/PackerGUI/Settings.cs b/PackerGUI/Settings.cs new file mode 100644 index 0000000..57bfe6e --- /dev/null +++ b/PackerGUI/Settings.cs @@ -0,0 +1,52 @@ +using PackerGUI.Classes; +using System; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PackerGUI +{ + public partial class Settings : Form + { + private Ini SettingsIni; + + public Settings(Ini settingsIni) + { + InitializeComponent(); + + this.SettingsIni = settingsIni; + checkBoxCompressAssets.Checked = bool.Parse(settingsIni.Data["Archive"]["CompressAssets"]); + checkBoxUsePS3FileFlags.Checked = bool.Parse(settingsIni.Data["Archive"]["UsePS3FileFlags"]); + checkBoxExtendDDS.Checked = bool.Parse(settingsIni.Data["DDS"]["ExtendData"]); + checkBoxConvertNormalMaps.Checked = bool.Parse(settingsIni.Data["DDS"]["ConvertNormalMaps"]); + } + + private void Settings_HelpButtonClicked(object sender, CancelEventArgs e) + { + MessageBox.Show("\"Compress assets\" - Compresses assets inside of BSA using zlib, may decrease BSA size but increase loading times.\n\n" + + "\"Use PS3 file flags\" - Use PS3 file flags opposed to PC file flags, on PS3 one or two BSAs don't use PS3 file flags.\n\n" + + "\"Extend data\" - Extends data for DDS files.\n\n" + + "\"Convert normal maps\" - Converts normal maps from PC variant to PS3 variant.\n\n" + + "DISCLAIMER: The last 3 settings should be turned on when packing for PS3 except under specific circumstances! Turning those settings " + + "off may result in BSAs that don't load right on PS3.", + "Help", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + private void buttonResetSettings_Click(object sender, EventArgs e) + { + checkBoxCompressAssets.Checked = false; + checkBoxUsePS3FileFlags.Checked = true; + checkBoxExtendDDS.Checked = true; + checkBoxConvertNormalMaps.Checked = true; + } + + private void buttonOK_Click(object sender, EventArgs e) + { + this.SettingsIni.Data["Archive"]["CompressAssets"] = checkBoxCompressAssets.Checked.ToString(); + this.SettingsIni.Data["Archive"]["UsePS3FileFlags"] = checkBoxUsePS3FileFlags.Checked.ToString(); + this.SettingsIni.Data["DDS"]["ExtendData"] = checkBoxExtendDDS.Checked.ToString(); + this.SettingsIni.Data["DDS"]["ConvertNormalMaps"] = checkBoxConvertNormalMaps.Checked.ToString(); + + this.Close(); + } + } +} \ No newline at end of file diff --git a/PackerGUI/Settings.resx b/PackerGUI/Settings.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/PackerGUI/Settings.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d9e5206 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +## BethesdaSoftworksArchive OblivionPS3 +Requires .NET Framework 4.8 + +### Features + - Can pack BSA archives for PS3 properly. + - Drag & drop for files and folders to add quickly into archive. + - Allows for modification of textures on Oblivion PS3. + - Allows for mods to have working textures alongside meshes. + - Can choose to compress assets inside of BSA or not. + - Normal maps from PC are converted to PS3 variant by default. + - Tool checks if asset data is already converted for PS3, if it is, nothing is done to it pre-packing; this allows repacking main game BSAs and seeing no differences. + - CLI tool allows for dragging folder onto executable and getting archives packed with PS3 settings straight away. + - NormalMapConverter tool allowing DDS normal maps from PC to be converted to PS3. This tool is used by default by the Packer GUI but I included it as a executable in case someone wants to use it separately. + +### F.A.Q + +Q: _How can we **open BSA**?_ +A: The [BSA Browser](https://github.com/AlexxEG/BSA_Browser) tool by AlexxEG (see credits) supports opening Oblivion PS3 BSA. + +Q: _What settings should I be using to pack on PS3?_ +A: In order to make a BSA that is proper for PS3 make sure "Use PS3 file flags", "Extend data", and "Convert normal maps" are turned on. If they are turned off BSA may not work right on PS3. + +Q: _Should I turn on the setting to compress assets in the archive?_ +A: This one is up to you, compression may increase load times but decrease BSA size. + +Q: _Should I pack BSAs for PC with this?_ +A: No. For PC there are other tools specifically made for it which can be Googled. If you decide to use my tool for packing on PC I hold no responsibility for bad BSAs being made (and I guaruntee some will be bad). + +### Credits + - Thanks to [AlexxEG](https://github.com/AlexxEG) for helping me understand buffers. + - Thanks to Spawnkiller for helping me understand how games handle textures and helping me with mesh-related work. + - Alberto M. for [.NET Zlib Implementation](https://www.codeproject.com/Tips/830793/NET-ZLib-Implementation) library for zlib compression. + - Dirk Lemstra (dlemstra) for [Magick.NET](https://github.com/dlemstra/Magick.NET) library for dealing with image files. + - Josip Medved for the OpenFolderDialog. \ No newline at end of file diff --git a/__DOCUMENTATION__/BSA.txt b/__DOCUMENTATION__/BSA.txt new file mode 100644 index 0000000..5bb3eac --- /dev/null +++ b/__DOCUMENTATION__/BSA.txt @@ -0,0 +1,6 @@ +The wiki entry below gives good details on the structure of Oblivion BSA, hashing algorithms, and more. + +UESP Wiki: https://en.uesp.net/wiki/Tes4Mod:BSA_File_Format + +One thing of note is that on PS3 the BSA have a special hashing algorithm for DDS file names, this algorithm can be found in "ArchiveInterop\OblivionBSAHash.cs"; if you +decide to use the algorithm please give credit where it is due! \ No newline at end of file diff --git a/__DOCUMENTATION__/DDS.txt b/__DOCUMENTATION__/DDS.txt new file mode 100644 index 0000000..ad90c34 --- /dev/null +++ b/__DOCUMENTATION__/DDS.txt @@ -0,0 +1,4 @@ +The links below gives good details on the DDS formats Oblivion supports, how some should be/are handled, and the specifications of the format. + +Texturing 101: http://tesalliance.org/forums/index.php?/tutorials/article/57-texturing-101/ +DDS_HEADER Structure: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header \ No newline at end of file diff --git a/__DOCUMENTATION__/Normal Maps (To PS3)/dxt1-noalpha.png b/__DOCUMENTATION__/Normal Maps (To PS3)/dxt1-noalpha.png new file mode 100644 index 0000000..d0e3402 Binary files /dev/null and b/__DOCUMENTATION__/Normal Maps (To PS3)/dxt1-noalpha.png differ diff --git a/__DOCUMENTATION__/Normal Maps (To PS3)/dxt5-alpha.png b/__DOCUMENTATION__/Normal Maps (To PS3)/dxt5-alpha.png new file mode 100644 index 0000000..2e993ef Binary files /dev/null and b/__DOCUMENTATION__/Normal Maps (To PS3)/dxt5-alpha.png differ