Advertisement
aveyo

build_weapon_sound_qol_dota_mod

Apr 23rd, 2019
979
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Batch 86.62 KB | None | 0 0
  1. /* 2>nul || @title VPKMOD by AveYo v2019.04.23 - weapon sound qol request
  2. @echo off & pushd %~dp0
  3.  
  4. :: Export bundled Source?Replacement pair list used by VPKMOD tool to generate the modded DOTA 2 pak01_dir.vpk  
  5.  >Mod.lst cd.
  6. ::------------------------------------------------------------------------------------------------------------------------------
  7. rem Hero_PhantomAssassin.PreAttack = Hero_ChaosKnight.PreAttack
  8. ::>>Mod.lst echo/sounds/weapons/hero/phantom_assassin/phantom_assassin_swing1.vsnd_c?sounds/weapons/hero/spirit_breaker/preattack01.vsnd_c
  9. rem Hero_PhantomAssassin.Attack , Hero_EmberSpirit.Attack = Hero_ChaosKnight.Attack
  10. >>Mod.lst echo/sounds/weapons/hero/shared/small_blade/attack_long01.vsnd_c?sounds/weapons/hero/omniknight/attack01.vsnd_c
  11. >>Mod.lst echo/sounds/weapons/hero/shared/small_blade/attack_long02.vsnd_c?sounds/weapons/hero/omniknight/attack02.vsnd_c
  12. >>Mod.lst echo/sounds/weapons/hero/shared/small_blade/attack_long03.vsnd_c?sounds/weapons/hero/omniknight/attack03.vsnd_c
  13. rem Hero_EmberSpirit.Attack.Ring = none
  14. >>Mod.lst echo/sounds/weapons/hero/shared/small_blade/ring01.vsnd_c?sounds/null.vsnd_c
  15. >>Mod.lst echo/sounds/weapons/hero/shared/small_blade/ring02.vsnd_c?sounds/null.vsnd_c
  16. >>Mod.lst echo/sounds/weapons/hero/shared/small_blade/ring03.vsnd_c?sounds/null.vsnd_c
  17. >>Mod.lst echo/sounds/weapons/hero/shared/small_blade/ring04.vsnd_c?sounds/null.vsnd_c
  18. rem Hero_DragonKnight.PreAttack = Hero_MonkeyKing.PreAttack
  19. >>Mod.lst echo/sounds/weapons/hero/dragon_knight/sword_swing1.vsnd_c?sounds/weapons/hero/brewmaster/preattack01.vsnd_c
  20. >>Mod.lst echo/sounds/weapons/hero/dragon_knight/sword_swing2.vsnd_c?sounds/weapons/hero/brewmaster/preattack02.vsnd_c
  21. >>Mod.lst echo/sounds/weapons/hero/dragon_knight/sword_swing3.vsnd_c?sounds/weapons/hero/brewmaster/preattack03.vsnd_c
  22. rem Hero_DragonKnight.Attack = Hero_ChaosKnight.Attack
  23. >>Mod.lst echo/sounds/weapons/hero/shared/impacts/sword_impact1.vsnd_c?sounds/weapons/hero/omniknight/attack01.vsnd_c
  24. >>Mod.lst echo/sounds/weapons/hero/shared/impacts/sword_impact2.vsnd_c?sounds/weapons/hero/omniknight/attack02.vsnd_c
  25. >>Mod.lst echo/sounds/weapons/hero/shared/impacts/sword_impact3.vsnd_c?sounds/weapons/hero/omniknight/attack03.vsnd_c
  26. rem Hero_Antimage.PreAttack = Hero_ChaosKnight.PreAttack
  27. ::>>Mod.lst echo/sounds/weapons/hero/antimage/antimage_swing1.vsnd_c?sounds/weapons/hero/spirit_breaker/preattack01.vsnd_c
  28. rem Hero_Antimage.Attack = Hero_ChaosKnight.Attack
  29. >>Mod.lst echo/sounds/weapons/hero/antimage/antimage_attack1.vsnd_c?sounds/weapons/hero/omniknight/attack01.vsnd_c
  30. >>Mod.lst echo/sounds/weapons/hero/antimage/antimage_attack2.vsnd_c?sounds/weapons/hero/omniknight/attack02.vsnd_c
  31. >>Mod.lst echo/sounds/weapons/hero/antimage/antimage_attack3.vsnd_c?sounds/weapons/hero/omniknight/attack03.vsnd_c
  32. >>Mod.lst echo/sounds/weapons/hero/antimage/antimage_attack4.vsnd_c?sounds/weapons/hero/omniknight/attack01.vsnd_c
  33. >>Mod.lst echo/sounds/weapons/hero/antimage/antimage_attack5.vsnd_c?sounds/weapons/hero/omniknight/attack02.vsnd_c
  34. >>Mod.lst echo/sounds/weapons/hero/antimage/antimage_attack6.vsnd_c?sounds/weapons/hero/omniknight/attack03.vsnd_c
  35. rem Hero_Juggernaut.ArcanaHaste.Anim
  36. >>Mod.lst echo/sounds/physics/movement/hero/juggernaut/haste.vsnd_c?sounds/null.vsnd_c
  37. ::------------------------------------------------------------------------------------------------------------------------------
  38.  
  39. :: Detect DOTA 2 path
  40. call :reg_query STEAMPATH "HKCU\SOFTWARE\Valve\Steam" "SteamPath"
  41. set "STEAMDATA=" & if defined STEAMPATH for %%# in ("%STEAMPATH%") do set "STEAMPATH=%%~dpnx#"
  42. if not exist "%STEAMPATH%\Steam.exe" call :end ! Cannot find SteamPath in registry
  43. call :reg_query ACTIVEUSER "HKCU\SOFTWARE\Valve\Steam\ActiveProcess" "ActiveUser" & set/a "STEAMID=ACTIVEUSER" >nul 2>nul
  44. if exist "%STEAMPATH%\userdata\%STEAMID%\config\localconfig.vdf" set "STEAMDATA=%STEAMPATH%\userdata\%STEAMID%"
  45. if not defined STEAMDATA for /f "delims=" %%# in ('dir "%STEAMPATH%\userdata" /b/o:d/t:w/s 2^>nul') do set "ACTIVEUSER=%%~dp#"
  46. if not defined STEAMDATA for /f "delims=\" %%# in ("%ACTIVEUSER:*\userdata\=%") do set "STEAMID=%%#"
  47. if exist "%STEAMPATH%\userdata\%STEAMID%\config\localconfig.vdf" set "STEAMDATA=%STEAMPATH%\userdata\%STEAMID%"
  48. set "libfilter=LibraryFolders { TimeNextStatsReport ContentStatsID }"
  49. if not exist "%STEAMPATH%\SteamApps\libraryfolders.vdf" call :end ! Cannot find "%STEAMPATH%\SteamApps\libraryfolders.vdf"
  50. for /f usebackq^ delims^=^"^ tokens^=4 %%s in (`findstr /v "%libfilter%" "%STEAMPATH%\SteamApps\libraryfolders.vdf"`) do (
  51. if exist "%%s\steamapps\appmanifest_570.acf" if exist "%%s\steamapps\common\dota 2 beta\game\dota\dota.fgd" set "libfs=%%s")
  52. set "STEAMAPPS=%STEAMPATH%\steamapps" & if defined libfs set "STEAMAPPS=%libfs:\\=\%\steamapps"
  53. if not exist "%STEAMAPPS%\common\dota 2 beta\game\dota\maps\dota.vpk" call :end ! Missing "%STEAMAPPS%\common\dota 2 beta\game"
  54. set "DOTA=%STEAMAPPS%\common\dota 2 beta\game" & set "CONTENT=%STEAMAPPS%\common\dota 2 beta\content"
  55.  
  56. :: Compile VPKMOD tool if not already present (needs net framework 3.5+ or 4.0+ on Windows 7)
  57. for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d /o:-n "%Windir%\Microsoft.NET\Framework\*csc.exe"') do set "csc=%%v"
  58. if not exist vpkmod.exe "%csc%" /out:vpkmod.exe /target:exe /platform:anycpu /optimize /nologo "%~f0"
  59. if not exist vpkmod.exe echo [ERROR] Failed compiling c# snippet! .net framework 3.5+ compiler needed &timeout /t -1 & exit /b
  60.  
  61. : Clear previous mod
  62. del /f /q "%DOTA%\dota_MOD\pak01_dir.vpk" >nul 2>nul
  63.  
  64. : Force-close dota2.exe
  65. taskkill /f /im dota2.exe /t >nul 2>nul & timeout /t 2 >nul
  66.  
  67. :: In-memory file replacement mod using nothing but unaltered Valve authored files (custom vpkmod tool exclusive feature)
  68. vpkmod -i "%DOTA%\dota\pak01_dir.vpk" -o "%DOTA%\dota_MOD\pak01_dir.vpk" -m "Mod.lst" &rem -s
  69.  
  70. : Done!
  71. if not exist "%DOTA%\dota_MOD\pak01_dir.vpk" (echo ERROR :^() else echo SUCCESS! To use, add launch option: -language MOD
  72. timeout /t -1
  73. exit/b
  74.  
  75. :reg_query [USAGE] call :reg_query ResultVar "HKCU\KeyName" "ValueName"
  76. (for /f "skip=2 delims=" %%s in ('reg query "%~2" /v "%~3" /z 2^>nul') do set ".=%%s" & call set "%~1=%%.:*)    =%%") & exit/b
  77.  
  78. :end Message[prefix with ! to signal failure]
  79. (echo. & echo %* & if "%~1"=="!" color 0c) & pause>nul & exit
  80.  
  81. exit/b VPKMOD C# source */
  82. using System;
  83. using System.Collections.Generic;
  84. using System.Diagnostics;
  85. using System.Globalization;
  86. using System.IO;
  87. using System.Linq;
  88. using System.Reflection;
  89. using System.Runtime.Serialization;
  90. using System.Security.Cryptography;
  91. using System.Security.Permissions;
  92. using System.Text;
  93. using SteamDatabase.ValvePak;
  94. using VPKMOD;
  95.  
  96. [assembly:AssemblyTitle("VPKMOD")]
  97. [assembly:AssemblyCompanyAttribute("AveYo")]
  98. [assembly:AssemblyCopyright("AveYo / SteamDatabase")]
  99. [assembly:AssemblyVersionAttribute("2019.03.19")]
  100.  
  101. namespace SteamDatabase.ValvePak
  102. {
  103.     // [assembly: AssemblyTitle("Valve Pak Library")]
  104.     // [assembly: AssemblyDescription("Library to work with Valve Pak archives")]
  105.     // [assembly: AssemblyCompany("SteamDatabase")]
  106.     // [assembly: AssemblyProduct("ValvePak")]
  107.     // [assembly: AssemblyCopyright("Copyright ® SteamDatabase 2016")]
  108.     // <auto-generated/>
  109.     // Not actually auto generated, but this makes StyleCop ignore this file
  110.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  111.     // PackageEntry.cs
  112.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  113.     public class PackageEntry
  114.     {
  115.         /// <summary>
  116.         /// Gets or sets file name of this entry.
  117.         /// </summary>
  118.         public string FileName { get; set; }
  119.  
  120.         /// <summary>
  121.         /// Gets or sets the name of the directory this file is in.
  122.         /// '/' is always used as a dictionary separator in Valve's implementation.
  123.         /// Directory names are also always lower cased in Valve's implementation.
  124.         /// </summary>
  125.         public string DirectoryName { get; set; }
  126.  
  127.         /// <summary>
  128.         /// Gets or sets the file extension.
  129.         /// If the file has no extension, this is an empty string.
  130.         /// </summary>
  131.         public string TypeName { get; set; }
  132.  
  133.         /// <summary>
  134.         /// Gets or sets the CRC32 checksum of this entry.
  135.         /// </summary>
  136.         public uint CRC32 { get; set; }
  137.  
  138.         /// <summary>
  139.         /// Gets or sets the length in bytes.
  140.         /// </summary>
  141.         public uint Length { get; set; }
  142.  
  143.         /// <summary>
  144.         /// Gets or sets the offset in the package.
  145.         /// </summary>
  146.         public uint Offset { get; set; }
  147.  
  148.         /// <summary>
  149.         /// Gets or sets which archive this entry is in.
  150.         /// </summary>
  151.         public ushort ArchiveIndex { get; set; }
  152.  
  153.         /// <summary>
  154.         /// Gets the length in bytes by adding Length and length of SmallData.
  155.         /// </summary>
  156.         public uint TotalLength
  157.         {
  158.             get
  159.             {
  160.                 var totalLength = Length;
  161.  
  162.                 if (SmallData != null)
  163.                 {
  164.                     totalLength += (uint)SmallData.Length;
  165.                 }
  166.  
  167.                 return totalLength;
  168.             }
  169.         }
  170.  
  171.         /// <summary>
  172.         /// Gets or sets the preloaded bytes.
  173.         /// </summary>
  174.         public byte[] SmallData { get; set; }
  175.  
  176.         /// <summary>
  177.         /// Returns the file name and extension.
  178.         /// </summary>
  179.         /// <returns>File name and extension.</returns>
  180.         public string GetFileName()
  181.         {
  182.             var fileName = FileName;
  183.  
  184.             if (TypeName != string.Empty)
  185.             {
  186.                 fileName += "." + TypeName;
  187.             }
  188.  
  189.             return fileName;
  190.         }
  191.  
  192.         /// <summary>
  193.         /// Returns the absolute path of the file in the package.
  194.         /// </summary>
  195.         /// <returns>Absolute path.</returns>
  196.         public string GetFullPath()
  197.         {
  198.             if (DirectoryName == null)
  199.             {
  200.                 return GetFileName();
  201.             }
  202.  
  203.             return DirectoryName + Package.DirectorySeparatorChar + GetFileName();
  204.         }
  205.  
  206.         public override string ToString()
  207.         {
  208.             //return $"{GetFullPath()} crc=0x{CRC32:x2} metadatasz={SmallData.Length} fnumber={ArchiveIndex} ofs=0x{Offset:x2} sz={Length}";
  209.             return String.Format("{0} crc=0x{1:x2} metadatasz={2} fnumber={3} ofs=0x{4:x2} sz={5}", GetFullPath(), CRC32, SmallData.Length, ArchiveIndex, Offset, Length);
  210.             //return ""+GetFullPath()+" crc=0x"+CRC32+" metadatasz="+SmallData.Length+" fnumber="+ArchiveIndex+" ofs=0x"+Offset+" sz="+Length;
  211.         }
  212.     }
  213.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  214.     // ArchiveMD5SectionEntry.cs
  215.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  216.     /// <summary>
  217.     /// VPK_ArchiveMD5SectionEntry
  218.     /// </summary>
  219.     public class ArchiveMD5SectionEntry
  220.     {
  221.         /// <summary>
  222.         /// Gets or sets the CRC32 checksum of this entry.
  223.         /// </summary>
  224.         public uint ArchiveIndex { get; set; }
  225.  
  226.         /// <summary>
  227.         /// Gets or sets the offset in the package.
  228.         /// </summary>
  229.         public uint Offset { get; set; }
  230.  
  231.         /// <summary>
  232.         /// Gets or sets the length in bytes.
  233.         /// </summary>
  234.         public uint Length { get; set; }
  235.  
  236.         /// <summary>
  237.         /// Gets or sets the expected Checksum checksum.
  238.         /// </summary>
  239.         public byte[] Checksum { get; set; }
  240.     }
  241.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  242.     // StreamHelpers.cs
  243.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  244.     internal static class StreamHelpers
  245.     {
  246.         /// <summary>
  247.         /// Reads a null terminated string.
  248.         /// </summary>
  249.         /// <returns>String.</returns>
  250.         /// <param name="stream">Stream.</param>
  251.         /// <param name="encoding">Encoding.</param>
  252.         public static string ReadNullTermString(this BinaryReader stream, Encoding encoding)
  253.         {
  254.             var characterSize = encoding.GetByteCount("e");
  255.  
  256.             using (var ms = new MemoryStream())
  257.             {
  258.                 while (true)
  259.                 {
  260.                     var data = new byte[characterSize];
  261.                     stream.Read(data, 0, characterSize);
  262.  
  263.                     if (encoding.GetString(data, 0, characterSize) == "\0")
  264.                     {
  265.                         break;
  266.                     }
  267.  
  268.                     ms.Write(data, 0, data.Length);
  269.                 }
  270.  
  271.                 return encoding.GetString(ms.ToArray());
  272.             }
  273.         }
  274.     }
  275.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  276.     // Crc32.cs
  277.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  278.     /// <summary>
  279.     /// A utility class to compute CRC32.
  280.     /// </summary>
  281.     internal static class Crc32
  282.     {
  283.         /// <summary>
  284.         /// CRC polynomial 0xEDB88320.
  285.         /// </summary>
  286.         private static readonly uint[] Table =
  287.         {
  288.             0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419,
  289.             0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
  290.             0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07,
  291.             0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
  292.             0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856,
  293.             0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
  294.             0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
  295.             0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
  296.             0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
  297.             0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
  298.             0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599,
  299.             0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
  300.             0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190,
  301.             0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
  302.             0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E,
  303.             0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
  304.             0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED,
  305.             0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
  306.             0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3,
  307.             0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
  308.             0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
  309.             0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
  310.             0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010,
  311.             0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
  312.             0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17,
  313.             0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
  314.             0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
  315.             0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
  316.             0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344,
  317.             0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
  318.             0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A,
  319.             0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
  320.             0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1,
  321.             0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
  322.             0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
  323.             0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
  324.             0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE,
  325.             0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
  326.             0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C,
  327.             0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
  328.             0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B,
  329.             0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
  330.             0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1,
  331.             0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
  332.             0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
  333.             0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
  334.             0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66,
  335.             0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
  336.             0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
  337.             0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
  338.             0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B,
  339.             0x2D02EF8D
  340.         };
  341.  
  342.         /// <summary>
  343.         /// Compute a checksum for a given array of bytes.
  344.         /// </summary>
  345.         /// <param name="buffer">The array of bytes to compute the checksum for.</param>
  346.         /// <returns>The computed checksum.</returns>
  347.         public static uint Compute(byte[] buffer)
  348.         {
  349.             uint crc = 0xFFFFFFFF;
  350.  
  351.             for (var i = 0; i < buffer.Length; i++)
  352.             {
  353.                 crc = (crc >> 8) ^ Table[buffer[i] ^ crc & 0xff];
  354.             }
  355.  
  356.             return ~crc;
  357.         }
  358.     }
  359.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  360.     // AsnKeyParser.cs
  361.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  362.     // <auto-generated/>
  363.     // Not actually auto generated, but this makes StyleCop ignore this file
  364.  
  365.     /*
  366.     This code is licenced under MIT
  367.  
  368.     // The MIT License
  369.     //
  370.     // Copyright (c) 2006-2008 TinyVine Software Limited.
  371.     //
  372.     // Permission is hereby granted, free of charge, to any person obtaining a copy
  373.     // of this software and associated documentation files (the "Software"), to deal
  374.     // in the Software without restriction, including without limitation the rights
  375.     // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  376.     // copies of the Software, and to permit persons to whom the Software is
  377.     // furnished to do so, subject to the following conditions:
  378.     //
  379.     // The above copyright notice and this permission notice shall be included in
  380.     // all copies or substantial portions of the Software.
  381.     //
  382.     // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  383.     // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  384.     // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  385.     // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  386.     // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  387.     // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  388.     // THE SOFTWARE.
  389.  
  390.     Portions of this software are Copyright of Simone Chiaretta
  391.     Portions of this software are Copyright of Nate Kohari
  392.     Portions of this software are Copyright of Alex Henderson
  393.     */
  394.     [Serializable]
  395.     internal sealed class BerDecodeException : Exception
  396.     {
  397.         private readonly int _position;
  398.  
  399.         public BerDecodeException(string message, int position)
  400.             : base(message)
  401.         {
  402.             _position = position;
  403.         }
  404.  
  405.         public BerDecodeException(string message, int position, Exception ex)
  406.             : base(message, ex)
  407.         {
  408.             _position = position;
  409.         }
  410.  
  411.         private BerDecodeException(SerializationInfo info, StreamingContext context)
  412.             : base(info, context)
  413.         {
  414.             _position = info.GetInt32("Position");
  415.         }
  416.  
  417.         public override string Message
  418.         {
  419.             get
  420.             {
  421.                 var sb = new StringBuilder(base.Message);
  422.  
  423.                 sb.AppendFormat(" (Position {0}){1}",
  424.                                 _position, Environment.NewLine);
  425.  
  426.                 return sb.ToString();
  427.             }
  428.         }
  429.  
  430.         [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
  431.         public override void GetObjectData(SerializationInfo info, StreamingContext context)
  432.         {
  433.             base.GetObjectData(info, context);
  434.             info.AddValue("Position", _position);
  435.         }
  436.     }
  437.  
  438.     internal sealed class AsnKeyParser
  439.     {
  440.         private readonly AsnParser _parser;
  441.  
  442.         public AsnKeyParser(ICollection<byte> contents)
  443.         {
  444.             _parser = new AsnParser(contents);
  445.         }
  446.  
  447.         public static byte[] TrimLeadingZero(byte[] values)
  448.         {
  449.             byte[] r;
  450.             if ((0x00 == values[0]) && (values.Length > 1))
  451.             {
  452.                 r = new byte[values.Length - 1];
  453.                 Array.Copy(values, 1, r, 0, values.Length - 1);
  454.             }
  455.             else
  456.             {
  457.                 r = new byte[values.Length];
  458.                 Array.Copy(values, r, values.Length);
  459.             }
  460.  
  461.             return r;
  462.         }
  463.  
  464.         public static bool EqualOid(byte[] first, byte[] second)
  465.         {
  466.             if (first.Length != second.Length)
  467.             {
  468.                 return false;
  469.             }
  470.  
  471.             for (int i = 0; i < first.Length; i++)
  472.             {
  473.                 if (first[i] != second[i])
  474.                 {
  475.                     return false;
  476.                 }
  477.             }
  478.  
  479.             return true;
  480.         }
  481.  
  482.         public RSAParameters ParseRSAPublicKey()
  483.         {
  484.             var parameters = new RSAParameters();
  485.  
  486.             // Current value
  487.  
  488.             // Sanity Check
  489.  
  490.             // Checkpoint
  491.             int position = _parser.CurrentPosition();
  492.  
  493.             // Ignore Sequence - PublicKeyInfo
  494.             int length = _parser.NextSequence();
  495.             if (length != _parser.RemainingBytes())
  496.             {
  497.                 var sb = new StringBuilder("Incorrect Sequence Size. ");
  498.                 sb.AppendFormat("Specified: {0}, Remaining: {1}",
  499.                                 length.ToString(CultureInfo.InvariantCulture),
  500.                                 _parser.RemainingBytes().ToString(CultureInfo.InvariantCulture));
  501.                 throw new BerDecodeException(sb.ToString(), position);
  502.             }
  503.  
  504.             // Checkpoint
  505.             position = _parser.CurrentPosition();
  506.  
  507.             // Ignore Sequence - AlgorithmIdentifier
  508.             length = _parser.NextSequence();
  509.             if (length > _parser.RemainingBytes())
  510.             {
  511.                 var sb = new StringBuilder("Incorrect AlgorithmIdentifier Size. ");
  512.                 sb.AppendFormat("Specified: {0}, Remaining: {1}",
  513.                                 length.ToString(CultureInfo.InvariantCulture),
  514.                                 _parser.RemainingBytes().ToString(CultureInfo.InvariantCulture));
  515.                 throw new BerDecodeException(sb.ToString(), position);
  516.             }
  517.  
  518.             // Checkpoint
  519.             position = _parser.CurrentPosition();
  520.             // Grab the OID
  521.             byte[] value = _parser.NextOID();
  522.             byte[] oid = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
  523.             if (!EqualOid(value, oid))
  524.             {
  525.                 throw new BerDecodeException("Expected OID 1.2.840.113549.1.1.1", position);
  526.             }
  527.  
  528.             // Optional Parameters
  529.             if (_parser.IsNextNull())
  530.             {
  531.                 _parser.NextNull();
  532.                 // Also OK: value = _parser.Next();
  533.             }
  534.             else
  535.             {
  536.                 // Gracefully skip the optional data
  537.                 _parser.Next();
  538.             }
  539.  
  540.             // Checkpoint
  541.             position = _parser.CurrentPosition();
  542.  
  543.             // Ignore BitString - PublicKey
  544.             length = _parser.NextBitString();
  545.             if (length > _parser.RemainingBytes())
  546.             {
  547.                 var sb = new StringBuilder("Incorrect PublicKey Size. ");
  548.                 sb.AppendFormat("Specified: {0}, Remaining: {1}",
  549.                                 length.ToString(CultureInfo.InvariantCulture),
  550.                                 _parser.RemainingBytes().ToString(CultureInfo.InvariantCulture));
  551.                 throw new BerDecodeException(sb.ToString(), position);
  552.             }
  553.  
  554.             // Checkpoint
  555.             position = _parser.CurrentPosition();
  556.  
  557.             // Ignore Sequence - RSAPublicKey
  558.             length = _parser.NextSequence();
  559.             if (length < _parser.RemainingBytes())
  560.             {
  561.                 var sb = new StringBuilder("Incorrect RSAPublicKey Size. ");
  562.                 sb.AppendFormat("Specified: {0}, Remaining: {1}",
  563.                                 length.ToString(CultureInfo.InvariantCulture),
  564.                                 _parser.RemainingBytes().ToString(CultureInfo.InvariantCulture));
  565.                 throw new BerDecodeException(sb.ToString(), position);
  566.             }
  567.  
  568.             parameters.Modulus = TrimLeadingZero(_parser.NextInteger());
  569.             parameters.Exponent = TrimLeadingZero(_parser.NextInteger());
  570.  
  571.             return parameters;
  572.         }
  573.     }
  574.  
  575.     internal sealed class AsnParser
  576.     {
  577.         private readonly int _initialCount;
  578.         private readonly List<byte> _octets;
  579.  
  580.         public AsnParser(ICollection<byte> values)
  581.         {
  582.             _octets = new List<byte>(values.Count);
  583.             _octets.AddRange(values);
  584.  
  585.             _initialCount = _octets.Count;
  586.         }
  587.  
  588.         public int CurrentPosition()
  589.         {
  590.             return _initialCount - _octets.Count;
  591.         }
  592.  
  593.         public int RemainingBytes()
  594.         {
  595.             return _octets.Count;
  596.         }
  597.  
  598.         private int GetLength()
  599.         {
  600.             int length = 0;
  601.  
  602.             // Checkpoint
  603.             int position = CurrentPosition();
  604.  
  605.             try
  606.             {
  607.                 byte b = GetNextOctet();
  608.  
  609.                 if (b == (b & 0x7f))
  610.                 {
  611.                     return b;
  612.                 }
  613.  
  614.                 int i = b & 0x7f;
  615.  
  616.                 if (i > 4)
  617.                 {
  618.                     var sb = new StringBuilder("Invalid Length Encoding. ");
  619.                     sb.AppendFormat("Length uses {0} _octets",
  620.                                     i.ToString(CultureInfo.InvariantCulture));
  621.                     throw new BerDecodeException(sb.ToString(), position);
  622.                 }
  623.  
  624.                 while (0 != i--)
  625.                 {
  626.                     // shift left
  627.                     length <<= 8;
  628.  
  629.                     length |= GetNextOctet();
  630.                 }
  631.             }
  632.             catch (ArgumentOutOfRangeException ex)
  633.             {
  634.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  635.             }
  636.  
  637.             return length;
  638.         }
  639.  
  640.         public byte[] Next()
  641.         {
  642.             int position = CurrentPosition();
  643.  
  644.             try
  645.             {
  646. #pragma warning disable 168
  647. #pragma warning disable 219
  648.                 byte b = GetNextOctet();
  649. #pragma warning restore 219
  650. #pragma warning restore 168
  651.  
  652.                 int length = GetLength();
  653.                 if (length > RemainingBytes())
  654.                 {
  655.                     var sb = new StringBuilder("Incorrect Size. ");
  656.                     sb.AppendFormat("Specified: {0}, Remaining: {1}",
  657.                                     length.ToString(CultureInfo.InvariantCulture),
  658.                                     RemainingBytes().ToString(CultureInfo.InvariantCulture));
  659.                     throw new BerDecodeException(sb.ToString(), position);
  660.                 }
  661.  
  662.                 return GetOctets(length);
  663.             }
  664.             catch (ArgumentOutOfRangeException ex)
  665.             {
  666.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  667.             }
  668.         }
  669.  
  670.         private byte GetNextOctet()
  671.         {
  672.             int position = CurrentPosition();
  673.  
  674.             if (0 == RemainingBytes())
  675.             {
  676.                 var sb = new StringBuilder("Incorrect Size. ");
  677.                 sb.AppendFormat("Specified: {0}, Remaining: {1}",
  678.                                 1.ToString(CultureInfo.InvariantCulture),
  679.                                 RemainingBytes().ToString(CultureInfo.InvariantCulture));
  680.                 throw new BerDecodeException(sb.ToString(), position);
  681.             }
  682.  
  683.             byte b = GetOctets(1)[0];
  684.  
  685.             return b;
  686.         }
  687.  
  688.         private byte[] GetOctets(int octetCount)
  689.         {
  690.             int position = CurrentPosition();
  691.  
  692.             if (octetCount > RemainingBytes())
  693.             {
  694.                 var sb = new StringBuilder("Incorrect Size. ");
  695.                 sb.AppendFormat("Specified: {0}, Remaining: {1}",
  696.                                 octetCount.ToString(CultureInfo.InvariantCulture),
  697.                                 RemainingBytes().ToString(CultureInfo.InvariantCulture));
  698.                 throw new BerDecodeException(sb.ToString(), position);
  699.             }
  700.  
  701.             var values = new byte[octetCount];
  702.  
  703.             try
  704.             {
  705.                 _octets.CopyTo(0, values, 0, octetCount);
  706.                 _octets.RemoveRange(0, octetCount);
  707.             }
  708.             catch (ArgumentOutOfRangeException ex)
  709.             {
  710.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  711.             }
  712.  
  713.             return values;
  714.         }
  715.  
  716.         public bool IsNextNull()
  717.         {
  718.             return _octets[0] == 0x05;
  719.         }
  720.  
  721.         public int NextNull()
  722.         {
  723.             int position = CurrentPosition();
  724.  
  725.             try
  726.             {
  727.                 byte b = GetNextOctet();
  728.                 if (0x05 != b)
  729.                 {
  730.                     var sb = new StringBuilder("Expected Null. ");
  731.                     sb.AppendFormat("Specified Identifier: {0}", b.ToString(CultureInfo.InvariantCulture));
  732.                     throw new BerDecodeException(sb.ToString(), position);
  733.                 }
  734.  
  735.                 // Next octet must be 0
  736.                 b = GetNextOctet();
  737.                 if (0x00 != b)
  738.                 {
  739.                     var sb = new StringBuilder("Null has non-zero size. ");
  740.                     sb.AppendFormat("Size: {0}", b.ToString(CultureInfo.InvariantCulture));
  741.                     throw new BerDecodeException(sb.ToString(), position);
  742.                 }
  743.  
  744.                 return 0;
  745.             }
  746.             catch (ArgumentOutOfRangeException ex)
  747.             {
  748.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  749.             }
  750.         }
  751.  
  752.         public int NextSequence()
  753.         {
  754.             int position = CurrentPosition();
  755.  
  756.             try
  757.             {
  758.                 byte b = GetNextOctet();
  759.                 if (0x30 != b)
  760.                 {
  761.                     var sb = new StringBuilder("Expected Sequence. ");
  762.                     sb.AppendFormat("Specified Identifier: {0}",
  763.                                     b.ToString(CultureInfo.InvariantCulture));
  764.                     throw new BerDecodeException(sb.ToString(), position);
  765.                 }
  766.  
  767.                 int length = GetLength();
  768.                 if (length > RemainingBytes())
  769.                 {
  770.                     var sb = new StringBuilder("Incorrect Sequence Size. ");
  771.                     sb.AppendFormat("Specified: {0}, Remaining: {1}",
  772.                                     length.ToString(CultureInfo.InvariantCulture),
  773.                                     RemainingBytes().ToString(CultureInfo.InvariantCulture));
  774.                     throw new BerDecodeException(sb.ToString(), position);
  775.                 }
  776.  
  777.                 return length;
  778.             }
  779.             catch (ArgumentOutOfRangeException ex)
  780.             {
  781.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  782.             }
  783.         }
  784.  
  785.         public int NextBitString()
  786.         {
  787.             int position = CurrentPosition();
  788.  
  789.             try
  790.             {
  791.                 byte b = GetNextOctet();
  792.                 if (0x03 != b)
  793.                 {
  794.                     var sb = new StringBuilder("Expected Bit String. ");
  795.                     sb.AppendFormat("Specified Identifier: {0}", b.ToString(CultureInfo.InvariantCulture));
  796.                     throw new BerDecodeException(sb.ToString(), position);
  797.                 }
  798.  
  799.                 int length = GetLength();
  800.  
  801.                 // We need to consume unused bits, which is the first
  802.                 //   octet of the remaing values
  803.                 b = _octets[0];
  804.                 _octets.RemoveAt(0);
  805.                 length--;
  806.  
  807.                 if (0x00 != b)
  808.                 {
  809.                     throw new BerDecodeException("The first octet of BitString must be 0", position);
  810.                 }
  811.  
  812.                 return length;
  813.             }
  814.             catch (ArgumentOutOfRangeException ex)
  815.             {
  816.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  817.             }
  818.         }
  819.  
  820.         public byte[] NextInteger()
  821.         {
  822.             int position = CurrentPosition();
  823.  
  824.             try
  825.             {
  826.                 byte b = GetNextOctet();
  827.                 if (0x02 != b)
  828.                 {
  829.                     var sb = new StringBuilder("Expected Integer. ");
  830.                     sb.AppendFormat("Specified Identifier: {0}", b.ToString(CultureInfo.InvariantCulture));
  831.                     throw new BerDecodeException(sb.ToString(), position);
  832.                 }
  833.  
  834.                 int length = GetLength();
  835.                 if (length > RemainingBytes())
  836.                 {
  837.                     var sb = new StringBuilder("Incorrect Integer Size. ");
  838.                     sb.AppendFormat("Specified: {0}, Remaining: {1}",
  839.                                     length.ToString(CultureInfo.InvariantCulture),
  840.                                     RemainingBytes().ToString(CultureInfo.InvariantCulture));
  841.                     throw new BerDecodeException(sb.ToString(), position);
  842.                 }
  843.  
  844.                 return GetOctets(length);
  845.             }
  846.             catch (ArgumentOutOfRangeException ex)
  847.             {
  848.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  849.             }
  850.         }
  851.  
  852.         public byte[] NextOID()
  853.         {
  854.             int position = CurrentPosition();
  855.  
  856.             try
  857.             {
  858.                 byte b = GetNextOctet();
  859.                 if (0x06 != b)
  860.                 {
  861.                     var sb = new StringBuilder("Expected Object Identifier. ");
  862.                     sb.AppendFormat("Specified Identifier: {0}",
  863.                                     b.ToString(CultureInfo.InvariantCulture));
  864.                     throw new BerDecodeException(sb.ToString(), position);
  865.                 }
  866.  
  867.                 int length = GetLength();
  868.                 if (length > RemainingBytes())
  869.                 {
  870.                     var sb = new StringBuilder("Incorrect Object Identifier Size. ");
  871.                     sb.AppendFormat("Specified: {0}, Remaining: {1}",
  872.                                     length.ToString(CultureInfo.InvariantCulture),
  873.                                     RemainingBytes().ToString(CultureInfo.InvariantCulture));
  874.                     throw new BerDecodeException(sb.ToString(), position);
  875.                 }
  876.  
  877.                 var values = new byte[length];
  878.  
  879.                 for (int i = 0; i < length; i++)
  880.                 {
  881.                     values[i] = _octets[0];
  882.                     _octets.RemoveAt(0);
  883.                 }
  884.  
  885.                 return values;
  886.             }
  887.             catch (ArgumentOutOfRangeException ex)
  888.             {
  889.                 throw new BerDecodeException("Error Parsing Key", position, ex);
  890.             }
  891.         }
  892.     }
  893.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  894.     // Package.cs
  895.     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  896.     /*
  897.      * Read() function was mostly taken from Rick's Gibbed.Valve.FileFormats,
  898.      * which is subject to this license:
  899.      *
  900.      * Copyright (c) 2008 Rick (rick 'at' gibbed 'dot' us)
  901.      *
  902.      * This software is provided 'as-is', without any express or implied
  903.      * warranty. In no event will the authors be held liable for any damages
  904.      * arising from the use of this software.
  905.      *
  906.      * Permission is granted to anyone to use this software for any purpose,
  907.      * including commercial applications, and to alter it and redistribute it
  908.      * freely, subject to the following restrictions:
  909.      *
  910.      * 1. The origin of this software must not be misrepresented; you must not
  911.      * claim that you wrote the original software. If you use this software
  912.      * in a product, an acknowledgment in the product documentation would be
  913.      * appreciated but is not required.
  914.      *
  915.      * 2. Altered source versions must be plainly marked as such, and must not be
  916.      * misrepresented as being the original software.
  917.      *
  918.      * 3. This notice may not be removed or altered from any source
  919.      * distribution.
  920.      */
  921.     public class Package : IDisposable
  922.     {
  923.         public const int MAGIC = 0x55AA1234;
  924.  
  925.         /// <summary>
  926.         /// Always '/' as per Valve's vpk implementation.
  927.         /// </summary>
  928.         public const char DirectorySeparatorChar = '/';
  929.  
  930.         private BinaryReader Reader;
  931.         private bool IsDirVPK;
  932.         private uint HeaderSize;
  933.  
  934.         /// <summary>
  935.         /// Gets the File Name
  936.         /// </summary>
  937.         public string FileName { get; private set; }
  938.  
  939.         /// <summary>
  940.         /// Gets the VPK version.
  941.         /// </summary>
  942.         public uint Version { get; private set; }
  943.  
  944.         /// <summary>
  945.         /// Gets the size in bytes of the directory tree.
  946.         /// </summary>
  947.         public uint TreeSize { get; private set; }
  948.  
  949.         /// <summary>
  950.         /// Gets how many bytes of file content are stored in this VPK file (0 in CSGO).
  951.         /// </summary>
  952.         public uint FileDataSectionSize { get; private set; }
  953.  
  954.         /// <summary>
  955.         /// Gets the size in bytes of the section containing MD5 checksums for external archive content.
  956.         /// </summary>
  957.         public uint ArchiveMD5SectionSize { get; private set; }
  958.  
  959.         /// <summary>
  960.         /// Gets the size in bytes of the section containing MD5 checksums for content in this file.
  961.         /// </summary>
  962.         public uint OtherMD5SectionSize { get; private set; }
  963.  
  964.         /// <summary>
  965.         /// Gets the size in bytes of the section containing the public key and signature.
  966.         /// </summary>
  967.         public uint SignatureSectionSize { get; private set; }
  968.  
  969.         /// <summary>
  970.         /// Gets the MD5 checksum of the file tree.
  971.         /// </summary>
  972.         public byte[] TreeChecksum { get; private set; }
  973.  
  974.         /// <summary>
  975.         /// Gets the MD5 checksum of the archive MD5 checksum section entries.
  976.         /// </summary>
  977.         public byte[] ArchiveMD5EntriesChecksum { get; private set; }
  978.  
  979.         /// <summary>
  980.         /// Gets the MD5 checksum of the complete package until the signature structure.
  981.         /// </summary>
  982.         public byte[] WholeFileChecksum { get; private set; }
  983.  
  984.         /// <summary>
  985.         /// Gets the public key.
  986.         /// </summary>
  987.         public byte[] PublicKey { get; private set; }
  988.  
  989.         /// <summary>
  990.         /// Gets the signature.
  991.         /// </summary>
  992.         public byte[] Signature { get; private set; }
  993.  
  994.         /// <summary>
  995.         /// Gets the package entries.
  996.         /// </summary>
  997.         public Dictionary<string, List<PackageEntry>> Entries { get; private set; }
  998.  
  999.         /// <summary>
  1000.         /// Gets the archive MD5 checksum section entries. Also known as cache line hashes.
  1001.         /// </summary>
  1002.         public List<ArchiveMD5SectionEntry> ArchiveMD5Entries { get; private set; }
  1003.  
  1004.         /// <summary>
  1005.         /// Releases binary reader.
  1006.         /// </summary>
  1007.         public void Dispose()
  1008.         {
  1009.             Dispose(true);
  1010.             GC.SuppressFinalize(this);
  1011.         }
  1012.  
  1013.         protected virtual void Dispose(bool disposing)
  1014.         {
  1015.             if (disposing && Reader != null)
  1016.             {
  1017.                 ((IDisposable)Reader).Dispose();
  1018.                 Reader = null;
  1019.             }
  1020.         }
  1021.  
  1022.         /// <summary>
  1023.         /// Sets the file name.
  1024.         /// </summary>
  1025.         /// <param name="fileName">Filename.</param>
  1026.         public void SetFileName(string fileName)
  1027.         {
  1028.             if (fileName.EndsWith(".vpk", StringComparison.Ordinal))
  1029.             {
  1030.                 fileName = fileName.Substring(0, fileName.Length - 4);
  1031.             }
  1032.  
  1033.             if (fileName.EndsWith("_dir", StringComparison.Ordinal))
  1034.             {
  1035.                 IsDirVPK = true;
  1036.  
  1037.                 fileName = fileName.Substring(0, fileName.Length - 4);
  1038.             }
  1039.  
  1040.             FileName = fileName;
  1041.         }
  1042.  
  1043.         /// <summary>
  1044.         /// Opens and reads the given filename.
  1045.         /// The file is held open until the object is disposed.
  1046.         /// </summary>
  1047.         /// <param name="filename">The file to open and read.</param>
  1048.         public void Read(string filename)
  1049.         {
  1050.             SetFileName(filename);
  1051.  
  1052.             var fs = new FileStream(String.Format("{0}{1}.vpk", FileName,(IsDirVPK ? "_dir" : string.Empty)), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  1053.             //var fs = new FileStream($"{FileName}{(IsDirVPK ? "_dir" : string.Empty)}.vpk", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  1054.  
  1055.             Read(fs);
  1056.         }
  1057.  
  1058.         /// <summary>
  1059.         /// Reads the given <see cref="Stream"/>.
  1060.         /// </summary>
  1061.         /// <param name="input">The input <see cref="Stream"/> to read from.</param>
  1062.         public void Read(Stream input)
  1063.         {
  1064.             if (FileName == null)
  1065.             {
  1066.                 throw new InvalidOperationException("If you call Read() directly with a stream, you must call SetFileName() first.");
  1067.             }
  1068.  
  1069.             Reader = new BinaryReader(input);
  1070.  
  1071.             if (Reader.ReadUInt32() != MAGIC)
  1072.             {
  1073.                 throw new InvalidDataException("Given file is not a VPK.");
  1074.             }
  1075.  
  1076.             Version = Reader.ReadUInt32();
  1077.             TreeSize = Reader.ReadUInt32();
  1078.  
  1079.             if (Version == 1)
  1080.             {
  1081.                 // Nothing else
  1082.             }
  1083.             else if (Version == 2)
  1084.             {
  1085.                 FileDataSectionSize = Reader.ReadUInt32();
  1086.                 ArchiveMD5SectionSize = Reader.ReadUInt32();
  1087.                 OtherMD5SectionSize = Reader.ReadUInt32();
  1088.                 SignatureSectionSize = Reader.ReadUInt32();
  1089.             }
  1090.             else
  1091.             {
  1092.                 throw new InvalidDataException(string.Format("Bad VPK version. ({0})", Version));
  1093.             }
  1094.  
  1095.             HeaderSize = (uint)input.Position;
  1096.  
  1097.             ReadEntries();
  1098.  
  1099.             if (Version == 2)
  1100.             {
  1101.                 // Skip over file data, if any
  1102.                 input.Position += FileDataSectionSize;
  1103.  
  1104.                 ReadArchiveMD5Section();
  1105.                 ReadOtherMD5Section();
  1106.                 ReadSignatureSection();
  1107.             }
  1108.         }
  1109.  
  1110.         /// <summary>
  1111.         /// Searches for a given file entry in the file list.
  1112.         /// </summary>
  1113.         /// <param name="filePath">Full path to the file to find.</param>
  1114.         public PackageEntry FindEntry(string filePath)
  1115.         {
  1116.             filePath = filePath.Replace('\\', DirectorySeparatorChar);
  1117.  
  1118.             // Even though technically we are passing in full path as file name, relevant functions in next overload fix it
  1119.             return FindEntry(Path.GetDirectoryName(filePath), filePath);
  1120.         }
  1121.  
  1122.         /// <summary>
  1123.         /// Searches for a given file entry in the file list.
  1124.         /// </summary>
  1125.         /// <param name="directory">Directory to search in.</param>
  1126.         /// <param name="fileName">File name to find.</param>
  1127.         public PackageEntry FindEntry(string directory, string fileName)
  1128.         {
  1129.             fileName = fileName.Replace('\\', DirectorySeparatorChar);
  1130.  
  1131.             return FindEntry(directory, Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName).TrimStart('.'));
  1132.         }
  1133.  
  1134.         /// <summary>
  1135.         /// Searches for a given file entry in the file list.
  1136.         /// </summary>
  1137.         /// <param name="directory">Directory to search in.</param>
  1138.         /// <param name="fileName">File name to find, without the extension.</param>
  1139.         /// <param name="extension">File extension, without the leading dot.</param>
  1140.         public PackageEntry FindEntry(string directory, string fileName, string extension)
  1141.         {
  1142.             // Assume no extension
  1143.             if (extension == null)
  1144.             {
  1145.                 extension = string.Empty;
  1146.             }
  1147.  
  1148.             if (!Entries.ContainsKey(extension))
  1149.             {
  1150.                 return null;
  1151.             }
  1152.  
  1153.             // We normalize path separators when reading the file list
  1154.             directory = directory.Replace('\\', DirectorySeparatorChar).Trim(DirectorySeparatorChar);
  1155.  
  1156.             // If the directory is empty after trimming, set it to null
  1157.             if (directory == string.Empty)
  1158.             {
  1159.                 directory = null;
  1160.             }
  1161.  
  1162.           //return Entries[extension].FirstOrDefault(x => x.DirectoryName == directory && x.FileName == fileName);
  1163.             foreach (PackageEntry x in Entries[extension])
  1164.             {
  1165.                 if (x.DirectoryName == directory && x.FileName == fileName)
  1166.                 {
  1167.                     return x;
  1168.                 }
  1169.             }
  1170.             return null;
  1171.         }
  1172.  
  1173.         /// <summary>
  1174.         /// Reads the entry from the VPK package.
  1175.         /// </summary>
  1176.         /// <param name="entry">Package entry.</param>
  1177.         /// <param name="output">Output buffer.</param>
  1178.         /// <param name="validateCrc">If true, CRC32 will be calculated and verified for read data.</param>
  1179.         public void ReadEntry(PackageEntry entry, out byte[] output)
  1180.         {
  1181.             ReadEntry(entry, out output, true);
  1182.         }
  1183.         public void ReadEntry(PackageEntry entry, out byte[] output, bool validateCrc)
  1184.         {
  1185.             output = new byte[entry.SmallData.Length + entry.Length];
  1186.  
  1187.             if (entry.SmallData.Length > 0)
  1188.             {
  1189.                 entry.SmallData.CopyTo(output, 0);
  1190.             }
  1191.  
  1192.             if (entry.Length > 0)
  1193.             {
  1194.                 Stream fs = null;
  1195.  
  1196.                 try
  1197.                 {
  1198.                     var offset = entry.Offset;
  1199.  
  1200.                     if (entry.ArchiveIndex != 0x7FFF)
  1201.                     {
  1202.                         if (!IsDirVPK)
  1203.                         {
  1204.                             throw new InvalidOperationException("Given VPK is not a _dir, but entry is referencing an external archive.");
  1205.                         }
  1206.  
  1207.                         //var fileName = FileName+"_"+entry.ArchiveIndex+".vpk";
  1208.                         var fileName = String.Format("{0}_{1:d3}.vpk",FileName,entry.ArchiveIndex);
  1209.  
  1210.                         fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
  1211.                     }
  1212.                     else
  1213.                     {
  1214.                         fs = Reader.BaseStream;
  1215.  
  1216.                         offset += HeaderSize + TreeSize;
  1217.                     }
  1218.  
  1219.                     fs.Seek(offset, SeekOrigin.Begin);
  1220.                     fs.Read(output, entry.SmallData.Length, (int)entry.Length);
  1221.                 }
  1222.                 finally
  1223.                 {
  1224.                     if (entry.ArchiveIndex != 0x7FFF)
  1225.                     {
  1226.                         fs.Close();
  1227.                     }
  1228.                 }
  1229.             }
  1230.  
  1231.             if (validateCrc && entry.CRC32 != Crc32.Compute(output))
  1232.             {
  1233.                 throw new InvalidDataException("CRC32 mismatch for read data.");
  1234.             }
  1235.         }
  1236.  
  1237.         private void ReadEntries()
  1238.         {
  1239.             var typeEntries = new Dictionary<string, List<PackageEntry>>();
  1240.  
  1241.             // Types
  1242.             while (true)
  1243.             {
  1244.                 var typeName = Reader.ReadNullTermString(Encoding.UTF8);
  1245.  
  1246.                 if (typeName == string.Empty)
  1247.                 {
  1248.                     break;
  1249.                 }
  1250.  
  1251.                 // Valve uses a space for missing extensions,
  1252.                 // we replace it with an empty string to match how System.IO.Path deals with it.
  1253.                 if (typeName == " ")
  1254.                 {
  1255.                     typeName = string.Empty;
  1256.                 }
  1257.  
  1258.                 var entries = new List<PackageEntry>();
  1259.  
  1260.                 // Directories
  1261.                 while (true)
  1262.                 {
  1263.                     var directoryName = Reader.ReadNullTermString(Encoding.UTF8);
  1264.  
  1265.                     if (directoryName == string.Empty)
  1266.                     {
  1267.                         break;
  1268.                     }
  1269.  
  1270.                     // Valve uses a space for blank directory names,
  1271.                     // we replace it with a null to match how System.IO.Path deals with root paths.
  1272.                     if (directoryName == " ")
  1273.                     {
  1274.                         directoryName = null;
  1275.                     }
  1276.  
  1277.                     // Files
  1278.                     while (true)
  1279.                     {
  1280.                         var fileName = Reader.ReadNullTermString(Encoding.UTF8);
  1281.  
  1282.                         if (fileName == string.Empty)
  1283.                         {
  1284.                             break;
  1285.                         }
  1286.  
  1287.                         var entry = new PackageEntry
  1288.                         {
  1289.                             FileName = fileName,
  1290.                             DirectoryName = directoryName,
  1291.                             TypeName = typeName,
  1292.                             CRC32 = Reader.ReadUInt32(),
  1293.                             SmallData = new byte[Reader.ReadUInt16()],
  1294.                             ArchiveIndex = Reader.ReadUInt16(),
  1295.                             Offset = Reader.ReadUInt32(),
  1296.                             Length = Reader.ReadUInt32()
  1297.                         };
  1298.  
  1299.                         if (Reader.ReadUInt16() != 0xFFFF)
  1300.                         {
  1301.                             throw new FormatException("Invalid terminator.");
  1302.                         }
  1303.  
  1304.                         if (entry.SmallData.Length > 0)
  1305.                         {
  1306.                             Reader.Read(entry.SmallData, 0, entry.SmallData.Length);
  1307.                         }
  1308.  
  1309.                         entries.Add(entry);
  1310.                     }
  1311.                 }
  1312.  
  1313.                 typeEntries.Add(typeName, entries);
  1314.             }
  1315.  
  1316.             Entries = typeEntries;
  1317.         }
  1318.  
  1319.         /// <summary>
  1320.         /// Verify checksums and signatures provided in the VPK
  1321.         /// </summary>
  1322.         public void VerifyHashes()
  1323.         {
  1324.             if (Version != 2)
  1325.             {
  1326.                 throw new InvalidDataException("Only version 2 is supported.");
  1327.             }
  1328.  
  1329.             using (var md5 = MD5.Create())
  1330.             {
  1331.                 Reader.BaseStream.Position = 0;
  1332.  
  1333.                 var hash = md5.ComputeHash(Reader.ReadBytes((int)(HeaderSize + TreeSize + FileDataSectionSize + ArchiveMD5SectionSize + 32)));
  1334.  
  1335.                 if (!hash.SequenceEqual(WholeFileChecksum))
  1336.                 {
  1337.                     //throw new InvalidDataException($"Package checksum mismatch ({BitConverter.ToString(hash)} != expected {BitConverter.ToString(WholeFileChecksum)})");
  1338.                     throw new InvalidDataException(String.Format("Package checksum mismatch ({0} != expected {1})", BitConverter.ToString(hash), BitConverter.ToString(WholeFileChecksum)));
  1339.                 }
  1340.  
  1341.                 Reader.BaseStream.Position = HeaderSize;
  1342.  
  1343.                 hash = md5.ComputeHash(Reader.ReadBytes((int)TreeSize));
  1344.  
  1345.                 if (!hash.SequenceEqual(TreeChecksum))
  1346.                 {
  1347.                     //throw new InvalidDataException($"File tree checksum mismatch ({BitConverter.ToString(hash)} != expected {BitConverter.ToString(TreeChecksum)})");
  1348.                     throw new InvalidDataException(String.Format("File tree checksum mismatch ({0} != expected {1})", BitConverter.ToString(hash), BitConverter.ToString(TreeChecksum)));
  1349.                 }
  1350.  
  1351.                 Reader.BaseStream.Position = HeaderSize + TreeSize + FileDataSectionSize;
  1352.  
  1353.                 hash = md5.ComputeHash(Reader.ReadBytes((int)ArchiveMD5SectionSize));
  1354.  
  1355.                 if (!hash.SequenceEqual(ArchiveMD5EntriesChecksum))
  1356.                 {
  1357.                     //throw new InvalidDataException($"Archive MD5 entries checksum mismatch ({BitConverter.ToString(hash)} != expected {BitConverter.ToString(ArchiveMD5EntriesChecksum)})");
  1358.                     throw new InvalidDataException(String.Format("Archive MD5 entries checksum mismatch ({0} != expected {1})", BitConverter.ToString(hash), BitConverter.ToString(ArchiveMD5EntriesChecksum)));
  1359.                 }
  1360.  
  1361.                 // TODO: verify archive checksums
  1362.             }
  1363.  
  1364.             if (PublicKey == null || Signature == null)
  1365.             {
  1366.                 return;
  1367.             }
  1368.  
  1369.             if (!IsSignatureValid())
  1370.             {
  1371.                 throw new InvalidDataException("VPK signature is not valid.");
  1372.             }
  1373.         }
  1374.  
  1375.         /// <summary>
  1376.         /// Verifies the RSA signature.
  1377.         /// </summary>
  1378.         /// <returns>True if signature is valid, false otherwise.</returns>
  1379.         public bool IsSignatureValid()
  1380.         {
  1381.             Reader.BaseStream.Position = 0;
  1382.  
  1383.             var keyParser = new AsnKeyParser(PublicKey);
  1384.  
  1385.             var rsa = new RSACryptoServiceProvider();
  1386.             rsa.ImportParameters(keyParser.ParseRSAPublicKey());
  1387.  
  1388.             var deformatter = new RSAPKCS1SignatureDeformatter(rsa);
  1389.             deformatter.SetHashAlgorithm("SHA256");
  1390.  
  1391.             var hash = new SHA256Managed().ComputeHash(Reader.ReadBytes((int)(HeaderSize + TreeSize + FileDataSectionSize + ArchiveMD5SectionSize + OtherMD5SectionSize)));
  1392.  
  1393.             return deformatter.VerifySignature(hash, Signature);
  1394.         }
  1395.  
  1396.         private void ReadArchiveMD5Section()
  1397.         {
  1398.             ArchiveMD5Entries = new List<ArchiveMD5SectionEntry>();
  1399.  
  1400.             if (ArchiveMD5SectionSize == 0)
  1401.             {
  1402.                 return;
  1403.             }
  1404.  
  1405.             var entries = ArchiveMD5SectionSize / 28; // 28 is sizeof(VPK_MD5SectionEntry), which is int + int + int + 16 chars
  1406.  
  1407.             for (var i = 0; i < entries; i++)
  1408.             {
  1409.                 ArchiveMD5Entries.Add(new ArchiveMD5SectionEntry
  1410.                 {
  1411.                     ArchiveIndex = Reader.ReadUInt32(),
  1412.                     Offset = Reader.ReadUInt32(),
  1413.                     Length = Reader.ReadUInt32(),
  1414.                     Checksum = Reader.ReadBytes(16)
  1415.                 });
  1416.             }
  1417.         }
  1418.  
  1419.         private void ReadOtherMD5Section()
  1420.         {
  1421.             if (OtherMD5SectionSize != 48)
  1422.             {
  1423.                 //throw new InvalidDataException($"Encountered OtherMD5Section with size of {OtherMD5SectionSize} (should be 48)");
  1424.                 throw new InvalidDataException(String.Format("Encountered OtherMD5Section with size of {0} (should be 48)", OtherMD5SectionSize));
  1425.             }
  1426.  
  1427.             TreeChecksum = Reader.ReadBytes(16);
  1428.             ArchiveMD5EntriesChecksum = Reader.ReadBytes(16);
  1429.             WholeFileChecksum = Reader.ReadBytes(16);
  1430.         }
  1431.  
  1432.         private void ReadSignatureSection()
  1433.         {
  1434.             if (SignatureSectionSize == 0)
  1435.             {
  1436.                 return;
  1437.             }
  1438.  
  1439.             var publicKeySize = Reader.ReadInt32();
  1440.             PublicKey = Reader.ReadBytes(publicKeySize);
  1441.  
  1442.             var signatureSize = Reader.ReadInt32();
  1443.             Signature = Reader.ReadBytes(signatureSize);
  1444.         }
  1445.     }
  1446. }
  1447.  
  1448. namespace VPKMOD
  1449. {
  1450.     public class Options
  1451.     {
  1452.         public string Input = string.Empty;
  1453.         public bool Recursive = false;
  1454.         public string Output = string.Empty;
  1455.         public List<string> ExtFilter = new List<string>();
  1456.         public List<string> PathFilter = new List<string>();
  1457.         public string FilterList = string.Empty;
  1458.         public string ModList = string.Empty;
  1459.         public bool OutputVPKDir = false;
  1460.         public bool CachedManifest = false;
  1461.         public bool VerifyVPKChecksums = false;
  1462.         public bool Silent = false;
  1463.         public bool Help = false;
  1464.         public Options()
  1465.         {
  1466.             Parsed = new Dictionary< string, List<string> >();
  1467.         }
  1468.         public IDictionary< string, List<string> > Parsed { get; private set; }
  1469.         public bool Find(string key)
  1470.         {
  1471.             return Find(key, false);
  1472.         }
  1473.         public bool Find(string key, bool keyonly)
  1474.         {
  1475.             if (Parsed.ContainsKey(key))
  1476.             {
  1477.                 if (keyonly) return true;
  1478.                 if (Parsed[key].Count != 0) return true;
  1479.                 Console.Error.WriteLine("VPKMOD ERROR! Missing argument for: -{0}", key);
  1480.             }
  1481.             return false;
  1482.         }
  1483.         public void Parse(string[] args)
  1484.         {
  1485.             var key = "";
  1486.             List<string> values = new List<string>();
  1487.             foreach (string arg in args)
  1488.             {
  1489.                 if (arg.StartsWith("-"))
  1490.                 {
  1491.                     if (key != "") Parsed[key] = values;
  1492.                     values =  new List<string>();
  1493.                     key = arg.Substring(1);
  1494.                 }
  1495.                 else if (key == "") Parsed[arg] = new List<string>();
  1496.                 else values = new List<string>(arg.Split(','));
  1497.             }
  1498.             if (key != "") Parsed[key] = values;
  1499.  
  1500.             if (Find("i"))
  1501.             {
  1502.                 Input = Parsed["i"].FirstOrDefault();
  1503.                 Console.WriteLine("VPKMOD -i Input:      {0}", Input);
  1504.             }
  1505.             if (Find("r", true))
  1506.             {
  1507.                 Recursive = true;
  1508.                 Console.WriteLine("VPKMOD -r Recursive:  {0} (if input is a folder)", Recursive);
  1509.             }
  1510.             if (Find("o"))
  1511.             {
  1512.                 Output = Parsed["o"].FirstOrDefault();
  1513.                 Console.WriteLine("VPKMOD -o Output:     {0}", Output);
  1514.             }
  1515.             if (Find("e"))
  1516.             {
  1517.                 ExtFilter = Parsed["e"];
  1518.                 Console.WriteLine("VPKMOD -e ExtFilter:  {0}", string.Join(", ", ExtFilter.ToArray()));
  1519.             }
  1520.             if (Find("p"))
  1521.             {
  1522.                 PathFilter = Parsed["p"];
  1523.                 Console.WriteLine("VPKMOD -p PathFilter: {0}", string.Join(", ", PathFilter.ToArray()));
  1524.             }
  1525.             if (Find("l"))
  1526.             {
  1527.                 FilterList = Parsed["l"].FirstOrDefault();
  1528.                 Console.WriteLine("VPKMOD -l Filter.lst: {0}", FilterList);
  1529.             }
  1530.             if (Find("m"))
  1531.             {
  1532.                 ModList = Parsed["m"].FirstOrDefault();
  1533.                 Console.WriteLine("VPKMOD -m Mod.lst:    {0}", ModList);
  1534.             }
  1535.             if (Find("d", true))
  1536.             {
  1537.                 OutputVPKDir = true;
  1538.                 Console.WriteLine("VPKMOD -d OutputVPKDir: {0}", OutputVPKDir);
  1539.             }
  1540.             if (Find("c", true))
  1541.             {
  1542.                 CachedManifest = true;
  1543.                 Console.WriteLine("VPKMOD -c CachedManifest: {0}", CachedManifest);
  1544.             }
  1545.             if (Find("v", true))
  1546.             {
  1547.                 VerifyVPKChecksums = true;
  1548.                 Console.WriteLine("VPKMOD -v VerifyVPKChecksums: {0}", VerifyVPKChecksums);
  1549.             }
  1550.             if (Find("s", true))
  1551.             {
  1552.                 Silent = true;
  1553.                 Console.WriteLine("VPKMOD -s Silent:     {0}", Silent);
  1554.             }
  1555.             if (Find("h", true) || args.Length == 0)
  1556.             {
  1557.                 Console.WriteLine("VPKMOD v1.0 \t AveYo / SteamDatabase");
  1558.                 Console.WriteLine(" -i  Input VPK file (unpak) or directory (pak)");
  1559.                 Console.WriteLine(" -r  Process all files in subdirectories (pak)");
  1560.                 Console.WriteLine(" -o  Output directory (unpak) or file.vpk (pak)");
  1561.                 Console.WriteLine(" -e  Extension(s) filter, example: \"vcss_c,vjs_c,vxml_c\"");
  1562.                 Console.WriteLine(" -p  Path(s) filter, example: \"particles/,models/\"");
  1563.                 Console.WriteLine(" -l  Import filters from external.lst,");
  1564.                 Console.WriteLine("     if -e or -p are also used, export filters");
  1565.                 Console.WriteLine(" -m  Import mod?src definitions from external.lst");
  1566.                 Console.WriteLine("     allowing in-memory unpak-rename-pak modding");
  1567.                 Console.WriteLine(" -d  Export VPK directory of files and their CRC");
  1568.                 Console.WriteLine(" -c  Use cached VPK manifest - only changed files get written to disk");
  1569.                 Console.WriteLine(" -v  Verify checksums and signatures");
  1570.                 Console.WriteLine(" -t  Parallel processing threads count");
  1571.                 Console.WriteLine(" -s  Silent processing");
  1572.                 Console.WriteLine(" -h  This help screen");
  1573.                 Console.WriteLine("Press ENTER to quit");
  1574.                 Console.ReadLine();
  1575.                 Environment.Exit(0);
  1576.             }
  1577.         }
  1578.     }
  1579.  
  1580.     public class Tree<TKey, TValue> : Dictionary<TKey, TValue> {}
  1581.     public class Tree<TKey1, TKey2, TValue> : Dictionary<TKey1, Tree<TKey2, TValue>> {}
  1582.     public class Tree<TKey1, TKey2, TKey3, TValue> : Dictionary<TKey1, Tree<TKey2, TKey3, TValue>> {}
  1583.     public static class TreeExtensions
  1584.     {
  1585.         public static Tree<TKey2, TValue> New<TKey1, TKey2, TValue>(this Tree<TKey1, TKey2, TValue> dictionary)
  1586.         {
  1587.             return new Tree<TKey2, TValue>();
  1588.         }
  1589.         public static Tree<TKey2, TKey3, TValue> New<TKey1,TKey2,TKey3,TValue>(this Tree<TKey1,TKey2,TKey3,TValue> dictionary)
  1590.         {
  1591.             return new Tree<TKey2, TKey3, TValue>();
  1592.         }
  1593.     }
  1594.  
  1595.     class Program
  1596.     {
  1597.         private static readonly object ConsoleWriterLock = new object();
  1598.         private static Options Options;
  1599.         private static int CurrentFile = 0;
  1600.         private static int TotalFiles = 0;
  1601.         private static Dictionary<string, uint> OldPakManifest = new Dictionary<string, uint>();
  1602.         private static Dictionary<string, Dictionary<string, bool>> ModSrc = new Dictionary<string, Dictionary<string, bool>>();
  1603.         private static Dictionary<string, string> SrcMod = new Dictionary<string, string>();
  1604.         private static List<string> FileFilter = new List<string>();
  1605.         private static List<string> ExtFilter = new List<string>();
  1606.         private static bool ExportFilter = false;
  1607.  
  1608.         public static void Main(string[] args)
  1609.         {
  1610.             Options = new Options();
  1611.             Options.Parse(args);
  1612.  
  1613.             if (String.IsNullOrEmpty(Options.Input))
  1614.             {
  1615.                 Echo("Missing -i input parameter!", ConsoleColor.Red);
  1616.                 return;
  1617.             }
  1618.             Options.Input = FixPathSlashes(Path.GetFullPath(Options.Input));
  1619.  
  1620.             if (!String.IsNullOrEmpty(Options.Output))
  1621.             {
  1622.                 Options.Output = FixPathSlashes(Path.GetFullPath(Options.Output));
  1623.             }
  1624.  
  1625.             if (!String.IsNullOrEmpty(Options.ModList))
  1626.             {
  1627.                 Options.ModList = FixPathSlashes(Path.GetFullPath(Options.ModList));
  1628.                 if (File.Exists(Options.ModList))
  1629.                 {
  1630.                     var file = new StreamReader(Options.ModList);
  1631.                     string line, ext, mod, src;
  1632.                     Dictionary<string, bool> m = new Dictionary<string, bool>();
  1633.                     while ((line = file.ReadLine()) != null)
  1634.                     {
  1635.                         var split = line.Split(new[] { '?' }, 2);
  1636.                         if (split.Length == 2)
  1637.                         {
  1638.                             mod = FixPathSlashes(split[0]);
  1639.                             src = FixPathSlashes(split[1]);
  1640.                             FileFilter.Add(src);
  1641.                             ext = Path.GetExtension(src);
  1642.                             if (ext.Length > 1) ExtFilter.Add(ext.Substring(1));
  1643.  
  1644.                             SrcMod[src] =  mod;
  1645.  
  1646.                             if (!ModSrc.ContainsKey(src))
  1647.                                 ModSrc.Add(src, new Dictionary<string, bool> { {mod , false} });
  1648.                             else
  1649.                               ModSrc[src].Add(mod, false);
  1650.                         }
  1651.                     }
  1652.                     file.Close();
  1653.                 }
  1654.             }
  1655.             else if (!String.IsNullOrEmpty(Options.FilterList))
  1656.             {
  1657.                 Options.FilterList = FixPathSlashes(Path.GetFullPath(Options.FilterList));
  1658.                 if (File.Exists(Options.FilterList))
  1659.                 {
  1660.                     var file = new StreamReader(Options.FilterList);
  1661.                     string line, ext;
  1662.                     while ((line = file.ReadLine()) != null)
  1663.                     {
  1664.                         FileFilter.Add(FixPathSlashes(line));
  1665.                         ext = Path.GetExtension(line);
  1666.                         if (ext.Length > 1) ExtFilter.Add(ext.Substring(1));
  1667.                     }
  1668.                     file.Close();
  1669.                 }
  1670.                 if (Options.PathFilter.Count > 0 || Options.ExtFilter.Count > 0)
  1671.                 {
  1672.                      ExportFilter = true;
  1673.                 }
  1674.             }
  1675.  
  1676.             if (Options.PathFilter.Count > 0)
  1677.             {
  1678.                 foreach (string filter in Options.PathFilter.ToList())
  1679.                 {
  1680.                     int index = Options.PathFilter.IndexOf(filter);
  1681.                     if (index != -1) Options.PathFilter[index] = FixPathSlashes(filter);
  1682.                 }
  1683.             }
  1684.  
  1685.             var paths = new List<string>();
  1686.  
  1687.             if (Directory.Exists(Options.Input))
  1688.             {
  1689.                 if (Path.GetExtension(Options.Output).ToLower() != ".vpk")
  1690.                 {
  1691.                     Echo(String.Format("Input \"{0}\" is a directory while Output \"{1}\" is not a VPK.", Options.Input, Options.Output), ConsoleColor.Red);
  1692.                     return;
  1693.                 }
  1694.                 paths.AddRange(Directory.GetFiles(Options.Input, "*.*", Options.Recursive ? SearchOption.AllDirectories : !!))
  1695.                 if (paths.Count == 0)
  1696.                 {
  1697.                     Echo(String.Format("No such file \"{0}\" or directory is empty. Did you mean to include -r (recursive) parameter?", Options.Input), ConsoleColor.Red);
  1698.                     return;
  1699.                 }
  1700.                 WriteVPK(paths, false); // pak directory into output.vpk
  1701.             }
  1702.             else if (File.Exists(Options.Input))
  1703.             {
  1704.                 if (Path.GetExtension(Options.Input).ToLower() != ".vpk")
  1705.                 {
  1706.                     Echo(String.Format("Input \"{0}\" is not a VPK.", Options.Input), ConsoleColor.Red);
  1707.                     return;
  1708.                 }
  1709.                 paths.Add(Options.Input);
  1710.                 if (Path.GetExtension(Options.Output).ToLower() != ".vpk")
  1711.                 {
  1712.                     ReadVPK(Options.Input); // unpak input.vpk into output dir
  1713.                 }
  1714.                 else
  1715.                 {
  1716.                     WriteVPK(paths, true); // mod input.vpk into output.vpk
  1717.                 }
  1718.             }
  1719.  
  1720.             CurrentFile = 0;
  1721.             TotalFiles = paths.Count;
  1722.         }
  1723.  
  1724.         private static void ReadVPK(string path)
  1725.         {
  1726.             Echo(String.Format("--- Listing files in package \"{0}\"", path), ConsoleColor.Green);
  1727.             var sw = Stopwatch.StartNew();
  1728.             var package = new Package();
  1729.             try
  1730.             {
  1731.                 package.Read(path);
  1732.             }
  1733.             catch (Exception e)
  1734.             {
  1735.                 Echo(e.ToString(), ConsoleColor.Yellow);
  1736.             }
  1737.  
  1738.             if (Options.VerifyVPKChecksums)
  1739.             {
  1740.                 try
  1741.                 {
  1742.                     package.VerifyHashes();
  1743.  
  1744.                     Console.WriteLine("VPK verification succeeded");
  1745.                 }
  1746.                 catch (Exception)
  1747.                 {
  1748.                     Echo("Failed to verify checksums and signature of given VPK:", ConsoleColor.Red);
  1749.                 }
  1750.                 return;
  1751.             }
  1752.  
  1753.             if (!String.IsNullOrEmpty(Options.Output) && !Options.OutputVPKDir)
  1754.             {
  1755.               //Console.WriteLine("--- Reading VPK files...");
  1756.                 var manifestPath = string.Concat(path, ".manifest.txt");
  1757.                 if (Options.CachedManifest && File.Exists(manifestPath))
  1758.                 {
  1759.                     var file = new StreamReader(manifestPath);
  1760.                     string line;
  1761.                     while ((line = file.ReadLine()) != null)
  1762.                     {
  1763.                         var split = line.Split(new[] { ' ' }, 2);
  1764.                         if (split.Length == 2)
  1765.                         {
  1766.                             OldPakManifest.Add(split[1], uint.Parse(split[0]));
  1767.                         }
  1768.                     }
  1769.                     file.Close();
  1770.                 }
  1771.  
  1772.                 foreach (var type in package.Entries)
  1773.                 {
  1774.                     if (ExtFilter.Count > 0 && !ExtFilter.Contains(type.Key))
  1775.                     {
  1776.                         continue;
  1777.                     }
  1778.                     else if (Options.ExtFilter.Count > 0 && !Options.ExtFilter.Contains(type.Key))
  1779.                     {
  1780.                         continue;
  1781.                     }
  1782.                     DumpVPK(package, type.Key);
  1783.                 }
  1784.  
  1785.                 if (Options.CachedManifest)
  1786.                 {
  1787.                     using (var file = new StreamWriter(manifestPath))
  1788.                     {
  1789.                         foreach (var hash in OldPakManifest)
  1790.                         {
  1791.                             if (package.FindEntry(hash.Key) == null)
  1792.                             {
  1793.                                 Console.WriteLine("\t{0} no longer exists in VPK", hash.Key);
  1794.                             }
  1795.                             file.WriteLine("{0} {1}", hash.Value, hash.Key);
  1796.                         }
  1797.                     }
  1798.                 }
  1799.             }
  1800.  
  1801.             if (Options.OutputVPKDir)
  1802.             {
  1803.                 foreach (var type in package.Entries)
  1804.                 {
  1805.                     foreach (var file in type.Value)
  1806.                     {
  1807.                         Console.WriteLine(file);
  1808.                     }
  1809.                 }
  1810.             }
  1811.  
  1812.             if (ExportFilter)
  1813.             {
  1814.                 using (var filter = new StreamWriter(Options.FilterList))
  1815.                 {
  1816.                     foreach (var type in package.Entries)
  1817.                     {
  1818.                         if (Options.ExtFilter.Count > 0 && !Options.ExtFilter.Contains(type.Key))
  1819.                         {
  1820.                             continue;
  1821.                         }
  1822.                         foreach (var file in type.Value)
  1823.                         {
  1824.                             var ListPath = FixPathSlashes(file.GetFullPath());
  1825.                             if (Options.PathFilter.Count > 0)
  1826.                             {
  1827.                                 var found = false;
  1828.                                 foreach (string pathfilter in Options.PathFilter)
  1829.                                 {
  1830.                                     if (ListPath.StartsWith(pathfilter, StringComparison.Ordinal)) found = true;
  1831.                                 }
  1832.                                 if (!found) continue;
  1833.                             }
  1834.                             filter.WriteLine(ListPath);
  1835.                             if (!Options.Silent) Console.WriteLine(ListPath);
  1836.                         }
  1837.                     }
  1838.                 }
  1839.             }
  1840.  
  1841.             sw.Stop();
  1842.  
  1843.             Echo(String.Format("--- Processed in {0}s", sw.Elapsed.TotalSeconds), ConsoleColor.Cyan);
  1844.         }
  1845.  
  1846.         private static void WriteVPK(List<string> paths, bool modding)
  1847.         {
  1848.  
  1849.             if (modding) Echo("--- Exporting filtered input", ConsoleColor.Green);
  1850.             else Echo("--- Paking input folder", ConsoleColor.Green);
  1851.             TotalFiles = paths.Count;
  1852.             var sw = Stopwatch.StartNew();
  1853.             Encoding iso = Encoding.GetEncoding("ISO-8859-1");
  1854.             Encoding utf = Encoding.UTF8;
  1855.             byte[] data = new byte[0];
  1856.             int Signature = 0x55AA1234;
  1857.             uint Version = 1;
  1858.             uint TreeSize = 0;
  1859.             uint HeaderSize = 4 * 3;
  1860.             var excluded = new List<string> { "zip", "reg", "rar", "msi", "exe", "dll", "com", "cmd", "bat", "vbs" };
  1861.             var tree = new Tree<string, string, string, byte[]>();
  1862.             var package = new Package();
  1863.  
  1864.             if (modding)
  1865.             {
  1866.                 try
  1867.                 {
  1868.                     package.Read(Options.Input);
  1869.                 }
  1870.                 catch (Exception e)
  1871.                 {
  1872.                     Echo(e.ToString(), ConsoleColor.Yellow);
  1873.                 }
  1874.                 foreach (var type in package.Entries)
  1875.                 {
  1876.                     if (ExtFilter.Count > 0 && !ExtFilter.Contains(type.Key))
  1877.                     {
  1878.                         continue;
  1879.                     }
  1880.                     else if (Options.ExtFilter.Count > 0 && !Options.ExtFilter.Contains(type.Key))
  1881.                     {
  1882.                         continue;
  1883.                     }
  1884.  
  1885.                     var entries = package.Entries[type.Key];
  1886.  
  1887.                     foreach (var file in entries)
  1888.                     {
  1889.                         var filePath = string.Format("{0}.{1}", file.FileName, file.TypeName);
  1890.                         if (file.DirectoryName.Length > 0)
  1891.                         {
  1892.                             filePath = Path.Combine(file.DirectoryName, filePath);
  1893.                         }
  1894.                         filePath = FixPathSlashes(filePath);
  1895.  
  1896.                         bool found = false;
  1897.                         if (FileFilter.Count > 0)
  1898.                         {
  1899.                             foreach (string filter in FileFilter)
  1900.                             {
  1901.                               //if (filePath.StartsWith(filter)) found = true;
  1902.                                 if (filePath == filter) found = true;
  1903.                             }
  1904.                             if (!found) continue;
  1905.                         }
  1906.                         else if (Options.PathFilter.Count > 0)
  1907.                         {
  1908.                             foreach (string filter in Options.PathFilter)
  1909.                             {
  1910.                               if (filePath.StartsWith(filter, StringComparison.Ordinal)) found = true;
  1911.                             }
  1912.                             if (!found) continue;
  1913.                         }
  1914.                         string root = string.Empty;
  1915.                         var ext = file.TypeName; //Path.GetExtension(root).TrimStart('.');
  1916.                         if (ext == string.Empty)
  1917.                         {
  1918.                             ext = " ";
  1919.                             if (!Options.Silent) Echo("  missing extension!", ConsoleColor.Red);
  1920.                             //continue;
  1921.                         }
  1922.                         if (excluded.Contains(ext))
  1923.                         {
  1924.                             if (!Options.Silent) Echo("  illegal extension!", ConsoleColor.Red);
  1925.                             continue;
  1926.                         }
  1927.                         var filename = file.FileName; //Path.GetFileNameWithoutExtension(root);
  1928.                         if (filename == string.Empty)
  1929.                         {
  1930.                             filename = " ";
  1931.                             if (!Options.Silent) Echo("  missing name!", ConsoleColor.Red);
  1932.                             //continue;
  1933.                         }
  1934.                         var rel = file.DirectoryName; //Path.GetDirectoryName(root).Replace('\\', '/');
  1935.  
  1936.                         byte[] output;
  1937.                         package.ReadEntry(file, out output);
  1938.  
  1939.                         if (ModSrc.ContainsKey(filePath))
  1940.                         {
  1941.                             if (!Options.Silent) Console.WriteLine("MOD: {0}", filePath);
  1942.                             foreach (var m in ModSrc[filePath])
  1943.                             {
  1944.                                 if (!Options.Silent) Console.WriteLine("   >>{0}", m);
  1945.                                 filePath = m.Key;
  1946.                                 ext = Path.GetExtension(m.Key).TrimStart('.');
  1947.                                 filename = Path.GetFileNameWithoutExtension(m.Key);
  1948.                                 rel = Path.GetDirectoryName(m.Key).Replace('\\', '/');
  1949.                                 if (rel == string.Empty) {
  1950.                                     rel = " ";
  1951.                                 }
  1952.                                 if (!tree.ContainsKey(ext)) {
  1953.                                     tree[ext] = tree.New();
  1954.                                 }
  1955.                                 if (!tree[ext].ContainsKey(rel)) {
  1956.                                     tree[ext][rel] = tree[ext].New();
  1957.                                 }
  1958.                                 tree[ext][rel][filename] = output; //File.ReadAllBytes(path);
  1959.                             }
  1960.                         }
  1961.                         else
  1962.                         {
  1963.                             if (rel == string.Empty) {
  1964.                                 rel = " ";
  1965.                             }
  1966.                             if (!tree.ContainsKey(ext)) {
  1967.                                 tree[ext] = tree.New();
  1968.                             }
  1969.                             if (!tree[ext].ContainsKey(rel)) {
  1970.                                 tree[ext][rel] = tree[ext].New();
  1971.                             }
  1972.                           //package.ReadEntry(file, out output);
  1973.                             tree[ext][rel][filename] = output; //File.ReadAllBytes(path);
  1974.                         }
  1975.  
  1976.                     }
  1977.                 }
  1978.             }
  1979.  
  1980.             if (!modding)
  1981.             {
  1982.                 foreach (var path in paths)
  1983.                 {
  1984.                     if (!Options.Silent) Console.WriteLine("[{0}/{1}] {2}", ++CurrentFile, TotalFiles, path);
  1985.                     byte[] latin = Encoding.Convert(utf, iso, utf.GetBytes(path.Substring(Options.Input.Length)));
  1986.                     string root = iso.GetString(latin).ToLower();
  1987.                     var ext = Path.GetExtension(root).TrimStart('.');
  1988.                     if (ext == string.Empty)
  1989.                     {
  1990.                         ext = " ";
  1991.                         if (!Options.Silent) Echo("  missing extension!", ConsoleColor.Red);
  1992.                         //continue;
  1993.                     }
  1994.                     if (excluded.Contains(ext))
  1995.                     {
  1996.                         if (!Options.Silent) Echo("  illegal extension!", ConsoleColor.Red);
  1997.                         continue;
  1998.                     }
  1999.                     var filename = Path.GetFileNameWithoutExtension(root);
  2000.                     if (filename == string.Empty)
  2001.                     {
  2002.                         filename = " ";
  2003.                         if (!Options.Silent) Echo("  missing name!", ConsoleColor.Red);
  2004.                         //continue;
  2005.                     }
  2006.                     var rel = Path.GetDirectoryName(root).Replace('\\', '/');
  2007.                     if (rel == string.Empty) {
  2008.                         rel = " ";
  2009.                     }
  2010.                     if (!tree.ContainsKey(ext)) {
  2011.                         tree[ext] = tree.New();
  2012.                     }
  2013.                     if (!tree[ext].ContainsKey(rel)) {
  2014.                         tree[ext][rel] = tree[ext].New();
  2015.                     }
  2016.                     tree[ext][rel][filename] = File.ReadAllBytes(path);
  2017.                 }
  2018.             }
  2019.  
  2020.             foreach (var ext in tree)
  2021.             {
  2022.                 TreeSize += (uint)ext.Key.Length + 2;
  2023.               //Console.WriteLine("[ {0} ]",ext.Key);
  2024.                 foreach (var relpath in tree[ext.Key]) {
  2025.                     TreeSize += (uint)relpath.Key.Length + 2;
  2026.                   //Console.WriteLine("[ --- {0}", relpath.Key);
  2027.                     foreach (var filename in tree[ext.Key][relpath.Key]) {
  2028.                         TreeSize += (uint)filename.Key.Length + 1 + 18;
  2029.                       //Console.WriteLine("[ --- : ---> {0}.{1}", filename.Key, ext.Key);
  2030.                     }
  2031.                 }
  2032.             }
  2033.             TreeSize += 1;
  2034.             using (var input = new MemoryStream())
  2035.             {
  2036.                 input.Write(BitConverter.GetBytes(Signature), 0, 4);
  2037.                 input.Write(BitConverter.GetBytes(Version), 0, 4);
  2038.                 input.Write(BitConverter.GetBytes(TreeSize), 0, 4);
  2039.                 HeaderSize = (uint)input.Position;
  2040.                 var data_offset = HeaderSize + TreeSize;
  2041.                 foreach (var ext in tree) {
  2042.                   input.Write(Encoding.UTF8.GetBytes(ext.Key), 0, ext.Key.Length);
  2043.                   input.Write(new byte[] { 0x0 }, 0, 1);
  2044.                   foreach (var relpath in tree[ext.Key]) {
  2045.                     input.Write(Encoding.UTF8.GetBytes(relpath.Key), 0, relpath.Key.Length);
  2046.                     input.Write(new byte[] { 0x0 }, 0, 1);
  2047.                     foreach (var filename in tree[ext.Key][relpath.Key]) {
  2048.                       input.Write(Encoding.UTF8.GetBytes(filename.Key), 0, filename.Key.Length);
  2049.                       input.Write(new byte[] { 0x0 }, 0, 1);
  2050.                       var metadata_offset = (uint)input.Position;
  2051.                       var file_offset = data_offset;
  2052.                       uint checksum = 0;
  2053.                       input.Position = data_offset;
  2054.                       var file = tree[ext.Key][relpath.Key][filename.Key];
  2055.                     //if (modding) checksum = package.FindEntry(relpath.Key, filename.Key, ext.Key).CRC32;
  2056.                     //else checksum = Crc32.Compute(file);
  2057.                       checksum = Crc32.Compute(file);
  2058.                       input.Write(file, 0, file.Length);
  2059.                       data_offset = (uint)input.Position;
  2060.                       var file_length = data_offset - file_offset;
  2061.                       input.Position = metadata_offset;
  2062.                       input.Write(BitConverter.GetBytes(checksum & 0xFFffFFff), 0, 4);
  2063.                       input.Write(new byte[] { 0x0, 0x0 }, 0, 2);
  2064.                       input.Write(BitConverter.GetBytes(0x7fff), 0, 2);
  2065.                       input.Write(BitConverter.GetBytes(file_offset - TreeSize - HeaderSize), 0, 4);
  2066.                       input.Write(BitConverter.GetBytes(file_length), 0, 4);
  2067.                       input.Write(BitConverter.GetBytes(0xffff), 0, 2);
  2068.                     }
  2069.                     // next relpath
  2070.                     input.Write(new byte[] { 0x0 }, 0, 1);
  2071.                   }
  2072.                   // next ext
  2073.                   input.Write(new byte[] { 0x0 }, 0, 1);
  2074.                 }
  2075.                 // end of file tree
  2076.                 input.Write(new byte[] { 0x0 }, 0, 1);
  2077.                 data = input.ToArray();
  2078.             }
  2079.             DumpFile(Options.Output, data);
  2080.             sw.Stop();
  2081.             Echo(String.Format("--- Processed in {0}s", sw.Elapsed.TotalSeconds), ConsoleColor.Cyan);
  2082.         }
  2083.  
  2084.         private static void DumpVPK(Package package, string type)
  2085.         {
  2086.             var entries = package.Entries[type];
  2087.  
  2088.             foreach (var file in entries)
  2089.             {
  2090.                 var filePath = string.Format("{0}.{1}", file.FileName, file.TypeName);
  2091.  
  2092.                 if (!String.IsNullOrEmpty(file.DirectoryName))
  2093.                 {
  2094.                     filePath = Path.Combine(file.DirectoryName, filePath);
  2095.                 }
  2096.  
  2097.                 filePath = FixPathSlashes(filePath);
  2098.  
  2099.                 bool found = false;
  2100.                 if (FileFilter.Count > 0)
  2101.                 {
  2102.                     foreach (string filter in FileFilter)
  2103.                     {
  2104.                       if (filePath.StartsWith(filter, StringComparison.Ordinal)) found = true;
  2105.                     }
  2106.                     if (!found) continue;
  2107.                 }
  2108.                 else if (Options.PathFilter.Count > 0)
  2109.                 {
  2110.                     foreach (string filter in Options.PathFilter)
  2111.                     {
  2112.                       if (filePath.StartsWith(filter, StringComparison.Ordinal)) found = true;
  2113.                     }
  2114.                     if (!found) continue;
  2115.                 }
  2116.  
  2117.                 if (!String.IsNullOrEmpty(Options.Output))
  2118.                 {
  2119.                     uint oldCrc32;
  2120.                     if (Options.CachedManifest && OldPakManifest.TryGetValue(filePath, out oldCrc32) && oldCrc32 == file.CRC32)
  2121.                     {
  2122.                         continue;
  2123.                     }
  2124.                     OldPakManifest[filePath] = file.CRC32;
  2125.                 }
  2126.  
  2127.                 byte[] output;
  2128.                 package.ReadEntry(file, out output);
  2129.  
  2130.                 if (!String.IsNullOrEmpty(Options.Output))
  2131.                 {
  2132.                     DumpFile(filePath, output);
  2133.                 }
  2134.             }
  2135.         }
  2136.  
  2137.         private static void DumpFile(string path, byte[] data)
  2138.         {
  2139.             var outputFile = Path.Combine(Options.Output, path);
  2140.             Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
  2141.             File.WriteAllBytes(outputFile, data);
  2142.             if (!Options.Silent) Console.WriteLine("--- Dump written to \"{0}\"", outputFile);
  2143.         }
  2144.  
  2145.         private static string FixPathSlashes(string path)
  2146.         {
  2147.             path = path.Replace('\\', '/');
  2148. //          if (Path.DirectorySeparatorChar != '/')
  2149. //          {
  2150. //              path = path.Replace('/', Path.DirectorySeparatorChar);
  2151. //          }
  2152.             return path;
  2153.         }
  2154.  
  2155.         public static void Echo(string msg, ConsoleColor clr)
  2156.         {
  2157.             lock (ConsoleWriterLock)
  2158.             {
  2159.                 Console.ForegroundColor = clr;
  2160.                 Console.Error.WriteLine(msg);
  2161.                 Console.ResetColor();
  2162.             }
  2163.         }
  2164.  
  2165.     }
  2166. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement