commit 5ecf5d6d6d9d1e794617f57a7bc3a418823e8e38 Author: MathiasL Date: Thu Jan 27 16:16:42 2022 +0100 Add DamageCalculator diff --git a/DamageCalculator/.vs/Config Manager/v16/.suo b/DamageCalculator/.vs/Config Manager/v16/.suo new file mode 100644 index 0000000..4953ade Binary files /dev/null and b/DamageCalculator/.vs/Config Manager/v16/.suo differ diff --git a/DamageCalculator/.vs/DamageCalculator/v16/.suo b/DamageCalculator/.vs/DamageCalculator/v16/.suo new file mode 100644 index 0000000..e638e13 Binary files /dev/null and b/DamageCalculator/.vs/DamageCalculator/v16/.suo differ diff --git a/DamageCalculator/DamageCalculator.sln b/DamageCalculator/DamageCalculator.sln new file mode 100644 index 0000000..fcfbaee --- /dev/null +++ b/DamageCalculator/DamageCalculator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31515.178 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Damage Calculator", "DamageCalculator\DamageCalculator.csproj", "{7A7AE40F-8677-44E3-873A-4AB7C9FCFEB4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7A7AE40F-8677-44E3-873A-4AB7C9FCFEB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A7AE40F-8677-44E3-873A-4AB7C9FCFEB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A7AE40F-8677-44E3-873A-4AB7C9FCFEB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A7AE40F-8677-44E3-873A-4AB7C9FCFEB4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DB4B39C3-0168-457A-BB47-A934F038897A} + EndGlobalSection +EndGlobal diff --git a/DamageCalculator/DamageCalculator/27.ico b/DamageCalculator/DamageCalculator/27.ico new file mode 100644 index 0000000..9279709 Binary files /dev/null and b/DamageCalculator/DamageCalculator/27.ico differ diff --git a/DamageCalculator/DamageCalculator/About.xaml b/DamageCalculator/DamageCalculator/About.xaml new file mode 100644 index 0000000..7e48e49 --- /dev/null +++ b/DamageCalculator/DamageCalculator/About.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/DamageCalculator/DamageCalculator/About.xaml.cs b/DamageCalculator/DamageCalculator/About.xaml.cs new file mode 100644 index 0000000..77e35b5 --- /dev/null +++ b/DamageCalculator/DamageCalculator/About.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace Damage_Calculator +{ + /// + /// Interaction logic for About.xaml + /// + public partial class About : Window + { + public About() + { + InitializeComponent(); + txtVersion.Text = "Version " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + } + } +} diff --git a/DamageCalculator/DamageCalculator/App.config b/DamageCalculator/DamageCalculator/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/DamageCalculator/DamageCalculator/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/DamageCalculator/DamageCalculator/App.xaml b/DamageCalculator/DamageCalculator/App.xaml new file mode 100644 index 0000000..812bf13 --- /dev/null +++ b/DamageCalculator/DamageCalculator/App.xaml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/DamageCalculator/DamageCalculator/App.xaml.cs b/DamageCalculator/DamageCalculator/App.xaml.cs new file mode 100644 index 0000000..4c26115 --- /dev/null +++ b/DamageCalculator/DamageCalculator/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace Damage_Calculator +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/DamageCalculator/DamageCalculator/CsgoHelper.cs b/DamageCalculator/DamageCalculator/CsgoHelper.cs new file mode 100644 index 0000000..a3ce3a3 --- /dev/null +++ b/DamageCalculator/DamageCalculator/CsgoHelper.cs @@ -0,0 +1,342 @@ +using Damage_Calculator.Models; +using Damage_Calculator.ZatVdfParser; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Damage_Calculator +{ + public class CsgoHelper + { + public string CsgoPath { get; set; } + + /// + /// Gets the prefixes allowed for maps when using . + /// + private readonly string[] validMapPrefixes = new[] + { + "de", + "cs", + "dz", + "ar" + }; + + /// + /// Gets the files relative to the CS:GO install path that are checked when validating. + /// + private readonly string[] filesToValidate = new[] + { + "csgo\\scripts\\items\\items_game.txt" // Item info (weapon stats etc.) + }; + + /// + /// Gets the directories relative to the CS:GO install path that are checked when validating. + /// + private readonly string[] directoriesToValidate = new[] + { + "csgo\\resource\\overviews" // Map overviews + }; + + public CsgoHelper() + { + // Nothing to do + } + + public CsgoHelper(string csgoPath) + { + this.CsgoPath = csgoPath; + } + + /// + /// Validates files and directories for CS:GO installed in the path. + /// + /// whether the files and directories exist. + public bool Validate() + { + return this.Validate(this.CsgoPath); + } + + + /// + /// Validates files and directories for CS:GO installed in the given path. + /// + /// The path to the CS:GO install directory, in which the executable resides. + /// whether the files and directories exist. + public bool Validate(string csgoPath) + { + foreach (string file in this.filesToValidate) + { + if (!File.Exists(Path.Combine(csgoPath, file))) + return false; + } + + foreach (string dir in this.directoriesToValidate) + { + if (!Directory.Exists(Path.Combine(csgoPath, dir))) + return false; + } + + return true; + } + + public List GetMaps() + { + List mapTextFiles = Directory.GetFiles(System.IO.Path.Combine(this.CsgoPath, "csgo\\resource\\overviews")).ToList().Where(f => f.ToLower().EndsWith(".txt")).Where(f => + this.mapFileNameValid(f)).ToList(); + + List maps = new List(); + + foreach (string file in mapTextFiles) + { + string potentialRadarFile = System.IO.Path.Combine(this.CsgoPath, "csgo\\resource\\overviews", System.IO.Path.GetFileNameWithoutExtension(file) + "_radar.dds"); + var map = new CsgoMapOverview(); + if (File.Exists(potentialRadarFile)) + { + map.MapImagePath = potentialRadarFile; + } + + var vdf = new VDFFile(file); + if (vdf.RootElements.Count > 0) + { + var rootNode = vdf.RootElements.First(); + if (float.TryParse(rootNode["scale"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float scale)) + { + map.MapSizeMultiplier = scale; + } + if (float.TryParse(rootNode["pos_x"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float posX)) + { + map.UpperLeftWorldXCoordinate = posX; + } + if (float.TryParse(rootNode["pos_y"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float posY)) + { + map.UpperLeftWorldYCoordinate = posY; + } + if (float.TryParse(rootNode["CTSpawn_x"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float ctX)) + { + map.CTSpawnMultiplierX = ctX; + } + if (float.TryParse(rootNode["CTSpawn_y"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float ctY)) + { + map.CTSpawnMultiplierY = ctY; + } + if (float.TryParse(rootNode["TSpawn_x"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float tX)) + { + map.TSpawnMultiplierX = tX; + } + if (float.TryParse(rootNode["TSpawn_y"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float tY)) + { + map.TSpawnMultiplierY = tY; + } + if (float.TryParse(rootNode["bombA_x"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float bombAX)) + { + map.BombAX = bombAX; + } + if (float.TryParse(rootNode["bombA_y"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float bombAY)) + { + map.BombAY = bombAY; + } + if (float.TryParse(rootNode["bombB_x"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float bombBX)) + { + map.BombBX = bombBX; + } + if (float.TryParse(rootNode["bombB_y"]?.Value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float bombBY)) + { + map.BombBY = bombBY; + } + } + + map.MapFileName = System.IO.Path.GetFileNameWithoutExtension(file).Split('_').Last(); + + DDSImage image; + try + { + image = new DDSImage(System.IO.File.ReadAllBytes(map.MapImagePath)); + } + catch + { + continue; + } + + if (image.BitmapImage.Width != image.BitmapImage.Height) + continue; + + System.Windows.Application.Current.Dispatcher.Invoke((Action)delegate + { + map.MapImage = Globals.BitmapToImageSource(image.BitmapImage); + }); + + maps.Add(map); + } + + return maps; + } + + public List GetWeapons() + { + string filePath = Path.Combine(this.CsgoPath, "csgo\\scripts\\items\\items_game.txt"); + if (!File.Exists(filePath)) + return null; + + var vdfItems = new VDFFile(filePath); + Element prefabs = vdfItems["items_game"]?["prefabs"]; + Element items = vdfItems["items_game"]?["items"]; + + if (prefabs == null || items == null) + return null; + + var weapons = new List(); + + foreach(var item in items.Children) + { + string itemPrefab = item["prefab"]?.Value; + string itemName = item["name"].Value; + + if (itemPrefab == null || !itemName.StartsWith("weapon_")) + continue; + + var weapon = new CsgoWeapon(); + weapon.ClassName = itemName; + + if(this.tryPopulateWeapon(weapon, prefabs, itemPrefab)) + { + weapons.Add(weapon); + } + } + + return weapons; + } + + private bool tryPopulateWeapon(CsgoWeapon weapon, Element prefabs, string prefabName, List prefabTrace = null) + { + Element prefab = prefabs[prefabName]; + + if (prefab == null) + // Prefab not existent (example was prefab named "valve csgo_tool") + return false; + + string nextPrefab = prefab["prefab"]?.Value; + + if (prefab == null || (nextPrefab == null && prefabTrace?.FirstOrDefault(pr => pr == "primary" || pr == "secondary") == null)) + // We've reached the end of abstraction but it wasn't found to be primary nor secondary + return false; + + bool gatheredAllInfo = true; + + Element attributes = prefab["attributes"]; + + if (attributes == null) + return false; + + // =========================== ATTRIBUTES =========================== // + + // Base damage + if (weapon.BaseDamage == -1) + { + string damage = attributes["damage"]?.Value; + if (damage != null) + { + // damage field exists + if (int.TryParse(damage, out int dmg)) + { + weapon.BaseDamage = dmg; + } + } + else + gatheredAllInfo = false; + } + + // Armor penetration + if (weapon.ArmorPenetration == -1) + { + string penetration = attributes["armor ratio"]?.Value; + if (penetration != null) + { + // Armor penetration field exists + if (float.TryParse(penetration, NumberStyles.Any, CultureInfo.InvariantCulture, out float pen)) + { + weapon.ArmorPenetration = pen * 100f / 2f; + } + } + else + gatheredAllInfo = false; + } + + // Damage dropoff + if (weapon.DamageDropoff == -1) + { + string dropoff = attributes["range modifier"]?.Value; + if (dropoff != null) + { + // Damage dropoff field exists + if (double.TryParse(dropoff, NumberStyles.Any, CultureInfo.InvariantCulture, out double drop)) + { + weapon.DamageDropoff = drop; + } + } + else + gatheredAllInfo = false; + } + + // Max range + if (weapon.MaxBulletRange == -1) + { + string maxrange = attributes["range"]?.Value; + if (maxrange != null) + { + // Max range field exists + if (int.TryParse(maxrange, out int range)) + { + weapon.MaxBulletRange = range; + } + } + else + gatheredAllInfo = false; + } + + // Headshot modifier + if (weapon.HeadshotModifier == -1) + { + string headshotModifier = attributes["headshot multiplier"]?.Value; + if (headshotModifier != null) + { + // Headshot modifier field exists + if (float.TryParse(headshotModifier, NumberStyles.Any, CultureInfo.InvariantCulture, out float hs)) + { + weapon.HeadshotModifier = hs; + } + } + else + gatheredAllInfo = false; + } + + // ================================================================== // + + if (gatheredAllInfo || nextPrefab == null) + return true; // ? + + if (prefabTrace == null) + prefabTrace = new List(); + + prefabTrace.Add(prefab.Name); + + return this.tryPopulateWeapon(weapon, prefabs, nextPrefab, prefabTrace); + } + + private bool mapFileNameValid(string mapPath) + { + string fileName = Path.GetFileName(mapPath.ToLower()); + + foreach(string prefix in this.validMapPrefixes) + { + if (fileName.StartsWith(prefix.ToLower())) + return true; + } + + return false; + } + } +} diff --git a/DamageCalculator/DamageCalculator/DDSImageParser.cs b/DamageCalculator/DamageCalculator/DDSImageParser.cs new file mode 100644 index 0000000..cc172f2 --- /dev/null +++ b/DamageCalculator/DamageCalculator/DDSImageParser.cs @@ -0,0 +1,2015 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System.IO; + +namespace Damage_Calculator +{ + #region DDSImage Class + public class DDSImage : IDisposable + { + #region Variables + private bool m_isValid = false; + private System.Drawing.Bitmap m_bitmap = null; + #endregion + + #region Constructor/Destructor + public DDSImage(byte[] ddsImage) + { + if (ddsImage == null) return; + if (ddsImage.Length == 0) return; + + using (MemoryStream stream = new MemoryStream(ddsImage.Length)) + { + stream.Write(ddsImage, 0, ddsImage.Length); + stream.Seek(0, SeekOrigin.Begin); + + using (BinaryReader reader = new BinaryReader(stream)) + { + this.Parse(reader); + } + } + } + + public DDSImage(Stream ddsImage) + { + if (ddsImage == null) return; + if (!ddsImage.CanRead) return; + + using (BinaryReader reader = new BinaryReader(ddsImage)) + { + this.Parse(reader); + } + } + + private DDSImage(System.Drawing.Bitmap bitmap) + { + this.m_bitmap = bitmap; + } + #endregion + + #region Override Methods + #endregion + + #region Private Methods + private void Parse(BinaryReader reader) + { + DDSStruct header = new DDSStruct(); + PixelFormat pixelFormat = PixelFormat.UNKNOWN; + byte[] data = null; + + if (this.ReadHeader(reader, ref header)) + { + this.m_isValid = true; + // patches for stuff + if (header.depth == 0) header.depth = 1; + + uint blocksize = 0; + pixelFormat = this.GetFormat(header, ref blocksize); + if (pixelFormat == PixelFormat.UNKNOWN) + { + throw new InvalidFileHeaderException(); + } + + data = this.ReadData(reader, header); + if (data != null) + { + byte[] rawData = this.DecompressData(header, data, pixelFormat); + this.m_bitmap = this.CreateBitmap((int)header.width, (int)header.height, rawData); + } + } + } + + private byte[] ReadData(BinaryReader reader, DDSStruct header) + { + byte[] compdata = null; + uint compsize = 0; + + if ((header.flags & DDSD_LINEARSIZE) > 1) + { + compdata = reader.ReadBytes((int)header.sizeorpitch); + compsize = (uint)compdata.Length; + } + else + { + uint bps = header.width * header.pixelformat.rgbbitcount / 8; + compsize = bps * header.height * header.depth; + compdata = new byte[compsize]; + + MemoryStream mem = new MemoryStream((int)compsize); + + byte[] temp; + for (int z = 0; z < header.depth; z++) + { + for (int y = 0; y < header.height; y++) + { + temp = reader.ReadBytes((int)bps); + mem.Write(temp, 0, temp.Length); + } + } + mem.Seek(0, SeekOrigin.Begin); + + mem.Read(compdata, 0, compdata.Length); + mem.Close(); + } + + return compdata; + } + + private System.Drawing.Bitmap CreateBitmap(int width, int height, byte[] rawData) + { + System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height) + , ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + IntPtr scan = data.Scan0; + int size = bitmap.Width * bitmap.Height * 4; + + unsafe + { + byte* p = (byte*)scan; + for (int i = 0; i < size; i += 4) + { + // iterate through bytes. + // Bitmap stores it's data in RGBA order. + // DDS stores it's data in BGRA order. + p[i] = rawData[i + 2]; // blue + p[i + 1] = rawData[i + 1]; // green + p[i + 2] = rawData[i]; // red + p[i + 3] = rawData[i + 3]; // alpha + } + } + + bitmap.UnlockBits(data); + return bitmap; + } + + private bool ReadHeader(BinaryReader reader, ref DDSStruct header) + { + byte[] signature = reader.ReadBytes(4); + if (!(signature[0] == 'D' && signature[1] == 'D' && signature[2] == 'S' && signature[3] == ' ')) + return false; + + header.size = reader.ReadUInt32(); + if (header.size != 124) + return false; + + //convert the data + header.flags = reader.ReadUInt32(); + header.height = reader.ReadUInt32(); + header.width = reader.ReadUInt32(); + header.sizeorpitch = reader.ReadUInt32(); + header.depth = reader.ReadUInt32(); + header.mipmapcount = reader.ReadUInt32(); + header.alphabitdepth = reader.ReadUInt32(); + + header.reserved = new uint[10]; + for (int i = 0; i < 10; i++) + { + header.reserved[i] = reader.ReadUInt32(); + } + + //pixelfromat + header.pixelformat.size = reader.ReadUInt32(); + header.pixelformat.flags = reader.ReadUInt32(); + header.pixelformat.fourcc = reader.ReadUInt32(); + header.pixelformat.rgbbitcount = reader.ReadUInt32(); + header.pixelformat.rbitmask = reader.ReadUInt32(); + header.pixelformat.gbitmask = reader.ReadUInt32(); + header.pixelformat.bbitmask = reader.ReadUInt32(); + header.pixelformat.alphabitmask = reader.ReadUInt32(); + + //caps + header.ddscaps.caps1 = reader.ReadUInt32(); + header.ddscaps.caps2 = reader.ReadUInt32(); + header.ddscaps.caps3 = reader.ReadUInt32(); + header.ddscaps.caps4 = reader.ReadUInt32(); + header.texturestage = reader.ReadUInt32(); + + return true; + } + + private PixelFormat GetFormat(DDSStruct header, ref uint blocksize) + { + PixelFormat format = PixelFormat.UNKNOWN; + if ((header.pixelformat.flags & DDPF_FOURCC) == DDPF_FOURCC) + { + blocksize = ((header.width + 3) / 4) * ((header.height + 3) / 4) * header.depth; + + switch (header.pixelformat.fourcc) + { + case FOURCC_DXT1: + format = PixelFormat.DXT1; + blocksize *= 8; + break; + + case FOURCC_DXT2: + format = PixelFormat.DXT2; + blocksize *= 16; + break; + + case FOURCC_DXT3: + format = PixelFormat.DXT3; + blocksize *= 16; + break; + + case FOURCC_DXT4: + format = PixelFormat.DXT4; + blocksize *= 16; + break; + + case FOURCC_DXT5: + format = PixelFormat.DXT5; + blocksize *= 16; + break; + + case FOURCC_ATI1: + format = PixelFormat.ATI1N; + blocksize *= 8; + break; + + case FOURCC_ATI2: + format = PixelFormat.THREEDC; + blocksize *= 16; + break; + + case FOURCC_RXGB: + format = PixelFormat.RXGB; + blocksize *= 16; + break; + + case FOURCC_DOLLARNULL: + format = PixelFormat.A16B16G16R16; + blocksize = header.width * header.height * header.depth * 8; + break; + + case FOURCC_oNULL: + format = PixelFormat.R16F; + blocksize = header.width * header.height * header.depth * 2; + break; + + case FOURCC_pNULL: + format = PixelFormat.G16R16F; + blocksize = header.width * header.height * header.depth * 4; + break; + + case FOURCC_qNULL: + format = PixelFormat.A16B16G16R16F; + blocksize = header.width * header.height * header.depth * 8; + break; + + case FOURCC_rNULL: + format = PixelFormat.R32F; + blocksize = header.width * header.height * header.depth * 4; + break; + + case FOURCC_sNULL: + format = PixelFormat.G32R32F; + blocksize = header.width * header.height * header.depth * 8; + break; + + case FOURCC_tNULL: + format = PixelFormat.A32B32G32R32F; + blocksize = header.width * header.height * header.depth * 16; + break; + + default: + format = PixelFormat.UNKNOWN; + blocksize *= 16; + break; + } // switch + } + else + { + // uncompressed image + if ((header.pixelformat.flags & DDPF_LUMINANCE) == DDPF_LUMINANCE) + { + if ((header.pixelformat.flags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS) + { + format = PixelFormat.LUMINANCE_ALPHA; + } + else + { + format = PixelFormat.LUMINANCE; + } + } + else + { + if ((header.pixelformat.flags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS) + { + format = PixelFormat.RGBA; + } + else + { + format = PixelFormat.RGB; + } + } + + blocksize = (header.width * header.height * header.depth * (header.pixelformat.rgbbitcount >> 3)); + } + + return format; + } + + #region Helper Methods + // iCompFormatToBpp + private uint PixelFormatToBpp(PixelFormat pf, uint rgbbitcount) + { + switch (pf) + { + case PixelFormat.LUMINANCE: + case PixelFormat.LUMINANCE_ALPHA: + case PixelFormat.RGBA: + case PixelFormat.RGB: + return rgbbitcount / 8; + + case PixelFormat.THREEDC: + case PixelFormat.RXGB: + return 3; + + case PixelFormat.ATI1N: + return 1; + + case PixelFormat.R16F: + return 2; + + case PixelFormat.A16B16G16R16: + case PixelFormat.A16B16G16R16F: + case PixelFormat.G32R32F: + return 8; + + case PixelFormat.A32B32G32R32F: + return 16; + + default: + return 4; + } + } + + // iCompFormatToBpc + private uint PixelFormatToBpc(PixelFormat pf) + { + switch (pf) + { + case PixelFormat.R16F: + case PixelFormat.G16R16F: + case PixelFormat.A16B16G16R16F: + return 4; + + case PixelFormat.R32F: + case PixelFormat.G32R32F: + case PixelFormat.A32B32G32R32F: + return 4; + + case PixelFormat.A16B16G16R16: + return 2; + + default: + return 1; + } + } + + private bool Check16BitComponents(DDSStruct header) + { + if (header.pixelformat.rgbbitcount != 32) + return false; + // a2b10g10r10 format + if (header.pixelformat.rbitmask == 0x3FF00000 && header.pixelformat.gbitmask == 0x000FFC00 && header.pixelformat.bbitmask == 0x000003FF + && header.pixelformat.alphabitmask == 0xC0000000) + return true; + // a2r10g10b10 format + else if (header.pixelformat.rbitmask == 0x000003FF && header.pixelformat.gbitmask == 0x000FFC00 && header.pixelformat.bbitmask == 0x3FF00000 + && header.pixelformat.alphabitmask == 0xC0000000) + return true; + + return false; + } + + private void CorrectPremult(uint pixnum, ref byte[] buffer) + { + for (uint i = 0; i < pixnum; i++) + { + byte alpha = buffer[i + 3]; + if (alpha == 0) continue; + int red = (buffer[i] << 8) / alpha; + int green = (buffer[i + 1] << 8) / alpha; + int blue = (buffer[i + 2] << 8) / alpha; + + buffer[i] = (byte)red; + buffer[i + 1] = (byte)green; + buffer[i + 2] = (byte)blue; + } + } + + private void ComputeMaskParams(uint mask, ref int shift1, ref int mul, ref int shift2) + { + shift1 = 0; mul = 1; shift2 = 0; + while ((mask & 1) == 0) + { + mask >>= 1; + shift1++; + } + uint bc = 0; + while ((mask & (1 << (int)bc)) != 0) bc++; + while ((mask * mul) < 255) + mul = (mul << (int)bc) + 1; + mask *= (uint)mul; + + while ((mask & ~0xff) != 0) + { + mask >>= 1; + shift2++; + } + } + + private unsafe void DxtcReadColors(byte* data, ref Colour8888[] op) + { + byte r0, g0, b0, r1, g1, b1; + + b0 = (byte)(data[0] & 0x1F); + g0 = (byte)(((data[0] & 0xE0) >> 5) | ((data[1] & 0x7) << 3)); + r0 = (byte)((data[1] & 0xF8) >> 3); + + b1 = (byte)(data[2] & 0x1F); + g1 = (byte)(((data[2] & 0xE0) >> 5) | ((data[3] & 0x7) << 3)); + r1 = (byte)((data[3] & 0xF8) >> 3); + + op[0].red = (byte)(r0 << 3 | r0 >> 2); + op[0].green = (byte)(g0 << 2 | g0 >> 3); + op[0].blue = (byte)(b0 << 3 | b0 >> 2); + + op[1].red = (byte)(r1 << 3 | r1 >> 2); + op[1].green = (byte)(g1 << 2 | g1 >> 3); + op[1].blue = (byte)(b1 << 3 | b1 >> 2); + } + + private void DxtcReadColor(ushort data, ref Colour8888 op) + { + byte r, g, b; + + b = (byte)(data & 0x1f); + g = (byte)((data & 0x7E0) >> 5); + r = (byte)((data & 0xF800) >> 11); + + op.red = (byte)(r << 3 | r >> 2); + op.green = (byte)(g << 2 | g >> 3); + op.blue = (byte)(b << 3 | r >> 2); + } + + private unsafe void DxtcReadColors(byte* data, ref Colour565 color_0, ref Colour565 color_1) + { + color_0.blue = (byte)(data[0] & 0x1F); + color_0.green = (byte)(((data[0] & 0xE0) >> 5) | ((data[1] & 0x7) << 3)); + color_0.red = (byte)((data[1] & 0xF8) >> 3); + + color_0.blue = (byte)(data[2] & 0x1F); + color_0.green = (byte)(((data[2] & 0xE0) >> 5) | ((data[3] & 0x7) << 3)); + color_0.red = (byte)((data[3] & 0xF8) >> 3); + } + + private void GetBitsFromMask(uint mask, ref uint shiftLeft, ref uint shiftRight) + { + uint temp, i; + + if (mask == 0) + { + shiftLeft = shiftRight = 0; + return; + } + + temp = mask; + for (i = 0; i < 32; i++, temp >>= 1) + { + if ((temp & 1) != 0) + break; + } + shiftRight = i; + + // Temp is preserved, so use it again: + for (i = 0; i < 8; i++, temp >>= 1) + { + if ((temp & 1) == 0) + break; + } + shiftLeft = 8 - i; + } + + // This function simply counts how many contiguous bits are in the mask. + private uint CountBitsFromMask(uint mask) + { + uint i, testBit = 0x01, count = 0; + bool foundBit = false; + + for (i = 0; i < 32; i++, testBit <<= 1) + { + if ((mask & testBit) != 0) + { + if (!foundBit) + foundBit = true; + count++; + } + else if (foundBit) + return count; + } + + return count; + } + + private uint HalfToFloat(ushort y) + { + int s = (y >> 15) & 0x00000001; + int e = (y >> 10) & 0x0000001f; + int m = y & 0x000003ff; + + if (e == 0) + { + if (m == 0) + { + // + // Plus or minus zero + // + return (uint)(s << 31); + } + else + { + // + // Denormalized number -- renormalize it + // + while ((m & 0x00000400) == 0) + { + m <<= 1; + e -= 1; + } + + e += 1; + m &= ~0x00000400; + } + } + else if (e == 31) + { + if (m == 0) + { + // + // Positive or negative infinity + // + return (uint)((s << 31) | 0x7f800000); + } + else + { + // + // Nan -- preserve sign and significand bits + // + return (uint)((s << 31) | 0x7f800000 | (m << 13)); + } + } + + // + // Normalized number + // + e = e + (127 - 15); + m = m << 13; + + // + // Assemble s, e and m. + // + return (uint)((s << 31) | (e << 23) | m); + } + + private unsafe void ConvFloat16ToFloat32(uint* dest, ushort* src, uint size) + { + uint i; + for (i = 0; i < size; ++i, ++dest, ++src) + { + //float: 1 sign bit, 8 exponent bits, 23 mantissa bits + //half: 1 sign bit, 5 exponent bits, 10 mantissa bits + *dest = HalfToFloat(*src); + } + } + + private unsafe void ConvG16R16ToFloat32(uint* dest, ushort* src, uint size) + { + uint i; + for (i = 0; i < size; i += 3) + { + //float: 1 sign bit, 8 exponent bits, 23 mantissa bits + //half: 1 sign bit, 5 exponent bits, 10 mantissa bits + *dest++ = HalfToFloat(*src++); + *dest++ = HalfToFloat(*src++); + *((float*)dest++) = 1.0f; + } + } + + private unsafe void ConvR16ToFloat32(uint* dest, ushort* src, uint size) + { + uint i; + for (i = 0; i < size; i += 3) + { + //float: 1 sign bit, 8 exponent bits, 23 mantissa bits + //half: 1 sign bit, 5 exponent bits, 10 mantissa bits + *dest++ = HalfToFloat(*src++); + *((float*)dest++) = 1.0f; + *((float*)dest++) = 1.0f; + } + } + #endregion + + #region Decompress Methods + private byte[] DecompressData(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + System.Diagnostics.Debug.WriteLine(pixelFormat); + // allocate bitmap + byte[] rawData = null; + + switch (pixelFormat) + { + case PixelFormat.RGBA: + rawData = this.DecompressRGBA(header, data, pixelFormat); + break; + + case PixelFormat.RGB: + rawData = this.DecompressRGB(header, data, pixelFormat); + break; + + case PixelFormat.LUMINANCE: + case PixelFormat.LUMINANCE_ALPHA: + rawData = this.DecompressLum(header, data, pixelFormat); + break; + + case PixelFormat.DXT1: + rawData = this.DecompressDXT1(header, data, pixelFormat); + break; + + case PixelFormat.DXT2: + rawData = this.DecompressDXT2(header, data, pixelFormat); + break; + + case PixelFormat.DXT3: + rawData = this.DecompressDXT3(header, data, pixelFormat); + break; + + case PixelFormat.DXT4: + rawData = this.DecompressDXT4(header, data, pixelFormat); + break; + + case PixelFormat.DXT5: + rawData = this.DecompressDXT5(header, data, pixelFormat); + break; + + case PixelFormat.THREEDC: + rawData = this.Decompress3Dc(header, data, pixelFormat); + break; + + case PixelFormat.ATI1N: + rawData = this.DecompressAti1n(header, data, pixelFormat); + break; + + case PixelFormat.RXGB: + rawData = this.DecompressRXGB(header, data, pixelFormat); + break; + + case PixelFormat.R16F: + case PixelFormat.G16R16F: + case PixelFormat.A16B16G16R16F: + case PixelFormat.R32F: + case PixelFormat.G32R32F: + case PixelFormat.A32B32G32R32F: + rawData = this.DecompressFloat(header, data, pixelFormat); + break; + + default: + throw new UnknownFileFormatException(); + } + + return rawData; + } + + private unsafe byte[] DecompressDXT1(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + // DXT1 decompressor + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + + Colour8888[] colours = new Colour8888[4]; + colours[0].alpha = 0xFF; + colours[1].alpha = 0xFF; + colours[2].alpha = 0xFF; + + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) + { + ushort colour0 = *((ushort*)temp); + ushort colour1 = *((ushort*)(temp + 2)); + DxtcReadColor(colour0, ref colours[0]); + DxtcReadColor(colour1, ref colours[1]); + + uint bitmask = ((uint*)temp)[1]; + temp += 8; + + if (colour0 > colour1) + { + // Four-color block: derive the other two colors. + // 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3 + // These 2-bit codes correspond to the 2-bit fields + // stored in the 64-bit block. + colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3); + colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3); + colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3); + //colours[2].alpha = 0xFF; + + colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3); + colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3); + colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3); + colours[3].alpha = 0xFF; + } + else + { + // Three-color block: derive the other color. + // 00 = color_0, 01 = color_1, 10 = color_2, + // 11 = transparent. + // These 2-bit codes correspond to the 2-bit fields + // stored in the 64-bit block. + colours[2].blue = (byte)((colours[0].blue + colours[1].blue) / 2); + colours[2].green = (byte)((colours[0].green + colours[1].green) / 2); + colours[2].red = (byte)((colours[0].red + colours[1].red) / 2); + //colours[2].alpha = 0xFF; + + colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3); + colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3); + colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3); + colours[3].alpha = 0x00; + } + + for (int j = 0, k = 0; j < 4; j++) + { + for (int i = 0; i < 4; i++, k++) + { + int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2); + Colour8888 col = colours[select]; + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp); + rawData[offset + 0] = (byte)col.red; + rawData[offset + 1] = (byte)col.green; + rawData[offset + 2] = (byte)col.blue; + rawData[offset + 3] = (byte)col.alpha; + } + } + } + } + } + } + } + + return rawData; + } + + private byte[] DecompressDXT2(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + // Can do color & alpha same as dxt3, but color is pre-multiplied + // so the result will be wrong unless corrected. + byte[] rawData = DecompressDXT3(header, data, pixelFormat); + CorrectPremult((uint)(width * height * depth), ref rawData); + + return rawData; + } + + private unsafe byte[] DecompressDXT3(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + // DXT3 decompressor + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + Colour8888[] colours = new Colour8888[4]; + + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) + { + byte* alpha = temp; + temp += 8; + + DxtcReadColors(temp, ref colours); + temp += 4; + + uint bitmask = ((uint*)temp)[1]; + temp += 4; + + // Four-color block: derive the other two colors. + // 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3 + // These 2-bit codes correspond to the 2-bit fields + // stored in the 64-bit block. + colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3); + colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3); + colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3); + //colours[2].alpha = 0xFF; + + colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3); + colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3); + colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3); + //colours[3].alpha = 0xFF; + + for (int j = 0, k = 0; j < 4; j++) + { + for (int i = 0; i < 4; k++, i++) + { + int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2); + + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp); + rawData[offset + 0] = (byte)colours[select].red; + rawData[offset + 1] = (byte)colours[select].green; + rawData[offset + 2] = (byte)colours[select].blue; + } + } + } + + for (int j = 0; j < 4; j++) + { + //ushort word = (ushort)(alpha[2 * j] + 256 * alpha[2 * j + 1]); + ushort word = (ushort)(alpha[2 * j] | (alpha[2 * j + 1] << 8)); + for (int i = 0; i < 4; i++) + { + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3); + rawData[offset] = (byte)(word & 0x0F); + rawData[offset] = (byte)(rawData[offset] | (rawData[offset] << 4)); + } + word >>= 4; + } + } + } + } + } + } + return rawData; + } + + private byte[] DecompressDXT4(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + // Can do color & alpha same as dxt5, but color is pre-multiplied + // so the result will be wrong unless corrected. + byte[] rawData = DecompressDXT5(header, data, pixelFormat); + CorrectPremult((uint)(width * height * depth), ref rawData); + + return rawData; + } + + private unsafe byte[] DecompressDXT5(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + Colour8888[] colours = new Colour8888[4]; + ushort[] alphas = new ushort[8]; + + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) + { + if (y >= height || x >= width) + break; + + alphas[0] = temp[0]; + alphas[1] = temp[1]; + byte* alphamask = (temp + 2); + temp += 8; + + DxtcReadColors(temp, ref colours); + uint bitmask = ((uint*)temp)[1]; + temp += 8; + + // Four-color block: derive the other two colors. + // 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3 + // These 2-bit codes correspond to the 2-bit fields + // stored in the 64-bit block. + colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3); + colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3); + colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3); + //colours[2].alpha = 0xFF; + + colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3); + colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3); + colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3); + //colours[3].alpha = 0xFF; + + int k = 0; + for (int j = 0; j < 4; j++) + { + for (int i = 0; i < 4; k++, i++) + { + int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2); + Colour8888 col = colours[select]; + // only put pixels out < width or height + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp); + rawData[offset] = (byte)col.red; + rawData[offset + 1] = (byte)col.green; + rawData[offset + 2] = (byte)col.blue; + } + } + } + + // 8-alpha or 6-alpha block? + if (alphas[0] > alphas[1]) + { + // 8-alpha block: derive the other six alphas. + // Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated. + alphas[2] = (ushort)((6 * alphas[0] + 1 * alphas[1] + 3) / 7); // bit code 010 + alphas[3] = (ushort)((5 * alphas[0] + 2 * alphas[1] + 3) / 7); // bit code 011 + alphas[4] = (ushort)((4 * alphas[0] + 3 * alphas[1] + 3) / 7); // bit code 100 + alphas[5] = (ushort)((3 * alphas[0] + 4 * alphas[1] + 3) / 7); // bit code 101 + alphas[6] = (ushort)((2 * alphas[0] + 5 * alphas[1] + 3) / 7); // bit code 110 + alphas[7] = (ushort)((1 * alphas[0] + 6 * alphas[1] + 3) / 7); // bit code 111 + } + else + { + // 6-alpha block. + // Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated. + alphas[2] = (ushort)((4 * alphas[0] + 1 * alphas[1] + 2) / 5); // Bit code 010 + alphas[3] = (ushort)((3 * alphas[0] + 2 * alphas[1] + 2) / 5); // Bit code 011 + alphas[4] = (ushort)((2 * alphas[0] + 3 * alphas[1] + 2) / 5); // Bit code 100 + alphas[5] = (ushort)((1 * alphas[0] + 4 * alphas[1] + 2) / 5); // Bit code 101 + alphas[6] = 0x00; // Bit code 110 + alphas[7] = 0xFF; // Bit code 111 + } + + // Note: Have to separate the next two loops, + // it operates on a 6-byte system. + + // First three bytes + //uint bits = (uint)(alphamask[0]); + uint bits = (uint)((alphamask[0]) | (alphamask[1] << 8) | (alphamask[2] << 16)); + for (int j = 0; j < 2; j++) + { + for (int i = 0; i < 4; i++) + { + // only put pixels out < width or height + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3); + rawData[offset] = (byte)alphas[bits & 0x07]; + } + bits >>= 3; + } + } + + // Last three bytes + //bits = (uint)(alphamask[3]); + bits = (uint)((alphamask[3]) | (alphamask[4] << 8) | (alphamask[5] << 16)); + for (int j = 2; j < 4; j++) + { + for (int i = 0; i < 4; i++) + { + // only put pixels out < width or height + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3); + rawData[offset] = (byte)alphas[bits & 0x07]; + } + bits >>= 3; + } + } + } + } + } + } + + return rawData; + } + + private unsafe byte[] DecompressRGB(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + + uint valMask = (uint)((header.pixelformat.rgbbitcount == 32) ? ~0 : (1 << (int)header.pixelformat.rgbbitcount) - 1); + uint pixSize = (uint)(((int)header.pixelformat.rgbbitcount + 7) / 8); + int rShift1 = 0; int rMul = 0; int rShift2 = 0; + ComputeMaskParams(header.pixelformat.rbitmask, ref rShift1, ref rMul, ref rShift2); + int gShift1 = 0; int gMul = 0; int gShift2 = 0; + ComputeMaskParams(header.pixelformat.gbitmask, ref gShift1, ref gMul, ref gShift2); + int bShift1 = 0; int bMul = 0; int bShift2 = 0; + ComputeMaskParams(header.pixelformat.bbitmask, ref bShift1, ref bMul, ref bShift2); + + int offset = 0; + int pixnum = width * height * depth; + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + while (pixnum-- > 0) + { + uint px = *((uint*)temp) & valMask; + temp += pixSize; + uint pxc = px & header.pixelformat.rbitmask; + rawData[offset + 0] = (byte)(((pxc >> rShift1) * rMul) >> rShift2); + pxc = px & header.pixelformat.gbitmask; + rawData[offset + 1] = (byte)(((pxc >> gShift1) * gMul) >> gShift2); + pxc = px & header.pixelformat.bbitmask; + rawData[offset + 2] = (byte)(((pxc >> bShift1) * bMul) >> bShift2); + rawData[offset + 3] = 0xff; + offset += 4; + } + } + return rawData; + } + + private unsafe byte[] DecompressRGBA(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + + uint valMask = (uint)((header.pixelformat.rgbbitcount == 32) ? ~0 : (1 << (int)header.pixelformat.rgbbitcount) - 1); + // Funny x86s, make 1 << 32 == 1 + uint pixSize = (header.pixelformat.rgbbitcount + 7) / 8; + int rShift1 = 0; int rMul = 0; int rShift2 = 0; + ComputeMaskParams(header.pixelformat.rbitmask, ref rShift1, ref rMul, ref rShift2); + int gShift1 = 0; int gMul = 0; int gShift2 = 0; + ComputeMaskParams(header.pixelformat.gbitmask, ref gShift1, ref gMul, ref gShift2); + int bShift1 = 0; int bMul = 0; int bShift2 = 0; + ComputeMaskParams(header.pixelformat.bbitmask, ref bShift1, ref bMul, ref bShift2); + int aShift1 = 0; int aMul = 0; int aShift2 = 0; + ComputeMaskParams(header.pixelformat.alphabitmask, ref aShift1, ref aMul, ref aShift2); + + int offset = 0; + int pixnum = width * height * depth; + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + + while (pixnum-- > 0) + { + uint px = *((uint*)temp) & valMask; + temp += pixSize; + uint pxc = px & header.pixelformat.rbitmask; + rawData[offset + 0] = (byte)(((pxc >> rShift1) * rMul) >> rShift2); + pxc = px & header.pixelformat.gbitmask; + rawData[offset + 1] = (byte)(((pxc >> gShift1) * gMul) >> gShift2); + pxc = px & header.pixelformat.bbitmask; + rawData[offset + 2] = (byte)(((pxc >> bShift1) * bMul) >> bShift2); + pxc = px & header.pixelformat.alphabitmask; + rawData[offset + 3] = (byte)(((pxc >> aShift1) * aMul) >> aShift2); + offset += 4; + } + } + return rawData; + } + + private unsafe byte[] Decompress3Dc(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + byte[] yColours = new byte[8]; + byte[] xColours = new byte[8]; + + int offset = 0; + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) + { + byte* temp2 = temp + 8; + + //Read Y palette + int t1 = yColours[0] = temp[0]; + int t2 = yColours[1] = temp[1]; + temp += 2; + if (t1 > t2) + for (int i = 2; i < 8; ++i) + yColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 7); + else + { + for (int i = 2; i < 6; ++i) + yColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 5); + yColours[6] = 0; + yColours[7] = 255; + } + + // Read X palette + t1 = xColours[0] = temp2[0]; + t2 = xColours[1] = temp2[1]; + temp2 += 2; + if (t1 > t2) + for (int i = 2; i < 8; ++i) + xColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 7); + else + { + for (int i = 2; i < 6; ++i) + xColours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 5); + xColours[6] = 0; + xColours[7] = 255; + } + + //decompress pixel data + int currentOffset = offset; + for (int k = 0; k < 4; k += 2) + { + // First three bytes + uint bitmask = ((uint)(temp[0]) << 0) | ((uint)(temp[1]) << 8) | ((uint)(temp[2]) << 16); + uint bitmask2 = ((uint)(temp2[0]) << 0) | ((uint)(temp2[1]) << 8) | ((uint)(temp2[2]) << 16); + for (int j = 0; j < 2; j++) + { + // only put pixels out < height + if ((y + k + j) < height) + { + for (int i = 0; i < 4; i++) + { + // only put pixels out < width + if (((x + i) < width)) + { + int t; + byte tx, ty; + + t1 = currentOffset + (x + i) * 3; + rawData[t1 + 1] = ty = yColours[bitmask & 0x07]; + rawData[t1 + 0] = tx = xColours[bitmask2 & 0x07]; + + //calculate b (z) component ((r/255)^2 + (g/255)^2 + (b/255)^2 = 1 + t = 127 * 128 - (tx - 127) * (tx - 128) - (ty - 127) * (ty - 128); + if (t > 0) + rawData[t1 + 2] = (byte)(Math.Sqrt(t) + 128); + else + rawData[t1 + 2] = 0x7F; + } + bitmask >>= 3; + bitmask2 >>= 3; + } + currentOffset += bps; + } + } + temp += 3; + temp2 += 3; + } + + //skip bytes that were read via Temp2 + temp += 8; + } + offset += bps * 4; + } + } + } + + return rawData; + } + + private unsafe byte[] DecompressAti1n(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + byte[] colours = new byte[8]; + + uint offset = 0; + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) + { + //Read palette + int t1 = colours[0] = temp[0]; + int t2 = colours[1] = temp[1]; + temp += 2; + if (t1 > t2) + for (int i = 2; i < 8; ++i) + colours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 7); + else + { + for (int i = 2; i < 6; ++i) + colours[i] = (byte)(t1 + ((t2 - t1) * (i - 1)) / 5); + colours[6] = 0; + colours[7] = 255; + } + + //decompress pixel data + uint currOffset = offset; + for (int k = 0; k < 4; k += 2) + { + // First three bytes + uint bitmask = ((uint)(temp[0]) << 0) | ((uint)(temp[1]) << 8) | ((uint)(temp[2]) << 16); + for (int j = 0; j < 2; j++) + { + // only put pixels out < height + if ((y + k + j) < height) + { + for (int i = 0; i < 4; i++) + { + // only put pixels out < width + if (((x + i) < width)) + { + t1 = (int)(currOffset + (x + i)); + rawData[t1] = colours[bitmask & 0x07]; + } + bitmask >>= 3; + } + currOffset += (uint)bps; + } + } + temp += 3; + } + } + offset += (uint)(bps * 4); + } + } + } + return rawData; + } + + private unsafe byte[] DecompressLum(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + + int lShift1 = 0; int lMul = 0; int lShift2 = 0; + ComputeMaskParams(header.pixelformat.rbitmask, ref lShift1, ref lMul, ref lShift2); + + int offset = 0; + int pixnum = width * height * depth; + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + while (pixnum-- > 0) + { + byte px = *(temp++); + rawData[offset + 0] = (byte)(((px >> lShift1) * lMul) >> lShift2); + rawData[offset + 1] = (byte)(((px >> lShift1) * lMul) >> lShift2); + rawData[offset + 2] = (byte)(((px >> lShift1) * lMul) >> lShift2); + rawData[offset + 3] = (byte)(((px >> lShift1) * lMul) >> lShift2); + offset += 4; + } + } + return rawData; + } + + private unsafe byte[] DecompressRXGB(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + + Colour565 color_0 = new Colour565(); + Colour565 color_1 = new Colour565(); + Colour8888[] colours = new Colour8888[4]; + byte[] alphas = new byte[8]; + + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) + { + if (y >= height || x >= width) + break; + alphas[0] = temp[0]; + alphas[1] = temp[1]; + byte* alphamask = temp + 2; + temp += 8; + + DxtcReadColors(temp, ref color_0, ref color_1); + temp += 4; + + uint bitmask = ((uint*)temp)[1]; + temp += 4; + + colours[0].red = (byte)(color_0.red << 3); + colours[0].green = (byte)(color_0.green << 2); + colours[0].blue = (byte)(color_0.blue << 3); + colours[0].alpha = 0xFF; + + colours[1].red = (byte)(color_1.red << 3); + colours[1].green = (byte)(color_1.green << 2); + colours[1].blue = (byte)(color_1.blue << 3); + colours[1].alpha = 0xFF; + + // Four-color block: derive the other two colors. + // 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3 + // These 2-bit codes correspond to the 2-bit fields + // stored in the 64-bit block. + colours[2].blue = (byte)((2 * colours[0].blue + colours[1].blue + 1) / 3); + colours[2].green = (byte)((2 * colours[0].green + colours[1].green + 1) / 3); + colours[2].red = (byte)((2 * colours[0].red + colours[1].red + 1) / 3); + colours[2].alpha = 0xFF; + + colours[3].blue = (byte)((colours[0].blue + 2 * colours[1].blue + 1) / 3); + colours[3].green = (byte)((colours[0].green + 2 * colours[1].green + 1) / 3); + colours[3].red = (byte)((colours[0].red + 2 * colours[1].red + 1) / 3); + colours[3].alpha = 0xFF; + + int k = 0; + for (int j = 0; j < 4; j++) + { + for (int i = 0; i < 4; i++, k++) + { + int select = (int)((bitmask & (0x03 << k * 2)) >> k * 2); + Colour8888 col = colours[select]; + + // only put pixels out < width or height + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp); + rawData[offset + 0] = col.red; + rawData[offset + 1] = col.green; + rawData[offset + 2] = col.blue; + } + } + } + + // 8-alpha or 6-alpha block? + if (alphas[0] > alphas[1]) + { + // 8-alpha block: derive the other six alphas. + // Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated. + alphas[2] = (byte)((6 * alphas[0] + 1 * alphas[1] + 3) / 7); // bit code 010 + alphas[3] = (byte)((5 * alphas[0] + 2 * alphas[1] + 3) / 7); // bit code 011 + alphas[4] = (byte)((4 * alphas[0] + 3 * alphas[1] + 3) / 7); // bit code 100 + alphas[5] = (byte)((3 * alphas[0] + 4 * alphas[1] + 3) / 7); // bit code 101 + alphas[6] = (byte)((2 * alphas[0] + 5 * alphas[1] + 3) / 7); // bit code 110 + alphas[7] = (byte)((1 * alphas[0] + 6 * alphas[1] + 3) / 7); // bit code 111 + } + else + { + // 6-alpha block. + // Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated. + alphas[2] = (byte)((4 * alphas[0] + 1 * alphas[1] + 2) / 5); // Bit code 010 + alphas[3] = (byte)((3 * alphas[0] + 2 * alphas[1] + 2) / 5); // Bit code 011 + alphas[4] = (byte)((2 * alphas[0] + 3 * alphas[1] + 2) / 5); // Bit code 100 + alphas[5] = (byte)((1 * alphas[0] + 4 * alphas[1] + 2) / 5); // Bit code 101 + alphas[6] = 0x00; // Bit code 110 + alphas[7] = 0xFF; // Bit code 111 + } + + // Note: Have to separate the next two loops, + // it operates on a 6-byte system. + // First three bytes + uint bits = *((uint*)alphamask); + for (int j = 0; j < 2; j++) + { + for (int i = 0; i < 4; i++) + { + // only put pixels out < width or height + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3); + rawData[offset] = alphas[bits & 0x07]; + } + bits >>= 3; + } + } + + // Last three bytes + bits = *((uint*)&alphamask[3]); + for (int j = 2; j < 4; j++) + { + for (int i = 0; i < 4; i++) + { + // only put pixels out < width or height + if (((x + i) < width) && ((y + j) < height)) + { + uint offset = (uint)(z * sizeofplane + (y + j) * bps + (x + i) * bpp + 3); + rawData[offset] = alphas[bits & 0x07]; + } + bits >>= 3; + } + } + } + } + } + } + return rawData; + } + + private unsafe byte[] DecompressFloat(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(this.PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * this.PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + int size = 0; + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + fixed (byte* destPtr = rawData) + { + byte* destData = destPtr; + switch (pixelFormat) + { + case PixelFormat.R32F: // Red float, green = blue = max + size = width * height * depth * 3; + for (int i = 0, j = 0; i < size; i += 3, j++) + { + ((float*)destData)[i] = ((float*)temp)[j]; + ((float*)destData)[i + 1] = 1.0f; + ((float*)destData)[i + 2] = 1.0f; + } + break; + + case PixelFormat.A32B32G32R32F: // Direct copy of float RGBA data + Array.Copy(data, rawData, data.Length); + break; + + case PixelFormat.G32R32F: // Red float, green float, blue = max + size = width * height * depth * 3; + for (int i = 0, j = 0; i < size; i += 3, j += 2) + { + ((float*)destData)[i] = ((float*)temp)[j]; + ((float*)destData)[i + 1] = ((float*)temp)[j + 1]; + ((float*)destData)[i + 2] = 1.0f; + } + break; + + case PixelFormat.R16F: // Red float, green = blue = max + size = width * height * depth * bpp; + ConvR16ToFloat32((uint*)destData, (ushort*)temp, (uint)size); + break; + + case PixelFormat.A16B16G16R16F: // Just convert from half to float. + size = width * height * depth * bpp; + ConvFloat16ToFloat32((uint*)destData, (ushort*)temp, (uint)size); + break; + + case PixelFormat.G16R16F: // Convert from half to float, set blue = max. + size = width * height * depth * bpp; + ConvG16R16ToFloat32((uint*)destData, (ushort*)temp, (uint)size); + break; + + default: + break; + } + } + } + + return rawData; + } + + #region UNUSED + private unsafe byte[] DecompressARGB(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + if (Check16BitComponents(header)) + return DecompressARGB16(header, data, pixelFormat); + + int sizeOfData = (int)((header.width * header.pixelformat.rgbbitcount / 8) * header.height * header.depth); + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + + if ((pixelFormat == PixelFormat.LUMINANCE) && (header.pixelformat.rgbbitcount == 16) && (header.pixelformat.rbitmask == 0xFFFF)) + { + Array.Copy(data, rawData, data.Length); + return rawData; + } + + uint readI = 0, tempBpp; + uint redL = 0, redR = 0; + uint greenL = 0, greenR = 0; + uint blueL = 0, blueR = 0; + uint alphaL = 0, alphaR = 0; + + GetBitsFromMask(header.pixelformat.rbitmask, ref redL, ref redR); + GetBitsFromMask(header.pixelformat.gbitmask, ref greenL, ref greenR); + GetBitsFromMask(header.pixelformat.bbitmask, ref blueL, ref blueR); + GetBitsFromMask(header.pixelformat.alphabitmask, ref alphaL, ref alphaR); + tempBpp = header.pixelformat.rgbbitcount / 8; + + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + for (int i = 0; i < sizeOfData; i += bpp) + { + //@TODO: This is SLOOOW... + //but the old version crashed in release build under + //winxp (and xp is right to stop this code - I always + //wondered that it worked the old way at all) + if (sizeOfData - i < 4) + { + //less than 4 byte to write? + if (tempBpp == 3) + { + //this branch is extra-SLOOOW + readI = (uint)(*temp | ((*(temp + 1)) << 8) | ((*(temp + 2)) << 16)); + } + else if (tempBpp == 1) + readI = *((byte*)temp); + else if (tempBpp == 2) + readI = (uint)(temp[0] | (temp[1] << 8)); + } + else + readI = (uint)(temp[0] | (temp[1] << 8) | (temp[2] << 16) | (temp[3] << 24)); + temp += tempBpp; + + rawData[i] = (byte)((((int)readI & (int)header.pixelformat.rbitmask) >> (int)redR) << (int)redL); + + if (bpp >= 3) + { + rawData[i + 1] = (byte)((((int)readI & (int)header.pixelformat.gbitmask) >> (int)greenR) << (int)greenL); + rawData[i + 2] = (byte)((((int)readI & header.pixelformat.bbitmask) >> (int)blueR) << (int)blueL); + + if (bpp == 4) + { + rawData[i + 3] = (byte)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL); + if (alphaL >= 7) + { + rawData[i + 3] = (byte)(rawData[i + 3] != 0 ? 0xFF : 0x00); + } + else if (alphaL >= 4) + { + rawData[i + 3] = (byte)(rawData[i + 3] | (rawData[i + 3] >> 4)); + } + } + } + else if (bpp == 2) + { + rawData[i + 1] = (byte)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL); + if (alphaL >= 7) + { + rawData[i + 1] = (byte)(rawData[i + 1] != 0 ? 0xFF : 0x00); + } + else if (alphaL >= 4) + { + rawData[i + 1] = (byte)(rawData[i + 1] | (rawData[i + 3] >> 4)); + } + } + } + } + return rawData; + } + + private unsafe byte[] DecompressARGB16(DDSStruct header, byte[] data, PixelFormat pixelFormat) + { + // allocate bitmap + int bpp = (int)(PixelFormatToBpp(pixelFormat, header.pixelformat.rgbbitcount)); + int bps = (int)(header.width * bpp * PixelFormatToBpc(pixelFormat)); + int sizeofplane = (int)(bps * header.height); + int width = (int)header.width; + int height = (int)header.height; + int depth = (int)header.depth; + + int sizeOfData = (int)((header.width * header.pixelformat.rgbbitcount / 8) * header.height * header.depth); + byte[] rawData = new byte[depth * sizeofplane + height * bps + width * bpp]; + + uint readI = 0, tempBpp = 0; + uint redL = 0, redR = 0; + uint greenL = 0, greenR = 0; + uint blueL = 0, blueR = 0; + uint alphaL = 0, alphaR = 0; + uint redPad = 0, greenPad = 0, bluePad = 0, alphaPad = 0; + + GetBitsFromMask(header.pixelformat.rbitmask, ref redL, ref redR); + GetBitsFromMask(header.pixelformat.gbitmask, ref greenL, ref greenR); + GetBitsFromMask(header.pixelformat.bbitmask, ref blueL, ref blueR); + GetBitsFromMask(header.pixelformat.alphabitmask, ref alphaL, ref alphaR); + redPad = 16 - CountBitsFromMask(header.pixelformat.rbitmask); + greenPad = 16 - CountBitsFromMask(header.pixelformat.gbitmask); + bluePad = 16 - CountBitsFromMask(header.pixelformat.bbitmask); + alphaPad = 16 - CountBitsFromMask(header.pixelformat.alphabitmask); + + redL = redL + redPad; + greenL = greenL + greenPad; + blueL = blueL + bluePad; + alphaL = alphaL + alphaPad; + + tempBpp = header.pixelformat.rgbbitcount / 8; + fixed (byte* bytePtr = data) + { + byte* temp = bytePtr; + fixed (byte* destPtr = rawData) + { + byte* destData = destPtr; + for (int i = 0; i < sizeOfData / 2; i += bpp) + { + //@TODO: This is SLOOOW... + //but the old version crashed in release build under + //winxp (and xp is right to stop this code - I always + //wondered that it worked the old way at all) + if (sizeOfData - i < 4) + { + //less than 4 byte to write? + if (tempBpp == 3) + { + //this branch is extra-SLOOOW + readI = (uint)(*temp | ((*(temp + 1)) << 8) | ((*(temp + 2)) << 16)); + } + else if (tempBpp == 1) + readI = *((byte*)temp); + else if (tempBpp == 2) + readI = (uint)(temp[0] | (temp[1] << 8)); + } + else + readI = (uint)(temp[0] | (temp[1] << 8) | (temp[2] << 16) | (temp[3] << 24)); + temp += tempBpp; + + ((ushort*)destData)[i + 2] = (ushort)((((int)readI & (int)header.pixelformat.rbitmask) >> (int)redR) << (int)redL); + + if (bpp >= 3) + { + ((ushort*)destData)[i + 1] = (ushort)((((int)readI & (int)header.pixelformat.gbitmask) >> (int)greenR) << (int)greenL); + ((ushort*)destData)[i] = (ushort)((((int)readI & (int)header.pixelformat.bbitmask) >> (int)blueR) << (int)blueL); + + if (bpp == 4) + { + ((ushort*)destData)[i + 3] = (ushort)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL); + if (alphaL >= 7) + { + ((ushort*)destData)[i + 3] = (ushort)(((ushort*)destData)[i + 3] != 0 ? 0xFF : 0x00); + } + else if (alphaL >= 4) + { + ((ushort*)destData)[i + 3] = (ushort)(((ushort*)destData)[i + 3] | (((ushort*)destData)[i + 3] >> 4)); + } + } + } + else if (bpp == 2) + { + ((ushort*)destData)[i + 1] = (ushort)((((int)readI & (int)header.pixelformat.alphabitmask) >> (int)alphaR) << (int)alphaL); + if (alphaL >= 7) + { + ((ushort*)destData)[i + 1] = (ushort)(((ushort*)destData)[i + 1] != 0 ? 0xFF : 0x00); + } + else if (alphaL >= 4) + { + ((ushort*)destData)[i + 1] = (ushort)(((ushort*)destData)[i + 1] | (rawData[i + 3] >> 4)); + } + } + } + } + } + return rawData; + } + #endregion + + #endregion + + #endregion + + #region Public Methods + public void Dispose() + { + if (this.m_bitmap != null) + { + this.m_bitmap.Dispose(); + this.m_bitmap = null; + } + } + #endregion + + #region Properties + /// + /// Returns a System.Imaging.Bitmap containing the DDS image. + /// + public System.Drawing.Bitmap BitmapImage + { + get { return this.m_bitmap; } + } + + /// + /// Returns the DDS image is valid format. + /// + public bool IsValid + { + get { return this.m_isValid; } + } + #endregion + + #region Operators + public static implicit operator DDSImage(System.Drawing.Bitmap value) + { + return new DDSImage(value); + } + + public static explicit operator System.Drawing.Bitmap(DDSImage value) + { + return value.BitmapImage; + } + #endregion + + #region Nested Types + + #region Colour8888 + [StructLayout(LayoutKind.Sequential)] + private struct Colour8888 + { + public byte red; + public byte green; + public byte blue; + public byte alpha; + } + #endregion + + #region Colour565 + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct Colour565 + { + public ushort blue; //: 5; + public ushort green; //: 6; + public ushort red; //: 5; + } + #endregion + + #region DDSStruct + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct DDSStruct + { + public uint size; // equals size of struct (which is part of the data file!) + public uint flags; + public uint height; + public uint width; + public uint sizeorpitch; + public uint depth; + public uint mipmapcount; + public uint alphabitdepth; + //[MarshalAs(UnmanagedType.U4, SizeConst = 11)] + public uint[] reserved;//[11]; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct pixelformatstruct + { + public uint size; // equals size of struct (which is part of the data file!) + public uint flags; + public uint fourcc; + public uint rgbbitcount; + public uint rbitmask; + public uint gbitmask; + public uint bbitmask; + public uint alphabitmask; + } + public pixelformatstruct pixelformat; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct ddscapsstruct + { + public uint caps1; + public uint caps2; + public uint caps3; + public uint caps4; + } + public ddscapsstruct ddscaps; + public uint texturestage; + + //#ifndef __i386__ + //void to_little_endian() + //{ + // size_t size = sizeof(DDSStruct); + // assert(size % 4 == 0); + // size /= 4; + // for (size_t i=0; i + /// Various pixel formats/compressors used by the DDS image. + /// + private enum PixelFormat + { + /// + /// 32-bit image, with 8-bit red, green, blue and alpha. + /// + RGBA, + /// + /// 24-bit image with 8-bit red, green, blue. + /// + RGB, + /// + /// 16-bit DXT-1 compression, 1-bit alpha. + /// + DXT1, + /// + /// DXT-2 Compression + /// + DXT2, + /// + /// DXT-3 Compression + /// + DXT3, + /// + /// DXT-4 Compression + /// + DXT4, + /// + /// DXT-5 Compression + /// + DXT5, + /// + /// DX10 Compression + /// + DX10, + /// + /// 3DC Compression + /// + THREEDC, + /// + /// ATI1n Compression + /// + ATI1N, + LUMINANCE, + LUMINANCE_ALPHA, + RXGB, + A16B16G16R16, + R16F, + G16R16F, + A16B16G16R16F, + R32F, + G32R32F, + A32B32G32R32F, + /// + /// Unknown pixel format. + /// + UNKNOWN + } + #endregion + + #endregion + } + #endregion + + #region Exceptions Class + /// + /// Thrown when an invalid file header has been encountered. + /// + public class InvalidFileHeaderException : Exception + { + } + + /// + /// Thrown when the data does not contain a DDS image. + /// + public class NotADDSImageException : Exception + { + + } + + /// + /// Thrown when there is an unknown compressor used in the DDS file. + /// + public class UnknownFileFormatException : Exception + { + } + #endregion +} \ No newline at end of file diff --git a/DamageCalculator/DamageCalculator/DamageCalculator.csproj b/DamageCalculator/DamageCalculator/DamageCalculator.csproj new file mode 100644 index 0000000..c9d72b2 --- /dev/null +++ b/DamageCalculator/DamageCalculator/DamageCalculator.csproj @@ -0,0 +1,159 @@ + + + + + Debug + AnyCPU + {7A7AE40F-8677-44E3-873A-4AB7C9FCFEB4} + WinExe + Damage_Calculator + CSGO Damage Calculator + v4.8 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + 27.ico + + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + About.xaml + + + + + + + + + + + + + + + + + + + + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DamageCalculator/DamageCalculator/Globals.cs b/DamageCalculator/DamageCalculator/Globals.cs new file mode 100644 index 0000000..7a0b488 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Globals.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Damage_Calculator +{ + static class Globals + { + public static Models.Settings Settings = new Models.Settings(); + + public static BitmapImage BitmapToImageSource(Bitmap src) + { + MemoryStream ms = new MemoryStream(); + ((System.Drawing.Bitmap)src).Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + BitmapImage image = new BitmapImage(); + image.BeginInit(); + ms.Seek(0, SeekOrigin.Begin); + image.StreamSource = ms; + image.EndInit(); + return image; + } + } +} diff --git a/DamageCalculator/DamageCalculator/MainWindow.xaml b/DamageCalculator/DamageCalculator/MainWindow.xaml new file mode 100644 index 0000000..7d88da3 --- /dev/null +++ b/DamageCalculator/DamageCalculator/MainWindow.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DamageCalculator/DamageCalculator/MainWindow.xaml.cs b/DamageCalculator/DamageCalculator/MainWindow.xaml.cs new file mode 100644 index 0000000..8fd773b --- /dev/null +++ b/DamageCalculator/DamageCalculator/MainWindow.xaml.cs @@ -0,0 +1,569 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Damage_Calculator.Models; +using Damage_Calculator.ZatVdfParser; +using System.Xml.Serialization; + +namespace Damage_Calculator +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + /// + /// Gets or sets the point that will be there when left-clicking a map. + /// + private MapPoint leftPoint = new MapPoint(); + private Color leftPointColour = Color.FromArgb(140, 255, 0, 0); + + /// + /// Gets or sets the point that will be there when right-clicking a map. + /// + private MapPoint rightPoint = new MapPoint(); + private Color rightPointColour = Color.FromArgb(140, 0, 255, 0); + + private Line connectingLine = new Line(); + + private Image CTSpawnIcon; + private Image TSpawnIcon; + private Image ASiteIcon; + private Image BSiteIcon; + + private double unitsDistance = -1; + + /// + /// Gets or sets the currently loaded map. + /// + private CsgoMapOverview loadedMap; + private CsgoWeapon selectedWeapon; + + private BackgroundWorker bgWorker = new BackgroundWorker(); + + private bool lineDrawn = false; + + public MainWindow() + { + InitializeComponent(); + + Globals.Settings.CsgoHelper.CsgoPath = Globals.Settings.SteamHelper.GetGamePathFromExactName("Counter-Strike: Global Offensive"); + if (Globals.Settings.CsgoHelper.CsgoPath == null) + { + MessageBox.Show("Make sure you have installed CS:GO and Steam correctly.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + this.Close(); + } + + bgWorker.DoWork += BgWorker_DoWork; + bgWorker.RunWorkerCompleted += BgWorker_RunWorkerCompleted; + bgWorker.ProgressChanged += BgWorker_ProgressChanged; + bgWorker.WorkerReportsProgress = true; + + this.gridLoading.Visibility = Visibility.Visible; + bgWorker.RunWorkerAsync(); + } + + #region background worker + private void BgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) + { + if (e.ProgressPercentage == 0) + { + // Add maps + var maps = new List(); + + foreach (var map in e.UserState as List) + { + var item = new ComboBoxItem(); + + item.Tag = map; + item.Content = map.MapFileName; + + maps.Add(item); + } + + this.comboBoxMaps.ItemsSource = maps.OrderBy(m => m.Content); + if (maps.Count > 0) + this.comboBoxMaps.SelectedIndex = 0; + } + else if(e.ProgressPercentage == 1) + { + // Add weapons + var weaponItems = new List(); + + foreach (var wpn in e.UserState as List) + { + var item = new ComboBoxItem(); + + item.Tag = wpn; + item.Content = wpn.ClassName.Substring(wpn.ClassName.IndexOf('_') + 1); + + weaponItems.Add(item); + } + + comboWeapons.ItemsSource = weaponItems.OrderBy(w => w.Content); + if (weaponItems.Count > 0) + this.comboWeapons.SelectedIndex = 0; + } + } + + private static string calculateMD5(string filename) + { + using (var md5 = System.Security.Cryptography.MD5.Create()) + { + using (var stream = File.OpenRead(filename)) + { + var hash = md5.ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + } + } + } + + private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + this.gridLoading.Visibility = Visibility.Collapsed; + } + + private void BgWorker_DoWork(object sender, DoWorkEventArgs e) + { + var maps = Globals.Settings.CsgoHelper.GetMaps(); + bgWorker.ReportProgress(0, maps); + var serializer = new XmlSerializer(typeof(List)); + + List weapons; + + string itemsFile = System.IO.Path.Combine(Globals.Settings.CsgoHelper.CsgoPath, "csgo\\scripts\\items\\items_game.txt"); + string saveFileDir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "CSGO Damage Calculator"); + string currentHash = calculateMD5(itemsFile); + + if (Directory.Exists(saveFileDir)) + { + string[] files = Directory.GetFiles(saveFileDir); + if (files.Length == 1) + { + // Compare hashes + string oldHash = System.IO.Path.GetFileName(files[0]); + + if(currentHash == oldHash) + { + weapons = (List)serializer.Deserialize(new FileStream(System.IO.Path.Combine(saveFileDir, currentHash), FileMode.Open)); + bgWorker.ReportProgress(1, weapons); + return; + } + else + { + foreach (string file in files) + { + File.Delete(file); + } + } + } + else + { + foreach(string file in files) + { + File.Delete(file); + } + } + } + else + { + Directory.CreateDirectory(saveFileDir); + } + + weapons = Globals.Settings.CsgoHelper.GetWeapons(); + serializer.Serialize(new FileStream(System.IO.Path.Combine(saveFileDir, currentHash), FileMode.Create), weapons); + bgWorker.ReportProgress(1, weapons); + } + #endregion + + private void resetCanvas() + { + this.pointsCanvas.Children.Clear(); + this.leftPoint = null; + this.rightPoint = null; + this.connectingLine = null; + this.unitsDistance = -1; + this.textDistanceMetres.Text = "0"; + this.textDistanceUnits.Text = "0"; + this.txtResult.Text = "0"; + this.txtResultArmor.Text = "0"; + } + + private void loadMap(CsgoMapOverview map) + { + mapImage.Source = map.MapImage; + } + + private Ellipse getPointEllipse(Color strokeColour) + { + Ellipse circle = new Ellipse(); + circle.Fill = null; + circle.Width = circle.Height = 14; + circle.Stroke = new SolidColorBrush(strokeColour); + circle.StrokeThickness = 2; + circle.IsHitTestVisible = false; + + return circle; + } + + private void updateCirclePositions() + { + if (this.connectingLine == null) + this.connectingLine = new Line(); + + if (leftPoint?.Circle != null) + { + Canvas.SetLeft(leftPoint.Circle, (leftPoint.PercentageX * pointsCanvas.ActualWidth / 100f) - (leftPoint.Circle.Width / 2)); + Canvas.SetTop(leftPoint.Circle, (leftPoint.PercentageY * pointsCanvas.ActualHeight / 100f) - (leftPoint.Circle.Height / 2)); + } + + if (rightPoint?.Circle != null) + { + Canvas.SetLeft(rightPoint.Circle, (rightPoint.PercentageX * pointsCanvas.ActualWidth / 100f) - (rightPoint.Circle.Width / 2)); + Canvas.SetTop(rightPoint.Circle, (rightPoint.PercentageY * pointsCanvas.ActualHeight / 100f) - (rightPoint.Circle.Height / 2)); + } + + if(leftPoint?.Circle != null && rightPoint?.Circle != null) + { + this.connectingLine.X1 = Canvas.GetLeft(leftPoint.Circle) + (leftPoint.Circle.Width / 2); + this.connectingLine.Y1 = Canvas.GetTop(leftPoint.Circle) + (leftPoint.Circle.Height / 2); + this.connectingLine.X2 = Canvas.GetLeft(rightPoint.Circle) + (rightPoint.Circle.Width / 2); + this.connectingLine.Y2 = Canvas.GetTop(rightPoint.Circle) + (rightPoint.Circle.Height / 2); + this.connectingLine.Fill = null; + this.connectingLine.Stroke = new SolidColorBrush(Color.FromArgb(140, 255, 255, 255)); + this.connectingLine.StrokeThickness = 2; + this.connectingLine.IsHitTestVisible = false; + + int indexLine = pointsCanvas.Children.IndexOf(this.connectingLine); + if (indexLine < 0) + { + pointsCanvas.Children.Add(this.connectingLine); + this.lineDrawn = true; + } + + this.unitsDistance = this.calculateDotDistanceInUnits(); + this.textDistanceUnits.Text = Math.Round(this.unitsDistance, 2).ToString(); + this.textDistanceMetres.Text = Math.Round(this.unitsDistance / 39.37, 2).ToString(); + this.settings_Updated(null, null); + } + else + { + this.lineDrawn = false; + } + + if(this.loadedMap != null && this.loadedMap.CTSpawnMultiplierX != -1 && this.loadedMap.CTSpawnMultiplierY != -1) + { + // CT Icon + if (this.CTSpawnIcon == null) + { + this.CTSpawnIcon = new Image(); + this.CTSpawnIcon.Source = new BitmapImage(new Uri("icon_ct.png", UriKind.RelativeOrAbsolute)); + this.CTSpawnIcon.Width = 25; + this.CTSpawnIcon.Height = 25; + this.CTSpawnIcon.Opacity = 0.6; + this.CTSpawnIcon.IsHitTestVisible = false; + } + + if(pointsCanvas.Children.IndexOf(CTSpawnIcon) == -1) + pointsCanvas.Children.Add(CTSpawnIcon); + + + Canvas.SetLeft(CTSpawnIcon, this.loadedMap.CTSpawnMultiplierX * this.mapImage.ActualWidth - (CTSpawnIcon.ActualWidth / 2)); + Canvas.SetTop(CTSpawnIcon, this.loadedMap.CTSpawnMultiplierY * this.mapImage.ActualWidth - (CTSpawnIcon.ActualHeight / 2)); + + // T Icon + if (this.TSpawnIcon == null) + { + this.TSpawnIcon = new Image(); + this.TSpawnIcon.Source = new BitmapImage(new Uri("icon_t.png", UriKind.RelativeOrAbsolute)); + this.TSpawnIcon.Width = 25; + this.TSpawnIcon.Height = 25; + this.TSpawnIcon.Opacity = 0.6; + this.TSpawnIcon.IsHitTestVisible = false; + } + + if (pointsCanvas.Children.IndexOf(TSpawnIcon) == -1) + pointsCanvas.Children.Add(TSpawnIcon); + + Canvas.SetLeft(TSpawnIcon, this.loadedMap.TSpawnMultiplierX * this.mapImage.ActualWidth - (TSpawnIcon.ActualWidth / 2)); + Canvas.SetTop(TSpawnIcon, this.loadedMap.TSpawnMultiplierY * this.mapImage.ActualWidth - (TSpawnIcon.ActualHeight / 2)); + + // Bomb A Icon + if (this.ASiteIcon == null) + { + this.ASiteIcon = new Image(); + this.ASiteIcon.Source = new BitmapImage(new Uri("icon_a_site.png", UriKind.RelativeOrAbsolute)); + this.ASiteIcon.Width = 25; + this.ASiteIcon.Height = 25; + this.ASiteIcon.Opacity = 0.6; + this.ASiteIcon.IsHitTestVisible = false; + } + + if (pointsCanvas.Children.IndexOf(ASiteIcon) == -1) + pointsCanvas.Children.Add(ASiteIcon); + + Canvas.SetLeft(ASiteIcon, this.loadedMap.BombAX * this.mapImage.ActualWidth - (ASiteIcon.ActualWidth / 2)); + Canvas.SetTop(ASiteIcon, this.loadedMap.BombAY * this.mapImage.ActualWidth - (ASiteIcon.ActualHeight / 2)); + + // Bomb B Icon + if (this.BSiteIcon == null) + { + this.BSiteIcon = new Image(); + this.BSiteIcon.Source = new BitmapImage(new Uri("icon_b_site.png", UriKind.RelativeOrAbsolute)); + this.BSiteIcon.Width = 25; + this.BSiteIcon.Height = 25; + this.BSiteIcon.Opacity = 0.6; + this.BSiteIcon.IsHitTestVisible = false; + } + + if (pointsCanvas.Children.IndexOf(BSiteIcon) == -1) + pointsCanvas.Children.Add(BSiteIcon); + + Canvas.SetLeft(BSiteIcon, this.loadedMap.BombBX * this.mapImage.ActualWidth - (BSiteIcon.ActualWidth / 2)); + Canvas.SetTop(BSiteIcon, this.loadedMap.BombBY * this.mapImage.ActualWidth - (BSiteIcon.ActualHeight / 2)); + } + } + + private double calculateDotDistanceInUnits() + { + Ellipse circleLeft = pointsCanvas.Children[pointsCanvas.Children.IndexOf(leftPoint.Circle)] as Ellipse; + double leftX = Canvas.GetLeft(circleLeft); + double leftY = Canvas.GetTop(circleLeft); + + Ellipse circleRight = pointsCanvas.Children[pointsCanvas.Children.IndexOf(rightPoint.Circle)] as Ellipse; + double rightX = Canvas.GetLeft(circleRight); + double rightY = Canvas.GetTop(circleRight); + + // Distance in shown pixels + double diffPixels = Math.Sqrt(Math.Pow(Math.Abs(leftX - rightX), 2) + Math.Pow(Math.Abs(leftY - rightY), 2)); + + // Percentage on shown pixels + double diffPerc = diffPixels * 100f / this.mapImage.ActualWidth; + + // Distance on original pixel scales + double diffPixelsOriginal = diffPerc * (this.mapImage.Source as BitmapSource).PixelWidth / 100f; + + // times scale multiplier + double unitsDifference = diffPixelsOriginal * this.loadedMap.MapSizeMultiplier; + + return unitsDifference; + } + + #region events + private void mapImage_LayoutUpdated(object sender, EventArgs e) + { + this.updateCirclePositions(); + } + + private void mapImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (this.leftPoint == null) + leftPoint = new MapPoint(); + + Point mousePos = Mouse.GetPosition(pointsCanvas); + pointsCanvas.Children.Remove(leftPoint.Circle); + + var circle = this.getPointEllipse(this.leftPointColour); + + pointsCanvas.Children.Add(circle); + + leftPoint.PercentageX = mousePos.X * 100f / pointsCanvas.ActualWidth; + leftPoint.PercentageY = mousePos.Y * 100f / pointsCanvas.ActualHeight; + + leftPoint.Circle = circle; + + this.updateCirclePositions(); + } + + private void mapImage_MouseRightButtonUp(object sender, MouseButtonEventArgs e) + { + if (this.rightPoint == null) + this.rightPoint = new MapPoint(); + + Point mousePos = Mouse.GetPosition(pointsCanvas); + pointsCanvas.Children.Remove(rightPoint.Circle); + + var circle = this.getPointEllipse(this.rightPointColour); + + pointsCanvas.Children.Add(circle); + + rightPoint.PercentageX = mousePos.X * 100f / pointsCanvas.ActualWidth; + rightPoint.PercentageY = mousePos.Y * 100f / pointsCanvas.ActualHeight; + + rightPoint.Circle = circle; + + this.updateCirclePositions(); + } + + private void changeTheme_Click(object sender, RoutedEventArgs e) + { + switch (int.Parse(((MenuItem)sender).Uid)) + { + case 0: REghZyFramework.Themes.ThemesController.SetTheme(REghZyFramework.Themes.ThemesController.ThemeTypes.Dark); + rectTop.Fill = rectSide.Fill = new SolidColorBrush(Colors.White); + txtEasterEggMetres.Text = "Metres:"; + break; + case 1: REghZyFramework.Themes.ThemesController.SetTheme(REghZyFramework.Themes.ThemesController.ThemeTypes.Light); + rectTop.Fill = rectSide.Fill = new SolidColorBrush(Colors.Black); + txtEasterEggMetres.Text = "Meters:"; + break; + } + e.Handled = true; + } + + private void comboBoxMaps_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var map = ((sender as ComboBox).SelectedItem as ComboBoxItem)?.Tag as CsgoMapOverview; + + if (map != null) + this.loadMap(map); + + this.resetCanvas(); + this.loadedMap = map; + } + + private void settings_Updated(object sender, EventArgs e) + { + if (this.selectedWeapon == null || !this.lineDrawn) + { + if(txtResult != null && txtResultArmor != null) + txtResult.Text = txtResultArmor.Text = "0"; + + return; + } + + double damage = this.selectedWeapon.BaseDamage; + double absorbedDamageByArmor = 0; + bool wasArmorHit = false; + + if(this.unitsDistance > this.selectedWeapon.MaxBulletRange) + { + damage = 0; + txtResult.Text = txtResultArmor.Text = damage.ToString(); + return; + } + + // Range + damage *= Math.Pow(this.selectedWeapon.DamageDropoff, double.Parse((this.unitsDistance / 500f).ToString())); + + // Multipliers and armor penetration + if (radioHead.IsChecked == true) + { + // Headshot + damage *= this.selectedWeapon.HeadshotModifier; + + if (chkHelmet.IsChecked == true) + { + // Has helmet + double previousDamage = damage; + damage *= this.selectedWeapon.ArmorPenetration / 100f; + absorbedDamageByArmor = previousDamage - (int)damage; + wasArmorHit = true; + } + } + else if(radioChestArms.IsChecked == true) + { + // Chest or arms + if (chkKevlar.IsChecked == true) + { + // Has kevlar + double previousDamage = damage; + damage *= this.selectedWeapon.ArmorPenetration / 100f; + absorbedDamageByArmor = previousDamage - (int)damage; + wasArmorHit = true; + } + } + else if(radioStomach.IsChecked == true) + { + // Stomach + damage *= 1.25f; + + if (chkKevlar.IsChecked == true) + { + // Has kevlar + double previousDamage = damage; + damage *= this.selectedWeapon.ArmorPenetration / 100f; + absorbedDamageByArmor = previousDamage - (int)damage; + wasArmorHit = true; + } + } + else + { + // Legs + damage *= 0.75f; + } + + txtResult.Text = ((int)damage).ToString(); + + txtResultArmor.Text = (wasArmorHit ? (int)(absorbedDamageByArmor / 2) : 0).ToString(); + + // TODO: HP and armor and HP and armor left after shot + } + + private void comboWeapons_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + var weapon = ((sender as ComboBox).SelectedItem as ComboBoxItem)?.Tag as CsgoWeapon; + + this.selectedWeapon = weapon; + settings_Updated(null, null); + } + + private void mnuHelp_Click(object sender, RoutedEventArgs e) + { + About about = new About(); + about.Owner = this; + about.ShowDialog(); + } + + private void Window_MouseMove(object sender, MouseEventArgs e) + { + if (this.mapImage.Source == null) + return; + + var position = Mouse.GetPosition(mapImage); + if (position.X >= 0 && position.Y >= 0 && position.X <= mapImage.ActualWidth && position.Y <= mapImage.ActualHeight) + { + // Percentage on shown pixels + double diffPercX = position.X * 100f / this.mapImage.ActualWidth; + // Distance on original pixel scales + double diffPixelsOriginalX = diffPercX * (this.mapImage.Source as BitmapSource).PixelWidth / 100f; + // times scale multiplier + double unitsDifferenceX = diffPixelsOriginalX * this.loadedMap.MapSizeMultiplier; + txtCursorX.Text = Math.Round(this.loadedMap.UpperLeftWorldXCoordinate + unitsDifferenceX, 2).ToString(System.Globalization.CultureInfo.InvariantCulture); + + // Percentage on shown pixels + double diffPercY = position.Y * 100f / this.mapImage.ActualWidth; + // Distance on original pixel scales + double diffPixelsOriginalY = diffPercY * (this.mapImage.Source as BitmapSource).PixelWidth / 100f; + // times scale multiplier + double unitsDifferenceY = diffPixelsOriginalY * this.loadedMap.MapSizeMultiplier; + txtCursorY.Text = Math.Round(this.loadedMap.UpperLeftWorldYCoordinate - unitsDifferenceY, 2).ToString(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + txtCursorX.Text = txtCursorY.Text = "0"; + } + } + private void Window_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control) + { + Clipboard.SetText(txtCursorX.Text + " " + txtCursorY.Text); + } + } + #endregion + } +} diff --git a/DamageCalculator/DamageCalculator/Models/CsgoMapOverview.cs b/DamageCalculator/DamageCalculator/Models/CsgoMapOverview.cs new file mode 100644 index 0000000..e223fa7 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Models/CsgoMapOverview.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Damage_Calculator.Models +{ + public class CsgoMapOverview + { + public BitmapSource MapImage { get; set; } + + public string MapImagePath { get; set; } + + public string MapFileName { get; set; } + + public float MapSizeMultiplier { get; set; } + + public float UpperLeftWorldXCoordinate { get; set; } = -1; + + public float UpperLeftWorldYCoordinate { get; set; } = -1; + + public float CTSpawnMultiplierX { get; set; } = -1; + + public float CTSpawnMultiplierY { get; set; } = -1; + + public float TSpawnMultiplierX { get; set; } = -1; + + public float TSpawnMultiplierY { get; set; } = -1; + + public float BombAX { get; set; } = -1; + + public float BombAY { get; set; } = -1; + + public float BombBX { get; set; } = -1; + + public float BombBY { get; set; } = -1; + } +} diff --git a/DamageCalculator/DamageCalculator/Models/CsgoWeapon.cs b/DamageCalculator/DamageCalculator/Models/CsgoWeapon.cs new file mode 100644 index 0000000..51df07b --- /dev/null +++ b/DamageCalculator/DamageCalculator/Models/CsgoWeapon.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Damage_Calculator.Models +{ + public class CsgoWeapon + { + /// + /// Gets or sets the class name of this weapon. + /// e.g. AK47 would be "weapon_ak47" + /// + public string ClassName { get; set; } + + /// + /// Gets or sets the base damage that the weapon deals, in health points. + /// e.g. AK47 would have 36 base damage + /// + public int BaseDamage { get; set; } = -1; + + /// + /// Gets or sets the armor penetration of this weapon as a percentage. + /// In the files this is saved as a float from 0 (0% penetration) to 2 (100% penetration). + /// + public float ArmorPenetration { get; set; } = -1; + + /// + /// Gets or sets the damage dropoff that this weapon has at 500 units distance. It's saved as a "RangeModifier" stat, + /// where the modifier is a float value between 0 and 1 that contains the factor of the damage left. + /// A value of 0.75 would mean, at 500 units the weapon loses 25% of its damage. + /// The damage left can be calculated by: DAMAGE * RangeModifier^(DISTANCE / 500) + /// + public double DamageDropoff { get; set; } = -1; + + /// + /// Gets or sets the maximum range a bullet of this weapon can travel in units. After this distance the "bullet" will just disappear. + /// + public int MaxBulletRange { get; set; } = -1; + + /// + /// Gets or sets the multiplier of headshots by this weapon. At the point of writing this is "4" for all weapons except two. + /// + public float HeadshotModifier { get; set; } = -1; + } +} diff --git a/DamageCalculator/DamageCalculator/Models/MapPoint.cs b/DamageCalculator/DamageCalculator/Models/MapPoint.cs new file mode 100644 index 0000000..3616dd1 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Models/MapPoint.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Damage_Calculator.Models +{ + class MapPoint + { + public System.Windows.Shapes.Ellipse Circle { get; set; } + + public double PercentageX { get; set; } + + public double PercentageY { get; set; } + } +} diff --git a/DamageCalculator/DamageCalculator/Models/Settings.cs b/DamageCalculator/DamageCalculator/Models/Settings.cs new file mode 100644 index 0000000..bf4f887 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Models/Settings.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Damage_Calculator.Models +{ + public class Settings + { + public SteamHelper SteamHelper = new SteamHelper(); + + public CsgoHelper CsgoHelper = new CsgoHelper(); + } +} diff --git a/DamageCalculator/DamageCalculator/Models/SteamGame.cs b/DamageCalculator/DamageCalculator/Models/SteamGame.cs new file mode 100644 index 0000000..e22c640 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Models/SteamGame.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Damage_Calculator.Models +{ + public class SteamGame + { + public string Name { get; set; } + + public string InstallFolderName { get; set; } + + public int AppId { get; set; } + + public int GameState { get; set; } + + public DateTime LastUpdated { get; set; } + + public long LastOwnerSteam64Id { get; set; } + + public long BytesToDownload { get; set; } + + public long BytesDownloaded { get; set; } + + public long BytesToStage { get; set; } + + public long BytesStaged { get; set; } + + public bool KeepAutomaticallyUpdated { get; set; } + + public bool AllowOtherUpdatesWhileRunning { get; set; } + } + + [Flags] + enum GameState + { + StateInvalid = 0, + StateUninstalled = 1, + StateUpdateRequired = 2, + StateFullyInstalled = 4, + StateEncrypted = 8, + StateLocked = 16, + StateFilesMissing = 32, + StateAppRunning = 64, + StateFilesCorrupt = 128, + StateUpdateRunning = 256, + StateUpdatePaused = 512, + StateUpdateStarted = 1024, + StateUninstalling = 2048, + StateBackupRunning = 4096, + StateReconfiguring = 65536, + StateValidating = 131072, + StateAddingFiles = 262144, + StatePreallocating = 524288, + StateDownloading = 1048576, + StateStaging = 2097152, + StateCommitting = 4194304, + StateUpdateStopping = 8388608 + } +} diff --git a/DamageCalculator/DamageCalculator/Models/SteamLibrary.cs b/DamageCalculator/DamageCalculator/Models/SteamLibrary.cs new file mode 100644 index 0000000..91d8733 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Models/SteamLibrary.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Damage_Calculator.Models +{ + public class SteamLibrary + { + public SteamLibrary() + { + // Nothing to do + } + + public SteamLibrary(string path) + { + this.Path = path; + this.DoesExist = System.IO.Directory.Exists(path); + } + + public SteamLibrary(string path, bool doesExist) + { + this.Path = path; + this.DoesExist = doesExist; + } + + public string Path { get; set; } + + public bool DoesExist { get; set; } + } +} diff --git a/DamageCalculator/DamageCalculator/Properties/AssemblyInfo.cs b/DamageCalculator/DamageCalculator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e70fd94 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// 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("CSGO Damage Calculator")] +[assembly: AssemblyDescription("CS:GO Damage Calculator")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Matty_L")] +[assembly: AssemblyProduct("CSGO Damage Calculator")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[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)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// 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/DamageCalculator/DamageCalculator/Properties/Resources.Designer.cs b/DamageCalculator/DamageCalculator/Properties/Resources.Designer.cs new file mode 100644 index 0000000..10d224b --- /dev/null +++ b/DamageCalculator/DamageCalculator/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 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 Damage_Calculator.Properties { + using System; + + + /// + /// 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", "16.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 (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Damage_Calculator.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/DamageCalculator/DamageCalculator/Properties/Resources.resx b/DamageCalculator/DamageCalculator/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/DamageCalculator/DamageCalculator/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/DamageCalculator/DamageCalculator/Properties/Settings.Designer.cs b/DamageCalculator/DamageCalculator/Properties/Settings.Designer.cs new file mode 100644 index 0000000..94e3df9 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// 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 Damage_Calculator.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.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/DamageCalculator/DamageCalculator/Properties/Settings.settings b/DamageCalculator/DamageCalculator/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/DamageCalculator/DamageCalculator/Serializer.cs b/DamageCalculator/DamageCalculator/Serializer.cs new file mode 100644 index 0000000..3c4eb40 --- /dev/null +++ b/DamageCalculator/DamageCalculator/Serializer.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace Damage_Calculator +{ + static class Serializer + { + public static void ToXml(object obj, string path) + { + var serializer = new XmlSerializer(obj.GetType()); + using(var writer = XmlWriter.Create(path)) + { + serializer.Serialize(writer, obj); + } + } + + public static object FromXml(Type type, string pathToXml) + { + var serializer = new XmlSerializer(type); + using (var reader = XmlReader.Create(pathToXml)) + { + return serializer.Deserialize(reader); + } + } + } +} diff --git a/DamageCalculator/DamageCalculator/SteamHelper.cs b/DamageCalculator/DamageCalculator/SteamHelper.cs new file mode 100644 index 0000000..f1937e9 --- /dev/null +++ b/DamageCalculator/DamageCalculator/SteamHelper.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Damage_Calculator.ZatVdfParser; +using Microsoft.Win32; +using Damage_Calculator.Models; + +namespace Damage_Calculator +{ + public class SteamHelper + { + private string steamPath; + private List steamLibraries; + + /// + /// Gets the absolute path to the Steam install directory. + /// If it can't be fetched (i.e. Steam is not installed) null is returned. + /// + public string SteamPath + { + get + { + if(this.steamPath == null) + { + this.UpdateSteamPath(); + } + return this.steamPath; + } + } + + /// + /// Gets a list of all Steam libraries, and whether they're existent or not. + /// If it can't be fetched (i.e. Steam is not installed) null is returned. + /// + public List SteamLibraries + { + get + { + if (this.steamLibraries == null) + { + this.UpdateSteamLibraries(); + } + return this.steamLibraries; + } + } + + /// + /// Forcefully tries to update the property with the current Steam path, even if it should be already set. + /// + public void UpdateSteamPath() + { + this.steamPath = this.GetSteamPath(); + } + + /// + /// Gets the path to the Steam install directory. (For external use is preferred.) + /// + /// The absolute path to the Steam install directory, or null if it can't be fetched. + public string GetSteamPath() + { + var steamKey = Registry.CurrentUser.OpenSubKey("software\\valve\\steam"); + + if (steamKey == null) + return null; + + var steamPath = steamKey.GetValue("SteamPath"); + + if (steamPath == null) + return null; + + return steamPath.ToString(); + } + + /// + /// Forcefully tries to update the property with the current Steam path, even if it should be already set. + /// + public void UpdateSteamLibraries() + { + this.steamLibraries = this.GetSteamLibraries(); + } + + /// + /// Fetches a list of Steam libraries, which are deposited in the Steam config, as well as whether the libraries exist on the drive. + /// + /// A list of all deposited Steam libraries, and if they exist. + public List GetSteamLibraries() + { + if (this.steamPath == null) + this.steamPath = this.GetSteamPath(); + + if (this.steamPath == null) + return null; + + string configFilePath = Path.Combine(this.steamPath, "config\\config.vdf"); + if (!File.Exists(configFilePath)) + return null; + + // Fetch additional libraries + var configFile = new VDFFile(configFilePath); + IEnumerable additionalSteamLibraries = configFile["InstallConfigStore"]?["Software"]?["valve"]?["Steam"].Children.Where(c => c.Name.StartsWith("BaseInstallFolder_")).Select(c => c.Value); + + // List libraries plus default Steam directory, because that's the default library + var allLibraries = new List { new SteamLibrary(this.steamPath) }; + foreach(string addLib in additionalSteamLibraries) + { + allLibraries.Add(new SteamLibrary(addLib.Replace("\\\\", "\\"))); + } + + return allLibraries; + } + + public List GetInstalledGames() + { + var steamLibraries = this.GetSteamLibraries(); + + if (steamLibraries == null) + return null; + + var allGames = new List(); + + foreach(var library in steamLibraries) + { + if (!library.DoesExist) + continue; + + List manifestFiles = Directory.GetFiles(Path.Combine(library.Path, "steamapps")).ToList().Where(f => this.isAppManifestFile(f)).ToList(); + + foreach (string manifestFile in manifestFiles) + { + var manifestVDF = new VDFFile(manifestFile); + + if (manifestVDF.RootElements.Count < 1) + // App manifest might be still existent but the game might not be installed (happened during testing) + continue; + + var root = manifestVDF["AppState"]; + + var currGame = new SteamGame(); + + this.populateGameInfo(currGame, root); + + if((currGame.GameState & (int)GameState.StateFullyInstalled) != 0) + { + // Game was fully installed according to steam + allGames.Add(currGame); + } + } + } + + return allGames; + } + + public string GetGamePathFromExactName(string gameName) + { + var steamLibraries = this.GetSteamLibraries(); + + if (steamLibraries == null) + return null; + + var allGames = new List(); + + foreach (var library in steamLibraries) + { + if (!library.DoesExist) + continue; + + List manifestFiles = Directory.GetFiles(Path.Combine(library.Path, "steamapps")).ToList().Where(f => this.isAppManifestFile(f)).ToList(); + + foreach (string manifestFile in manifestFiles) + { + var manifestVDF = new VDFFile(manifestFile); + + if (manifestVDF.RootElements.Count < 1) + // App manifest might be still existent but the game might not be installed (happened during testing) + continue; + + var root = manifestVDF["AppState"]; + + if(root["name"].Value.Trim().ToLower() != gameName.Trim().ToLower()) + { + // Not our wanted game, skip + continue; + } + + var currGame = new SteamGame(); + + this.populateGameInfo(currGame, root); + + if ((currGame.GameState & (int)GameState.StateFullyInstalled) != 0) + { + // Game was fully installed according to steam + return Path.Combine(library.Path, "steamapps", "common", currGame.InstallFolderName); + } + } + } + + return null; + } + + #region Private Methods + private bool isAppManifestFile(string filePath) + { + return System.Text.RegularExpressions.Regex.IsMatch(filePath.Split(new[] { '\\', '/' }).Last(), "appmanifest_\\d+.acf"); ; + } + + private DateTime fromUnixFormat(long unixFormat) + { + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Local); + return dateTime.AddSeconds(unixFormat); + } + + private void populateGameInfo(SteamGame game, Element appStateVdf) + { + game.Name = appStateVdf["name"].Value; + + game.InstallFolderName = appStateVdf["installdir"].Value; + + if (int.TryParse(appStateVdf["appid"].Value, out int appId)) + { + game.AppId = appId; + } + + if (int.TryParse(appStateVdf["StateFlags"].Value, out int stateFlags)) + { + game.GameState = stateFlags; + } + + if (long.TryParse(appStateVdf["LastUpdated"].Value, out long lastUpdated)) + { + game.LastUpdated = fromUnixFormat(lastUpdated); + } + + if (long.TryParse(appStateVdf["LastOwner"].Value, out long lastOwner)) + { + game.LastOwnerSteam64Id = lastOwner; + } + + if (long.TryParse(appStateVdf["BytesToDownload"].Value, out long bytesToDownload)) + { + game.BytesToDownload = bytesToDownload; + } + + if (long.TryParse(appStateVdf["BytesDownloaded"].Value, out long bytesDownloaded)) + { + game.BytesDownloaded = bytesDownloaded; + } + + if (long.TryParse(appStateVdf["BytesToStage"].Value, out long bytesToStage)) + { + game.BytesToStage = bytesToStage; + } + + if (long.TryParse(appStateVdf["BytesStaged"].Value, out long bytesStaged)) + { + game.BytesStaged = bytesStaged; + } + + game.KeepAutomaticallyUpdated = appStateVdf["AutoUpdateBehavior"].Value != "0"; + + game.AllowOtherUpdatesWhileRunning = appStateVdf["AllowOtherDownloadsWhileRunning"].Value != "0"; + } + #endregion + } +} diff --git a/DamageCalculator/DamageCalculator/Themes/ColourfulDarkTheme.xaml b/DamageCalculator/DamageCalculator/Themes/ColourfulDarkTheme.xaml new file mode 100644 index 0000000..7f2ecfd --- /dev/null +++ b/DamageCalculator/DamageCalculator/Themes/ColourfulDarkTheme.xaml @@ -0,0 +1,4496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +