Advertisement
aveyo

Dota2 No click sound QOL request

Jan 11th, 2024
1,161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Batch 61.98 KB | None | 0 0
  1. /* 2>nul || @title DOTA MOD BUILDER by AveYo v10f - No click sound QOL request
  2.  
  3. @set "OVERRIDE_STEAM_PATH_IF_NEEDED="
  4. @set "OVERRIDE_DOTA2_PATH_IF_NEEDED="
  5. @set "OVERRIDE_OUTPUT_VPK_IF_NEEDED="
  6. @goto :init
  7.  
  8. :vpkmod_source_replacement_pairs:[
  9. sounds/ui/ui_general_deny.vsnd_c  sounds/ui/select_action.vsnd_c
  10. sounds/ui/deny_cooldown.vsnd_c    sounds/ui/select_action.vsnd_c
  11. sounds/ui/deny_mana.vsnd_c        sounds/ui/select_action.vsnd_c
  12. sounds/ui/select_action.vsnd_c    sounds/null.vsnd_c
  13.  
  14. :vpkmod_source_replacement_pairs:]
  15.  
  16. :: gameinfo_branchspecific.gi is a static alternative to using launch option: -language mods
  17. :gameinfo:[
  18. "GameInfo"
  19. {
  20.     //
  21.     // Branch-varying info, such as the game/title and app IDs, is in gameinfo_branchspecific.gi.
  22.     // gameinfo.gi is the non-branch-varying content and can be integrated between branches.
  23.     //
  24.  
  25.     game         "Dota 2"
  26.     title        "Dota 2"
  27.  
  28.     FileSystem
  29.     {
  30.         SteamAppId               570
  31.         BreakpadAppId            373300
  32.         BreakpadAppId_Tools      375360
  33.  
  34.         // gameinfo_branchspecific.gi alternative to -language option for running mods is quite popular in China
  35.         // and Valve have been less enthusiastic about hindering client-side modding in non-western world, wonder why
  36.         SearchPaths
  37.         {
  38.             // AveYo: here is a cleaner way to add mods, without messing the cfg/ path and other auto SearchPaths
  39.             Game_NonTools        dota_mods
  40.  
  41.             // These are optional language paths. They must be mounted first, which is why there are first in the list.
  42.             // *LANGUAGE* will be replaced with the actual language name. Not mounted if not running a specific language.
  43.             Game_Language        dota_*LANGUAGE*
  44.  
  45.             // These are optional low-violence paths. They will only get mounted if you are in a low-violence mode.
  46.             Game_LowViolence     dota_lv
  47.  
  48.             Game                 dota
  49.             Game                 core
  50.  
  51.             Mod                  dota
  52.  
  53.             Write                dota
  54.  
  55.             // These are optional language paths. They must be mounted first, which is why there are first in the list.
  56.             // *LANGUAGE* will be replaced with the actual language name. Not mounted if not running a specific language.
  57.             AddonRoot_Language   dota_*LANGUAGE*_addons
  58.  
  59.             AddonRoot            dota_addons
  60.  
  61.             // Note: addon content is included in publiccontent by default.
  62.             PublicContent        dota_core
  63.             PublicContent        core
  64.         }
  65.         AddonsChangeDefaultWritePath 0
  66.         // restore original file: github.com/SteamDatabase/GameTracking-Dota2/blob/master/game/dota/gameinfo_branchspecific.gi
  67.     }
  68. }
  69.  
  70. :gameinfo:]
  71.  
  72. :init
  73. @echo off & chcp 1252 >nul & cls
  74. ::# detect STEAM path
  75. for /f "tokens=2*" %%R in ('reg query HKCU\SOFTWARE\Valve\Steam /v SteamPath 2^>nul') do set "steam_reg=%%S" & set "libfs="
  76. if not exist "%STEAM%\steamapps\libraryfolders.vdf" for %%S in ("%steam_reg%") do set "STEAM=%%~fS"
  77. if defined OVERRIDE_STEAM_PATH_IF_NEEDED set "STEAM=%OVERRIDE_STEAM_PATH_IF_NEEDED%"
  78. ::# detect DOTA2 path
  79. for /f usebackq^ delims^=^"^ tokens^=4 %%s in (`findstr /c:":\\" "%STEAM%\SteamApps\libraryfolders.vdf"`) do (
  80.  if exist "%%s\steamapps\appmanifest_570.acf" if exist "%%s\steamapps\common\dota 2 beta\game\core\pak01_dir.vpk" set "libfs=%%s")
  81. set "STEAMAPPS=%STEAM%\steamapps"& if defined libfs set "STEAMAPPS=%libfs:\\=\%\steamapps"
  82. set "DOTA2=%STEAMAPPS%\common\dota 2 beta"
  83. if defined OVERRIDE_DOTA2_PATH_IF_NEEDED set "DOTA2=%OVERRIDE_DOTA2_PATH_IF_NEEDED%"
  84. ::# lean xp+ color macros by AveYo:  %<%:af " hello "%>>%  &  %<%:cf " w\"or\"ld "%>%   for single \ / " use .%|%\  .%|%/  \"%|%\"
  85. for /f "delims=:" %%s in ('echo;prompt $h$s$h:^|cmd /d') do set "|=%%s"&set ">>=\..\c nul&set /p s=%%s%%s%%s%%s%%s%%s%%s<nul&popd"
  86. set "<=pushd "%appdata%"&2>nul findstr /c:\ /a" &set ">=%>>%&echo;" &set "|=%!!|%" &set /p s=\<nul>"%appdata%\c"
  87. ::# is dota running?
  88. tasklist /fi "imagename eq dota2.exe" |findstr /i dota2.exe >nul 2>nul && (
  89.   %<%:cf " ERROR "%>>%  &  %<%:70 " DOTA2 is currently running! Try again after closing it "%>%  & timeout -1 >nul & exit
  90. )
  91. ::# check required paths
  92. if not exist "%STEAM%\steamapps\libraryfolders.vdf" (
  93.   %<%:cf " ERROR "%>>%  &  %<%:70 " STEAM not found! Set it manually in the script "%>%  & timeout -1 >nul & exit
  94. )
  95. if not exist "%DOTA2%\game\core\pak01_dir.vpk" (
  96.   %<%:cf " ERROR "%>>%  &  %<%:70 " DOTA2 not found! Set it manually in the script "%>%  & timeout -1 >nul & exit
  97. )
  98. set resourcecompiler="%DOTA2%\game\bin\win64\resourcecompiler.exe"
  99. set vpkmod="%~dp0vpkmod.exe"
  100. if not exist %vpkmod% call :csc_compile_vpkmod_tool & if not exist %vpkmod% (
  101.   %<%:cf " ERROR "%>>%  &  %<%:70 " compiling VPKMOD C# code! Needs .net framework 4.0 or VS2010+ "%>%  & timeout -1 >nul & exit
  102. )
  103. %<%:4f " DOTA "%>>%  &  %<%:2f " MOD "%>>% &  %<%:9f " BUILDER "%>%
  104.  
  105. :process
  106. set "DIR=%DOTA2%\game\dota_mods" & set "FILE=pak01_dir"
  107. if defined OVERRIDE_OUTPUT_VPK_IF_NEEDED set "FILE=%OVERRIDE_OUTPUT_VPK_IF_NEEDED%"
  108. echo  DIR  = %DIR%
  109. echo  FILE = %FILE%.vpk
  110. echo  Preparing quick file replacement mod with nothing but unaltered Valve authored files
  111. (mkdir "%DIR%" & rmdir /s/q "%DIR%\working" & mkdir "%DIR%\working") >nul 2>nul
  112. echo  Exporting Mod.ini source replacement pairs for VPKMOD tool exclusive feature -m
  113. call :export vpkmod_source_replacement_pairs > "%DIR%\working\Mod.ini"
  114. %vpkmod% -i "%DOTA2%\game\dota\pak01_dir.vpk" -o "%DIR%\%FILE%.vpk" -m "%DIR%\working\Mod.ini"
  115. echo  Exporting gameinfo static alternative to launch option: -language mods
  116. call :export gameinfo > "%DOTA2%\game\dota\gameinfo_branchspecific.gi"
  117. echo  Cleanup
  118. rmdir /s/q "%DIR%\working" >nul 2>nul
  119. %<%:2f " DONE "%>%
  120. timeout /t -1
  121. exit /b
  122.  
  123. :export usage: call :export NAME || Prints all text between lines starting with :NAME:[ and :NAME:] - pure batch snippet by AveYo
  124. setlocal enabledelayedexpansion  || can expand variables by using [/] instead of % - example: [/]systemroot[/]
  125. set [=&for /f "delims=:" %%s in ('findstr /nbrc:":%~1:\[" /c:":%~1:\]" "%~f0"')do if defined [ (set /a ]=%%s-3)else set /a [=%%s-1
  126. <"%~fs0" ((for /l %%i in (0 1 %[%) do set /p =)&for /l %%i in (%[% 1 %]%) do (set txt=&set /p txt=&set var=!txt:[/]=%%!&set %%=[/]
  127. if "!var!" neq "!txt!" (if "!txt!" equ "" (echo() else call echo(!var!) else echo(!txt!)) &endlocal &exit /b
  128.  
  129. :csc_compile_vpkmod_tool used to create vpk archive
  130. for /f "tokens=* delims=" %%v in ('dir /b /s /a:-d /o:-n "%SystemRoot%\Microsoft.NET\Framework\*csc.exe"') do set "csc=%%v"
  131. pushd %~dp0 & "%csc%" /out:vpkmod.exe /target:exe /platform:anycpu /optimize /nologo "%~f0"
  132. exit /b VPKMOD C# source */
  133.  
  134. using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net;
  135. using System.Diagnostics; using System.Reflection; using System.Security.Cryptography; using System.Runtime.CompilerServices;
  136. using SteamDB.ValvePak; [assembly:AssemblyDescriptionAttribute("VPKMOD 2.3")] [assembly: AssemblyTitle("AveYo")]
  137. [assembly:AssemblyVersionAttribute("2023.01.12")]
  138.  
  139. class Program
  140. {
  141.     // VPKMOD v2.3 retains the useful v1.x legacy code based on Decompiler by SteamDB
  142.     // so it continues to be a generic tool to list, extract, create, filter and in-memory mod VPKs
  143.     // The main focus however is on supporting No-Bling DOTA mod builder functionality [stripped]
  144.  
  145.     private static Options Options;
  146.     private static readonly object ConsoleWriterLock = new object();
  147.     private static Dictionary<string,uint> OldPakManifest = new Dictionary<string,uint>();
  148.     private static Dictionary<string,Dictionary<string,bool>> ModSrc = new Dictionary<string,Dictionary<string,bool>>();
  149.     private static Dictionary<string,string> SrcMod = new Dictionary<string,string>();
  150.     private static List<string> FileFilter = new List<string>();
  151.     private static List<string> ExtFilter = new List<string>();
  152.     private static bool ExportFilter = false;
  153.  
  154.     public static void Main(string[] args)
  155.     {
  156.         Options = new Options(args);
  157.  
  158.         // Legacy VPKMOD v1 functions:
  159.         if (String.IsNullOrEmpty(Options.Input))
  160.         {
  161.             Echo("Missing -i input parameter!", ConsoleColor.Red);
  162.             return;
  163.         }
  164.         Options.Input = SlashPath(Path.GetFullPath(Options.Input));
  165.  
  166.         if (!String.IsNullOrEmpty(Options.Output)) Options.Output = SlashPath(Path.GetFullPath(Options.Output));
  167.  
  168.         if (!String.IsNullOrEmpty(Options.ModList))
  169.         {
  170.             Options.ModList = SlashPath(Path.GetFullPath(Options.ModList));
  171.             if (File.Exists(Options.ModList))
  172.             {
  173.                 var file = new StreamReader(Options.ModList);
  174.                 string line, ext, mod, src;
  175.                 Dictionary<string,bool> m = new Dictionary<string,bool>();
  176.                 while ((line = file.ReadLine()) != null)
  177.                 {
  178.                     //var split = line.Split(new string[] { " ? " }, 2, 0);
  179.                     var split = line.IndexOf("  ");
  180.                     if (split > 0)
  181.                     {
  182.                         mod = SlashPath(line.Substring(0, split).Trim());
  183.                         src = SlashPath(line.Substring(split + 2).Trim());
  184.                         FileFilter.Add(src);
  185.                         ext = Path.GetExtension(src);
  186.                         if (ext.Length > 1) ExtFilter.Add(ext.Substring(1));
  187.                         SrcMod[src] = mod;
  188.                         if (!ModSrc.ContainsKey(src)) ModSrc.Add(src, new Dictionary<string,bool> { { mod, false } });
  189.                         else ModSrc[src].Add(mod, false);
  190.                     }
  191.                 }
  192.                 file.Close();
  193.             }
  194.         }
  195.         else if (!String.IsNullOrEmpty(Options.FilterList))
  196.         {
  197.             Options.FilterList = SlashPath(Path.GetFullPath(Options.FilterList));
  198.             if (File.Exists(Options.FilterList))
  199.             {
  200.                 var file = new StreamReader(Options.FilterList);
  201.                 string line, ext;
  202.                 while ((line = file.ReadLine()) != null)
  203.                 {
  204.                     FileFilter.Add(SlashPath(line));
  205.                     ext = Path.GetExtension(line);
  206.                     if (ext.Length > 1) ExtFilter.Add(ext.Substring(1));
  207.                 }
  208.                 file.Close();
  209.             }
  210.  
  211.             if (Options.PathFilter.Count > 0 || Options.ExtFilter.Count > 0) ExportFilter = true;
  212.         }
  213.  
  214.         if (Options.PathFilter.Count > 0) Options.PathFilter = Options.PathFilter.ConvertAll(SlashPath);
  215.  
  216.         var paths = new List<string>();
  217.  
  218.         if (Directory.Exists(Options.Input))
  219.         {
  220.             if (Path.GetExtension(Options.Output).ToLower() != ".vpk")
  221.             {
  222.                 Echo(String.Format("Input \"{0}\" is a directory while Output \"{1}\" is not a VPK.",
  223.                   Options.Input, Options.Output), ConsoleColor.Red);
  224.                 return;
  225.             }
  226.             paths.AddRange(Directory.GetFiles(Options.Input, "*.*",
  227.               Options.Recursive ? SearchOption.AllDirectories : !!))
  228.             if (paths.Count == 0)
  229.             {
  230.                 Echo(String.Format("No such file \"{0}\" or dir is empty. Did you mean to include -r (recursive) parameter?",
  231.                     Options.Input), ConsoleColor.Red);
  232.                 return;
  233.             }
  234.             LegacyWriteVPK(paths, false); // pak directory into output.vpk
  235.         }
  236.         else if (File.Exists(Options.Input))
  237.         {
  238.             if (Path.GetExtension(Options.Input).ToLower() != ".vpk")
  239.             {
  240.                 Echo(String.Format("Input \"{0}\" is not a VPK.", Options.Input), ConsoleColor.Red);
  241.                 return;
  242.             }
  243.             paths.Add(Options.Input);
  244.  
  245.             if (Path.GetExtension(Options.Output).ToLower() != ".vpk")
  246.                 LegacyReadVPK(Options.Input); // unpak input.vpk into output dir
  247.             else
  248.                 LegacyWriteVPK(paths, true); // mod input.vpk into output.vpk
  249.         }
  250.     }
  251.  
  252.     private static void LegacyReadVPK(string path)
  253.     {
  254.         Echo(String.Format("--- Listing files in package \"{0}\"", path), ConsoleColor.Green);
  255.         var sw = Stopwatch.StartNew();
  256.         var package = new Package();
  257.         try
  258.         {
  259.             package.Read(path);
  260.         }
  261.         catch (Exception e)
  262.         {
  263.             Echo(e.ToString(), ConsoleColor.Yellow);
  264.         }
  265.  
  266.         if (Options.VerifyVPKChecksums && package.Version == 2)
  267.         {
  268.             try
  269.             {
  270.                 package.VerifyHashes();
  271.                 Console.WriteLine("VPK verification succeeded");
  272.             }
  273.             catch (Exception)
  274.             {
  275.                 Echo("Failed to verify checksums and signature of given VPK:", ConsoleColor.Red);
  276.             }
  277.             return;
  278.         }
  279.  
  280.         if (!String.IsNullOrEmpty(Options.Output) && !Options.OutputVPKDir)
  281.         {
  282.             //Console.WriteLine("--- Reading VPK files...");
  283.             var manifestPath = String.Concat(path, ".manifest.txt");
  284.             if (Options.CachedManifest && File.Exists(manifestPath))
  285.             {
  286.                 var file = new StreamReader(manifestPath);
  287.                 string line;
  288.                 while ((line = file.ReadLine()) != null)
  289.                 {
  290.                     var split = line.Split(new char[1] { ' ' }, 2);
  291.                     if (split.Length == 2) OldPakManifest.Add(split[1], uint.Parse(split[0]));
  292.                 }
  293.                 file.Close();
  294.             }
  295.  
  296.             foreach (var etype in package.Entries)
  297.             {
  298.                 if (ExtFilter.Count > 0 && !ExtFilter.Contains(etype.Key)) continue;
  299.                 else if (Options.ExtFilter.Count > 0 && !Options.ExtFilter.Contains(etype.Key)) continue;
  300.  
  301.                 LegacyDumpVPK(package, etype.Key);
  302.             }
  303.  
  304.             if (Options.CachedManifest)
  305.             {
  306.                 using (var file = new StreamWriter(manifestPath))
  307.                 {
  308.                     foreach (var hash in OldPakManifest)
  309.                     {
  310.                         if (package.FindEntry(hash.Key) == null) Console.WriteLine("\t{0} no longer exists in VPK", hash.Key);
  311.                         file.WriteLine("{0} {1}", hash.Value, hash.Key);
  312.                     }
  313.                 }
  314.             }
  315.         }
  316.  
  317.         if (Options.OutputVPKDir)
  318.         {
  319.             foreach (var etype in package.Entries)
  320.             {
  321.                 foreach (var entry in etype.Value)
  322.                 {
  323.                     Console.WriteLine(entry);
  324.                 }
  325.             }
  326.         }
  327.  
  328.         if (ExportFilter)
  329.         {
  330.             using (var filter = new StreamWriter(Options.FilterList))
  331.             {
  332.                 foreach (var etype in package.Entries)
  333.                 {
  334.                     if (Options.ExtFilter.Count > 0 && !Options.ExtFilter.Contains(etype.Key)) continue;
  335.  
  336.                     foreach (var entry in etype.Value)
  337.                     {
  338.                         var ListPath = SlashPath(entry.GetFullPath());
  339.                         if (Options.PathFilter.Count > 0)
  340.                         {
  341.                             var found = false;
  342.                             foreach (string pathfilter in Options.PathFilter)
  343.                             {
  344.                                 if (ListPath.StartsWith(pathfilter, StringComparison.OrdinalIgnoreCase)) found = true;
  345.                             }
  346.                             if (!found) continue;
  347.                         }
  348.                         filter.WriteLine(ListPath);
  349.                         if (!Options.Silent) Console.WriteLine(ListPath);
  350.                     }
  351.                 }
  352.             }
  353.         }
  354.  
  355.         sw.Stop();
  356.  
  357.         Echo(String.Format("--- Processed in {0}s", sw.Elapsed.TotalSeconds), ConsoleColor.Cyan);
  358.     }
  359.  
  360.     private static int LegacyWriteVPK(List<string> paths, bool modding)
  361.     {
  362.         if (paths.Count == 0) return 0;
  363.         var inputdir = Options.Input;
  364.         var sw = Stopwatch.StartNew();
  365.         var package = new Package();
  366.         var pak01_dir = new Package();
  367.  
  368.         Echo(modding ? "--- Modding... " : "--- Paking... ", ConsoleColor.Green, 0);
  369.         Echo(paths.Count);
  370.  
  371.         if (modding)
  372.         {
  373.             try { package.Read(Options.Input); } catch (Exception e) { Echo(e.ToString(), ConsoleColor.Yellow); }
  374.  
  375.             foreach (var etype in package.Entries)
  376.             {
  377.                 if (ExtFilter.Count > 0 && !ExtFilter.Contains(etype.Key)) continue;
  378.                 else if (Options.ExtFilter.Count > 0 && !Options.ExtFilter.Contains(etype.Key)) continue;
  379.  
  380.                 var entries = package.Entries[etype.Key];
  381.  
  382.                 foreach (var entry in entries)
  383.                 {
  384.                     var filePath = String.Format("{0}.{1}", entry.FileName, entry.TypeName);
  385.                     if (entry.DirectoryName.Length > 0) filePath = Path.Combine(entry.DirectoryName, filePath);
  386.                     filePath = SlashPath(filePath);
  387.  
  388.                     bool found = false;
  389.                     if (FileFilter.Count > 0)
  390.                     {
  391.                         foreach (string filter in FileFilter)
  392.                         {
  393.                             if (filePath == filter) found = true; // StartsWith
  394.                         }
  395.                         if (!found) continue;
  396.                     }
  397.                     else if (Options.PathFilter.Count > 0)
  398.                     {
  399.                         foreach (string filter in Options.PathFilter)
  400.                         {
  401.                             if (filePath.StartsWith(filter, StringComparison.OrdinalIgnoreCase)) found = true;
  402.                         }
  403.                         if (!found) continue;
  404.                     }
  405.  
  406.                     var ext = entry.TypeName;
  407.                     if (ext == "")
  408.                     {
  409.                         ext = " ";
  410.                         if (!Options.Silent) Echo("  missing extension!", ConsoleColor.Red);
  411.                         //continue;
  412.                     }
  413.                     var file = entry.FileName; //Path.GetFileNameWithoutExtension(root);
  414.                     if (file == "")
  415.                     {
  416.                         file = " ";
  417.                         if (!Options.Silent) Echo("  missing name!", ConsoleColor.Red);
  418.                         //continue;
  419.                     }
  420.                     var dir = entry.DirectoryName; //Path.GetDirectoryName(root).Replace('\\', '/');
  421.  
  422.                     byte[] output;
  423.                     lock (package)
  424.                     {
  425.                         package.ReadEntry(entry, out output, false);
  426.                     }
  427.  
  428.                     if (ModSrc.ContainsKey(filePath))
  429.                     {
  430.                         if (!Options.Silent) Console.WriteLine("--- Replacing with {0}", filePath);
  431.                         foreach (var m in ModSrc[filePath])
  432.                         {
  433.                             if (!Options.Silent) Console.WriteLine("    {0}", m);
  434.                             filePath = m.Key;
  435.                             ext = Path.GetExtension(m.Key).TrimStart('.');
  436.                             file = Path.GetFileNameWithoutExtension(m.Key);
  437.                             dir = Path.GetDirectoryName(m.Key).Replace('\\', '/');
  438.                             if (dir == "") dir = " ";
  439.                             pak01_dir.AddEntry(dir, file, ext, output);
  440.                         }
  441.                     }
  442.                     else
  443.                     {
  444.                         if (dir == "") dir = " ";
  445.                         pak01_dir.AddEntry(dir, file, ext, output);
  446.                     }
  447.                 }
  448.             }
  449.  
  450.             // mod size optimization: replace res with zero-byte file if mod src pair has src="00"
  451.             string nix = "00";
  452.             if (ModSrc.ContainsKey(nix))
  453.             {
  454.                 if (!Options.Silent) Console.WriteLine("--- Replacing with \"00\" [ 0-byte data ]");
  455.                 foreach (var m in ModSrc[nix])
  456.                 {
  457.                     if (!Options.Silent) Console.WriteLine("    {0}", m);
  458.                     var ext = Path.GetExtension(m.Key).TrimStart('.');
  459.                     var file = Path.GetFileNameWithoutExtension(m.Key);
  460.                     var dir = Path.GetDirectoryName(m.Key).Replace('\\', '/');
  461.                     if (dir == "") dir = " ";
  462.                     pak01_dir.AddEntry(dir, file, ext, new byte[0]);
  463.                 }
  464.             }
  465.         }
  466.  
  467.         // include pak01_dir subfolder (if it exists) for manual overrides when modding
  468.         if (Directory.Exists("pak01_dir") && modding)
  469.         {
  470.             if (!Options.Silent) Console.WriteLine("--- Including files in \"pak01_dir\" folder");
  471.             pak01_dir.AddFolder("pak01_dir");
  472.         }
  473.  
  474.         if (!modding)
  475.         {
  476.             pak01_dir.AddFolder(inputdir);
  477.         }
  478.  
  479.         pak01_dir.SaveToFile(Options.Output);
  480.         sw.Stop();
  481.         var files = pak01_dir.Entries.Values.Sum(_ => _.Count);
  482.         Echo(String.Format("--- Processed {0} files in {1}s", files, sw.Elapsed.TotalSeconds), ConsoleColor.Cyan);
  483.         return files;
  484.     }
  485.  
  486.     private static void LegacyDumpVPK(Package package, string ext)
  487.     {
  488.         var entries = package.Entries[ext];
  489.  
  490.         foreach (var entry in entries)
  491.         {
  492.             var filePath = String.Format("{0}.{1}", entry.FileName, entry.TypeName);
  493.             if (!String.IsNullOrEmpty(entry.DirectoryName)) filePath = Path.Combine(entry.DirectoryName, filePath);
  494.             filePath = SlashPath(filePath);
  495.  
  496.             bool found = false;
  497.             if (FileFilter.Count > 0)
  498.             {
  499.                 foreach (string filter in FileFilter)
  500.                 {
  501.                     if (filePath.StartsWith(filter, StringComparison.OrdinalIgnoreCase)) found = true;
  502.                 }
  503.                 if (!found) continue;
  504.             }
  505.             else if (Options.PathFilter.Count > 0)
  506.             {
  507.                 foreach (string filter in Options.PathFilter)
  508.                 {
  509.                     if (filePath.StartsWith(filter, StringComparison.OrdinalIgnoreCase)) found = true;
  510.                 }
  511.                 if (!found) continue;
  512.             }
  513.  
  514.             if (!String.IsNullOrEmpty(Options.Output))
  515.             {
  516.                 uint oldCrc32;
  517.                 if (Options.CachedManifest && OldPakManifest.TryGetValue(filePath, out oldCrc32) && oldCrc32 == entry.CRC32)
  518.                     continue;
  519.                 OldPakManifest[filePath] = entry.CRC32;
  520.             }
  521.  
  522.             byte[] output;
  523.             lock (package)
  524.             {
  525.                 package.ReadEntry(entry, out output, false);
  526.             }
  527.  
  528.             if (!String.IsNullOrEmpty(Options.Output)) LegacyDumpFile(filePath, output);
  529.         }
  530.     }
  531.  
  532.     private static void LegacyDumpFile(string path, byte[] data)
  533.     {
  534.         var outputFile = SlashPath(Path.Combine(Options.Output, path));
  535.         Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
  536.         File.WriteAllBytes(outputFile, data);
  537.         if (!Options.Silent) Console.WriteLine("--- Written \"{0}\"", outputFile);
  538.     }
  539.  
  540.     public static string SlashPath(string path)
  541.     {
  542.         if (path == null) return null;
  543.         else if (path.Length == 1) return path;
  544.         else return String.Join("/", path.Split(new char[2] {'/', '\\'}, StringSplitOptions.None)).TrimEnd('/');
  545.     }
  546.  
  547.     public static void Log(params object[] msg)
  548.     {
  549.         using (TextWriter errorWriter = Console.Error)
  550.         {
  551.             errorWriter.WriteLine(String.Join(" ", msg));
  552.         }
  553.     }
  554.  
  555.     public static void Echo(object msg, ConsoleColor clr = ConsoleColor.Gray, int newline = 1)
  556.     {
  557.         lock (ConsoleWriterLock)
  558.         {
  559.             Console.ForegroundColor = clr;
  560.             Console.Write(newline == 1 ? "{0}\n" : "{0}", msg);
  561.             Console.ResetColor();
  562.         }
  563.     }
  564. }
  565.  
  566. public class Options
  567. {
  568.     public string Input { get; set; }
  569.     public bool Recursive { get; set; }
  570.     public string Output { get; set; }
  571.     public bool OutputVPKDir { get; set; }
  572.     public bool CachedManifest { get; set; }
  573.     public bool VerifyVPKChecksums { get; set; }
  574.     public List<string> ExtFilter { get; set; }
  575.     public List<string> PathFilter { get; set; }
  576.     public string FilterList { get; set; }
  577.     public string ModList { get; set; }
  578.     public bool Silent { get; set; }
  579.     public bool Help { get; set; }
  580.     public bool Dialog { get { return (Environment.GetEnvironmentVariable("NO_CHOICES_DIALOG") == null); } }
  581.     public bool MONO { get { int p = (int)Environment.OSVersion.Platform; return ((p == 4) || (p == 6) || (p == 128)); } }
  582.     internal IDictionary<string,List<string>> Parsed { get; private set; }
  583.  
  584.     public Options(string[] cmd)
  585.     {
  586.         Parsed = new Dictionary<string,List<string>>(); Parse(cmd);
  587.     }
  588.  
  589.     internal bool Find(string key, bool novalue = false)
  590.     {
  591.         if (Parsed.ContainsKey(key))
  592.         {
  593.             if (novalue || Parsed[key].Count != 0) return true;
  594.             Console.WriteLine("-" + key + " requires a value!");
  595.         }
  596.         return false;
  597.     }
  598.  
  599.     internal void Parse(string[] cmd)
  600.     {
  601.         var key = ""; List<string> values = new List<string>();
  602.         foreach (string item in cmd)
  603.         {
  604.             if (item[0] == '-') { if (key != "") Parsed[key] = values; key = item.Substring(1); values = new List<string>(); }
  605.             else if (key == "") { Parsed[item] = new List<string>(); }
  606.             else { values = new List<string>(item.Split(',')); }
  607.         }
  608.         if (key != "") Parsed[key] = values;
  609.  
  610.         Input              = Find("i") ? Parsed["i"][0] : !!
  611.         Recursive          = Find("r", true);
  612.         Output             = Find("o") ? Parsed["o"][0] : !!
  613.         CachedManifest     = Find("c", true);
  614.         OutputVPKDir       = Find("d", true);
  615.         VerifyVPKChecksums = Find("v", true);
  616.         ExtFilter          = Find("e") ? Parsed["e"] : new List<string>();
  617.         PathFilter         = Find("p") ? Parsed["p"] : new List<string>();
  618.         FilterList         = Find("l") ? Parsed["l"][0] : !!
  619.         ModList            = Find("m") ? Parsed["m"][0] : !!
  620.         Silent             = Find("s", true);
  621.         Help               = Find("h", true) || cmd.Length == 0;
  622.  
  623.         if (Silent == false)
  624.         {
  625.             Console.ForegroundColor = ConsoleColor.Black; Console.BackgroundColor = ConsoleColor.Cyan;
  626.             Console.Write(" VPKMOD v2.3 "); Console.ResetColor(); Console.WriteLine("  AveYo / SteamDB");
  627.         }
  628.         if (Help)
  629.         {
  630.             Console.WriteLine(" -i input      Directory to create new VPK from, or File to extract VPK from");
  631.             Console.WriteLine(" -o output     File to create VPK to, or Directory to extract VPK to");
  632.             Console.WriteLine(" -r            Recursively include all files in Directory");
  633.             Console.WriteLine(" -c            Cached VPK manifest: only changed files get extracted to disk");
  634.             Console.WriteLine(" -d            Write VPK directory of files and their CRC to console");
  635.             Console.WriteLine(" -v            Verify checksums and signatures: only for VPK version 2");
  636.             Console.WriteLine(" -e txt,vjs_c  Extension(s) filter: only include these file extensions");
  637.             Console.WriteLine(" -p cfg/,dev/  Path(s) filter: only include files from these paths");
  638.             Console.WriteLine(" -l list.txt   List file to import fullpath filters from");
  639.             Console.WriteLine("               | if -e or -p are also used, export current filters instead");
  640.             Console.WriteLine("               | vpkmod -i pak01_dir.vpk -e vmdl_c -p models/heroes/mars -l mars.txt");
  641.             Console.WriteLine(" -m mod.txt    Mod = Src pairs file for in-memory unpak-replace-pak quick modding");
  642.             Console.WriteLine("               | sounds/misc/soundboard/all_dead.vsnd_c?sounds/null.vsnd_c");
  643.             Console.WriteLine("               | if Src is \"00\" set Mod file content to 0-byte");
  644.             Console.WriteLine("               | automatically imports files from a pak01_dir subfolder");
  645.             Console.WriteLine(" -s            Silent");
  646.             Console.WriteLine(" -h            This help screen");
  647.             Console.ReadKey(); Environment.Exit(0);
  648.         }
  649.     }
  650. }
  651.  
  652. namespace SteamDB.ValvePak
  653. {
  654.     /*
  655.     MIT License
  656.  
  657.     Copyright (c) 2008 Rick (rick 'at' gibbed 'dot' us)
  658.     Copyright (c) 2016 SteamDB
  659.     Copyright (c) 2019 AveYo
  660.  
  661.     Permission is hereby granted, free of charge, to any person obtaining a copy
  662.     of this software and associated documentation files (the "Software"), to deal
  663.     in the Software without restriction, including without limitation the rights
  664.     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  665.     copies of the Software, and to permit persons to whom the Software is
  666.     furnished to do so, subject to the following conditions:
  667.  
  668.     The above copyright notice and this permission notice shall be included in all
  669.     copies or substantial portions of the Software.
  670.  
  671.     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  672.     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  673.     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  674.     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  675.     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  676.     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  677.     SOFTWARE.
  678.     */
  679.  
  680.     public class Package : IDisposable
  681.     {
  682.         public const int MAGIC = 0x55AA1234;
  683.         public const char DirectorySeparatorChar = '/';
  684.         private BinaryReader Reader;
  685.         public string FileName { get; private set; }
  686.         public bool IsDirVPK { get; private set; }
  687.         public uint Version { get; private set; }
  688.         public uint HeaderSize { get; private set; }
  689.         public uint TreeSize { get; private set; }
  690.         public uint FileDataSectionSize { get; private set; }
  691.         public uint ArchiveMD5SectionSize { get; private set; }
  692.         public uint OtherMD5SectionSize { get; private set; }
  693.         public uint SignatureSectionSize { get; private set; }
  694.         public byte[] TreeChecksum { get; private set; }
  695.         public byte[] ArchiveMD5EntriesChecksum { get; private set; }
  696.         public byte[] WholeFileChecksum { get; private set; }
  697.         public byte[] PublicKey { get; private set; }
  698.         public byte[] Signature { get; private set; }
  699.         public Dictionary<string,List<PackageEntry>> Entries { get; private set; }
  700.         public List<ArchiveMD5SectionEntry> ArchiveMD5Entries { get; private set; }
  701.  
  702.         public void Dispose()
  703.         {
  704.             Dispose(true);
  705.             GC.SuppressFinalize(this);
  706.         }
  707.  
  708.         protected virtual void Dispose(bool disposing)
  709.         {
  710.             if (disposing && Reader != null)
  711.             {
  712.                 Reader.Dispose();
  713.                 Reader = null;
  714.             }
  715.         }
  716.  
  717.         public void SetFileName(string fileName)
  718.         {
  719.             if (fileName == null)
  720.             {
  721.                 throw new ArgumentNullException("vpk fileName is null");
  722.             }
  723.  
  724.             if (fileName.EndsWith(".vpk", StringComparison.OrdinalIgnoreCase))
  725.             {
  726.                 fileName = fileName.Substring(0, fileName.Length - 4);
  727.             }
  728.  
  729.             if (fileName.EndsWith("_dir", StringComparison.OrdinalIgnoreCase))
  730.             {
  731.                 IsDirVPK = true;
  732.                 fileName = fileName.Substring(0, fileName.Length - 4);
  733.             }
  734.  
  735.             FileName = fileName;
  736.         }
  737.  
  738.         public void Read(string filename)
  739.         {
  740.             SetFileName(filename);
  741.  
  742.             var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  743.             //String.Format("{0}{1}.vpk", FileName, IsDirVPK ? "_dir" : "")
  744.  
  745.             Read(fs);
  746.         }
  747.  
  748.         public void Read(Stream input)
  749.         {
  750.             if (input == null)
  751.             {
  752.                 throw new ArgumentNullException("VPK stream input is null");
  753.             }
  754.  
  755.             if (FileName == null)
  756.                 throw new InvalidOperationException("You must call SetFileName() before calling Read() directly with a stream.");
  757.  
  758.             Reader = new BinaryReader(input);
  759.  
  760.             if (Reader.ReadUInt32() != MAGIC)
  761.                 throw new InvalidDataException("Given file is not a VPK.");
  762.  
  763.             Version = Reader.ReadUInt32();
  764.             TreeSize = Reader.ReadUInt32();
  765.  
  766.             if (Version == 1)
  767.             {
  768.                 // Nothing else
  769.             }
  770.             else if (Version == 2)
  771.             {
  772.                 FileDataSectionSize = Reader.ReadUInt32();
  773.                 ArchiveMD5SectionSize = Reader.ReadUInt32();
  774.                 OtherMD5SectionSize = Reader.ReadUInt32();
  775.                 SignatureSectionSize = Reader.ReadUInt32();
  776.             }
  777.             else if (Version == 0x00030002) // Apex Legends, Titanfall
  778.             {
  779.                 throw new NotSupportedException("Respawn uses customized vpk format which this library does not support.");
  780.             }
  781.             else
  782.             {
  783.                 throw new InvalidDataException(String.Format("Bad VPK version. ({0})", Version));
  784.             }
  785.  
  786.             HeaderSize = (uint)input.Position;
  787.  
  788.             ReadEntries();
  789.  
  790.             if (Version == 2)
  791.             {
  792.                 // Skip over file data, if any
  793.                 input.Position += FileDataSectionSize;
  794.  
  795.                 ReadArchiveMD5Section();
  796.                 ReadOtherMD5Section();
  797.                 ReadSignatureSection();
  798.             }
  799.         }
  800.  
  801.         public PackageEntry FindEntry(string filePath)
  802.         {
  803.             if (filePath == null)
  804.                 throw new ArgumentNullException("filePath");
  805.  
  806.             filePath = filePath.Replace('\\', DirectorySeparatorChar);
  807.  
  808.             var lastSeparator = filePath.LastIndexOf(DirectorySeparatorChar);
  809.             var directory = lastSeparator > -1 ? filePath.Substring(0, lastSeparator) : !!
  810.             var fileName = filePath.Substring(lastSeparator + 1);
  811.  
  812.             return FindEntry(directory, fileName);
  813.         }
  814.  
  815.         public PackageEntry FindEntry(string directory, string fileName)
  816.         {
  817.             if (directory == null)
  818.                 throw new ArgumentNullException("directory");
  819.  
  820.             if (fileName == null)
  821.                 throw new ArgumentNullException("fileName");
  822.  
  823.             var dot = fileName.LastIndexOf('.');
  824.             string extension;
  825.  
  826.             if (dot > -1)
  827.             {
  828.                 extension = fileName.Substring(dot + 1);
  829.                 fileName = fileName.Substring(0, dot);
  830.             }
  831.             else
  832.             {
  833.                 // Valve uses a space for missing extensions
  834.                 extension = " ";
  835.             }
  836.  
  837.             return FindEntry(directory, fileName, extension);
  838.         }
  839.  
  840.         public PackageEntry FindEntry(string directory, string fileName, string extension)
  841.         {
  842.             if (directory == null)
  843.                 throw new ArgumentNullException("directory");
  844.  
  845.             if (fileName == null)
  846.                 throw new ArgumentNullException("fileName");
  847.  
  848.             if (extension == null)
  849.                 throw new ArgumentNullException("extension");
  850.  
  851.             if (!Entries.ContainsKey(extension))
  852.                 return null;
  853.  
  854.             // We normalize path separators when reading the file list
  855.             // And remove the trailing slash
  856.             directory = directory.Replace('\\', DirectorySeparatorChar).Trim(DirectorySeparatorChar);
  857.  
  858.             // If the directory is empty after trimming, set it to a space to match Valve's behaviour
  859.             if (directory.Length == 0)
  860.                 directory = " ";
  861.  
  862.             return Entries[extension].Find(_ => _.DirectoryName == directory && _.FileName == fileName);
  863.         }
  864.  
  865.         public void ReadEntry(PackageEntry entry, out byte[] output, bool validateCrc = true)
  866.         {
  867.             output = new byte[entry.SmallData.Length + entry.Length];
  868.  
  869.             if (entry.SmallData.Length > 0)
  870.                 entry.SmallData.CopyTo(output, 0);
  871.  
  872.             if (entry.Length > 0)
  873.             {
  874.                 Stream fs = null;
  875.  
  876.                 try
  877.                 {
  878.                     var offset = entry.Offset;
  879.  
  880.                     if (entry.ArchiveIndex != 0x7FFF)
  881.                     {
  882.                         if (!IsDirVPK)
  883.                             throw new InvalidOperationException("Given VPK is not _dir, but entry references external archive.");
  884.  
  885.                         var fileName = String.Format("{0}_{1:d3}.vpk", FileName, entry.ArchiveIndex);
  886.                         fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
  887.                     }
  888.                     else
  889.                     {
  890.                         fs = Reader.BaseStream;
  891.                         offset += HeaderSize + TreeSize;
  892.                     }
  893.  
  894.                     fs.Seek(offset, SeekOrigin.Begin);
  895.  
  896.                     int length = (int)entry.Length;
  897.                     int readOffset = entry.SmallData.Length;
  898.                     int bytesRead;
  899.                     int totalRead = 0;
  900.                     while ((bytesRead = fs.Read(output, readOffset + totalRead, length - totalRead)) != 0)
  901.                     {
  902.                         totalRead += bytesRead;
  903.                     }
  904.                 }
  905.                 finally
  906.                 {
  907.                     if (entry.ArchiveIndex != 0x7FFF && fs != null)
  908.                         fs.Close();
  909.                 }
  910.             }
  911.  
  912.             if (validateCrc && entry.CRC32 != Crc32.Compute(output))
  913.                 throw new InvalidDataException("CRC32 mismatch for read data.");
  914.         }
  915.  
  916.         private void ReadEntries()
  917.         {
  918.             var typeEntries = new Dictionary<string, List<PackageEntry>>();
  919.             using (MemoryStream ms = new MemoryStream())
  920.  
  921.             // Types
  922.             while (true)
  923.             {
  924.                 var typeName = ReadNullTermUtf8String(ms);
  925.  
  926.                 if (string.IsNullOrEmpty(typeName))
  927.                 {
  928.                     break;
  929.                 }
  930.  
  931.                 var entries = new List<PackageEntry>();
  932.  
  933.                 // Directories
  934.                 while (true)
  935.                 {
  936.                     var directoryName = ReadNullTermUtf8String(ms);
  937.  
  938.                     if (string.IsNullOrEmpty(directoryName))
  939.                     {
  940.                         break;
  941.                     }
  942.  
  943.                     // Files
  944.                     while (true)
  945.                     {
  946.                         var fileName = ReadNullTermUtf8String(ms);
  947.  
  948.                         if (string.IsNullOrEmpty(fileName))
  949.                         {
  950.                             break;
  951.                         }
  952.  
  953.                         var entry = new PackageEntry
  954.                         {
  955.                             FileName = fileName,
  956.                             DirectoryName = directoryName,
  957.                             TypeName = typeName,
  958.                         };
  959.  
  960.                         entry.CRC32 = Reader.ReadUInt32();
  961.                         var smallDataSize = Reader.ReadUInt16();
  962.                         entry.ArchiveIndex = Reader.ReadUInt16();
  963.                         entry.Offset = Reader.ReadUInt32();
  964.                         entry.Length = Reader.ReadUInt32();
  965.  
  966.                         var terminator = Reader.ReadUInt16();
  967.  
  968.                         if (terminator != 0xFFFF)
  969.                             throw new FormatException("VPK entry with invalid terminator.");
  970.  
  971.                         if (smallDataSize > 0)
  972.                         {
  973.                             entry.SmallData = new byte[smallDataSize];
  974.  
  975.                             int bytesRead;
  976.                             int totalRead = 0;
  977.                             while ((bytesRead = Reader.Read(entry.SmallData, totalRead, entry.SmallData.Length - totalRead)) != 0)
  978.                             {
  979.                                 totalRead += bytesRead;
  980.                             }
  981.                         }
  982.                         else
  983.                         {
  984.                             entry.SmallData = Array.Empty<byte>();
  985.                         }
  986.  
  987.                         entries.Add(entry);
  988.                     }
  989.                 }
  990.  
  991.                 typeEntries.Add(typeName, entries);
  992.             }
  993.  
  994.             Entries = typeEntries;
  995.         }
  996.  
  997.         public void VerifyHashes()
  998.         {
  999.             if (Version != 2)
  1000.                 throw new InvalidDataException("Only version 2 is supported.");
  1001.  
  1002.             using (var md5 = MD5.Create())
  1003.             {
  1004.                 Reader.BaseStream.Position = 0;
  1005.  
  1006.                 var hash = md5.ComputeHash(
  1007.                   Reader.ReadBytes((int)(HeaderSize + TreeSize + FileDataSectionSize + ArchiveMD5SectionSize + 32)));
  1008.  
  1009.                 if (!hash.SequenceEqual(WholeFileChecksum))
  1010.                     throw new InvalidDataException(String.Format("Package checksum mismatch ({0} != expected {1})",
  1011.                       BitConverter.ToString(hash), BitConverter.ToString(WholeFileChecksum)));
  1012.  
  1013.                 Reader.BaseStream.Position = HeaderSize;
  1014.  
  1015.                 hash = md5.ComputeHash(Reader.ReadBytes((int)TreeSize));
  1016.  
  1017.                 if (!hash.SequenceEqual(TreeChecksum))
  1018.                     throw new InvalidDataException(String.Format("File tree checksum mismatch ({0} != expected {1})",
  1019.                       BitConverter.ToString(hash), BitConverter.ToString(TreeChecksum)));
  1020.  
  1021.                 Reader.BaseStream.Position = HeaderSize + TreeSize + FileDataSectionSize;
  1022.  
  1023.                 hash = md5.ComputeHash(Reader.ReadBytes((int)ArchiveMD5SectionSize));
  1024.  
  1025.                 if (!hash.SequenceEqual(ArchiveMD5EntriesChecksum))
  1026.                     throw new InvalidDataException(String.Format("Archive MD5 entries checksum mismatch ({0} != expected {1})",
  1027.                       BitConverter.ToString(hash), BitConverter.ToString(ArchiveMD5EntriesChecksum)));
  1028.  
  1029.                 // TODO: verify archive checksums
  1030.             }
  1031.  
  1032.             if (PublicKey == null || Signature == null)
  1033.                 return;
  1034.  
  1035.             if (!IsSignatureValid())
  1036.                 throw new InvalidDataException("VPK signature is not valid.");
  1037.         }
  1038.  
  1039.         public bool IsSignatureValid()
  1040.         {
  1041.             // AveYo : just return true since RSA and AsnKeyParser are not used in VPKMOD
  1042.             return true;
  1043.         }
  1044.  
  1045.         private void ReadArchiveMD5Section()
  1046.         {
  1047.             ArchiveMD5Entries = new List<ArchiveMD5SectionEntry>();
  1048.  
  1049.             if (ArchiveMD5SectionSize == 0)
  1050.             {
  1051.                 return;
  1052.             }
  1053.  
  1054.             var entries = ArchiveMD5SectionSize / 28; // 28 is sizeof(VPK_MD5SectionEntry), which is int + int + int + 16 chars
  1055.  
  1056.             for (var i = 0; i < entries; i++)
  1057.             {
  1058.                 ArchiveMD5Entries.Add(new ArchiveMD5SectionEntry
  1059.                 {
  1060.                     ArchiveIndex = Reader.ReadUInt32(),
  1061.                     Offset = Reader.ReadUInt32(),
  1062.                     Length = Reader.ReadUInt32(),
  1063.                     Checksum = Reader.ReadBytes(16)
  1064.                 });
  1065.             }
  1066.         }
  1067.  
  1068.         private void ReadOtherMD5Section()
  1069.         {
  1070.             if (OtherMD5SectionSize != 48)
  1071.                 throw new InvalidDataException(String.Format("Encountered OtherMD5Section with size of {0} (should be 48)",
  1072.                   OtherMD5SectionSize));
  1073.  
  1074.             TreeChecksum = Reader.ReadBytes(16);
  1075.             ArchiveMD5EntriesChecksum = Reader.ReadBytes(16);
  1076.             WholeFileChecksum = Reader.ReadBytes(16);
  1077.         }
  1078.  
  1079.         private void ReadSignatureSection()
  1080.         {
  1081.             if (SignatureSectionSize == 0)
  1082.             {
  1083.                 return;
  1084.             }
  1085.  
  1086.             var publicKeySize = Reader.ReadInt32();
  1087.  
  1088.             if (SignatureSectionSize == 20 && publicKeySize == MAGIC)
  1089.             {
  1090.                 // CS2 has this
  1091.                 return;
  1092.             }
  1093.  
  1094.             PublicKey = Reader.ReadBytes(publicKeySize);
  1095.  
  1096.             var signatureSize = Reader.ReadInt32();
  1097.             Signature = Reader.ReadBytes(signatureSize);
  1098.         }
  1099.  
  1100.         [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1101.         private string ReadNullTermUtf8String(MemoryStream ms)
  1102.         {
  1103.             while (true)
  1104.             {
  1105.                 var b = Reader.ReadByte();
  1106.  
  1107.                 if (b == 0x00)
  1108.                 {
  1109.                     break;
  1110.                 }
  1111.  
  1112.                 ms.WriteByte(b);
  1113.             }
  1114.  
  1115.             ArraySegment<byte> buffer;
  1116.  
  1117.             ms.TryGetBuffer(out buffer);
  1118.  
  1119.             var str = Encoding.UTF8.GetString(buffer.ToArray());
  1120.  
  1121.             ms.SetLength(0);
  1122.  
  1123.             return str;
  1124.         }
  1125.  
  1126.         // AveYo: enhanced rework of my previous stand-alone vpk writer to fit in the Package class
  1127.         public void SaveToFile(string fn)
  1128.         {
  1129.             // VPKMOD23 just counts offsets for duplicate files, so the exported .vpk is generally smaller for repetitive content!
  1130.             var sw = Stopwatch.StartNew();
  1131.             if (Entries == null) { File.Delete(fn); return; }
  1132.             Directory.CreateDirectory(Path.GetDirectoryName(fn));
  1133.             using (var md5 = MD5.Create())
  1134.             using (var sha1 = SHA1.Create())
  1135.             using (FileStream fs = new FileStream(fn, FileMode.Create, FileAccess.Write))
  1136.             using (MemoryStream mtree = new MemoryStream(), mdata = new MemoryStream())
  1137.             using (BinaryWriter tree = new BinaryWriter(mtree), data = new BinaryWriter(mdata))
  1138.             using (BinaryReader buff = new BinaryReader(mtree))
  1139.             {
  1140.                 uint version = 2, tree_size = 0, data_size = 0, data_offset = 16, lookup_offset = 0;
  1141.                 short preload_bytes = 0, archive_index = 0x7fff, terminator = -1;
  1142.                 var seen = new Dictionary<string, uint>();
  1143.                 bool unique = true;
  1144.                 byte nul1 = 0;
  1145.  
  1146.                 data.Write(0x4d4b5056); data.Write(0x3332444f); data.Write(0x41204020); data.Write(0x4f594556); // id
  1147.  
  1148.                 tree.Write(MAGIC);                                                  // Signature             4
  1149.                 tree.Write(version);                                                // Version               4
  1150.                 tree.Write(tree_size);                                              // TreeSize (TBD)        4
  1151.  
  1152.                 tree.Write(0x00000000);                                             // FileDataSectionSize   4
  1153.                 tree.Write(0x00000000);                                             // ArchiveMD5SectionSize 4
  1154.                 tree.Write(0x00000030);                                             // OtherMD5SectionSize   4
  1155.                 tree.Write(0x00000000);                                             // SignatureSectionSize  4
  1156.  
  1157.                 foreach (string etype in Entries.Keys)
  1158.                 {
  1159.                     tree.Write(Encoding.UTF8.GetBytes(etype));                      // TypeName              ?
  1160.                     tree.Write(nul1);                                               // 00                    1
  1161.                     tree_size += (uint)etype.Length + 1;
  1162.  
  1163.                     var directories = Entries[etype].Select(_ => _.DirectoryName).Distinct();
  1164.                     foreach (string dirname in directories)
  1165.                     {
  1166.                         tree.Write(Encoding.UTF8.GetBytes(dirname));                // DirectoryName         ?
  1167.                         tree.Write(nul1);                                           // 00                    1
  1168.                         tree_size += (uint)dirname.Length + 1;
  1169.  
  1170.                         var files = Entries[etype].Where(_ => _.DirectoryName == dirname).Select(_ => _.FileName);
  1171.                         foreach (string filename in files)
  1172.                         {
  1173.                             byte[] data_bytes;
  1174.                             var found = Entries[etype].Find(_ => _.DirectoryName == dirname && _.FileName == filename);
  1175.  
  1176.                             if (found != null)
  1177.                                 ReadEntry(found, out data_bytes, false); // it should always be found
  1178.                             else
  1179.                                 data_bytes = new byte[0];
  1180.  
  1181.                             uint data_length = (uint)data_bytes.Length;
  1182.                             uint crc = Crc32.Compute(data_bytes);
  1183.                             string hash = String.Join("", sha1.ComputeHash(data_bytes));
  1184.                             if (seen.ContainsKey(hash))
  1185.                             {
  1186.                                 unique = false; lookup_offset = seen[hash];
  1187.                             }
  1188.                             else
  1189.                             {
  1190.                                 unique = true; lookup_offset = data_offset; seen.Add(hash, data_offset);
  1191.                             }
  1192.                             tree.Write(Encoding.UTF8.GetBytes(filename));           // FileName              ?
  1193.                             tree.Write(nul1);                                       // 00                    1
  1194.                             tree.Write(crc);                                        // CheckSum              4
  1195.                             tree.Write(preload_bytes);                              // PreloadBytes          2
  1196.                             tree.Write(archive_index);                              // ArchiveIndex          2
  1197.                             tree.Write(lookup_offset);                              // EntryOffset           4
  1198.                             tree.Write(data_length);                                // EntryLength           4
  1199.                             tree.Write(terminator);                                 // Terminator            2
  1200.                             if (unique)
  1201.                             {
  1202.                                 data.Write(data_bytes);                             // DataBytes written
  1203.                                 data_offset += data_length;                         // to secondary stream
  1204.                             }
  1205.                             tree_size += (uint)filename.Length + 19;
  1206.                         }
  1207.                         tree.Write(nul1);                                           // 00 Next Directory     1
  1208.                         tree_size += 1;
  1209.                     }
  1210.                     tree.Write(nul1);                                               // 00 Next Type          1
  1211.                     tree_size += 1;
  1212.                 }
  1213.  
  1214.                 tree.Write(nul1);                                                   // 00 Tree End           1
  1215.                 tree_size += 1;
  1216.  
  1217.                 mdata.Position = 0;
  1218.                 mdata.CopyTo(mtree);                                                // Data write            ?
  1219.                 data_size = (uint)mdata.Length;
  1220.  
  1221.                 mtree.Position = 8;
  1222.                 tree.Write(tree_size);                                              // TreeSize update
  1223.                 tree.Write(data_size);                                              // FileDataSectionSize update
  1224.  
  1225.                 mtree.Position = 28;
  1226.                 var tree_checksum = md5.ComputeHash(buff.ReadBytes((int)tree_size));
  1227.                 var archive000_checksum = md5.ComputeHash(new byte[0]);
  1228.                 mtree.Position = 28 + tree_size + data_size;
  1229.                 tree.Write(tree_checksum);                                          // TreeChecksum         16
  1230.                 tree.Write(archive000_checksum);                                    // Archive000Checksum   16
  1231.  
  1232.                 mtree.Position = 0;
  1233.                 var wholefile_checksum = md5.ComputeHash(buff.ReadBytes((int)(28 + tree_size + data_size + 32)));
  1234.                 mtree.Position = 28 + tree_size + data_size + 32;
  1235.                 tree.Write(wholefile_checksum);                                     // WholeFileChecksum    16
  1236.  
  1237.                 mtree.Position = 0;
  1238.                 mtree.CopyTo(fs);                                                   // File write  tree + data
  1239.             }
  1240.             sw.Stop();
  1241.             Console.WriteLine(String.Format("--- Written {0} in {1}s", fn, sw.Elapsed.TotalSeconds));
  1242.         }
  1243.  
  1244.         public void AddEntry(string dir, string name, string ext, byte[] data)
  1245.         {
  1246.             if (Entries == null)
  1247.                 Entries = new Dictionary<string,List<PackageEntry>>();
  1248.  
  1249.             if (!Entries.Keys.Contains(ext))
  1250.                 Entries.Add(ext, new List<PackageEntry>());
  1251.  
  1252.             var found = Entries[ext].Find(_ => _.DirectoryName == dir && _.FileName == name);
  1253.  
  1254.             if (found == null)
  1255.             {
  1256.                 Entries[ext].Add(new PackageEntry {
  1257.                   FileName = name, DirectoryName = dir, TypeName = ext,
  1258.                   CRC32 = 0, SmallData = data, ArchiveIndex = 0, Offset = 0, Length = 0
  1259.                 });
  1260.             }
  1261.             else
  1262.             {
  1263.                 found.Length = 0;
  1264.                 found.SmallData = data;
  1265.             }
  1266.         }
  1267.  
  1268.         public void AddEntry(string path, byte[] data)
  1269.         {
  1270.             var s = path.LastIndexOf("/");
  1271.             var dir = (s == -1) ? " " : path.Substring(0, s);
  1272.             var file = path.Substring(s + 1);
  1273.             s = file.LastIndexOf('.');
  1274.             var ext = (s == -1) ? " " : file.Substring(s + 1);
  1275.             var name = (s == -1) ? file : file.Substring(0, s);
  1276.  
  1277.             if (Entries == null)
  1278.                 Entries = new Dictionary<string,List<PackageEntry>>();
  1279.  
  1280.             if (!Entries.Keys.Contains(ext))
  1281.                 Entries.Add(ext, new List<PackageEntry>());
  1282.  
  1283.             var found = Entries[ext].Find(_ => _.DirectoryName == dir && _.FileName == name);
  1284.  
  1285.             if (found == null)
  1286.             {
  1287.                 Entries[ext].Add(new PackageEntry {
  1288.                   FileName = name, DirectoryName = dir, TypeName = ext,
  1289.                   CRC32 = 0, SmallData = data, ArchiveIndex = 0, Offset = 0, Length = 0
  1290.                 });
  1291.             }
  1292.             else
  1293.             {
  1294.                 found.Length = 0;
  1295.                 found.SmallData = data;
  1296.             }
  1297.         }
  1298.  
  1299.         public void AddFolder(string inputdir)
  1300.         {
  1301.             // include pak01_dir subfolder (if it exists) for manual overrides when modding
  1302.             if (!Directory.Exists(inputdir))
  1303.                 return;
  1304.  
  1305.             var paths = new List<string>();
  1306.             paths.AddRange(Directory.GetFiles(inputdir, "*.*", SearchOption.AllDirectories));
  1307.  
  1308.             if (paths.Count == 0)
  1309.                 return;
  1310.  
  1311.             Console.WriteLine("--- Adding files in \"{0}\"", inputdir);
  1312.  
  1313.             var excluded = new List<string>() { "zip", "reg", "rar", "msi", "exe", "dll", "com", "cmd", "bat", "vbs" };
  1314.             var iso = Encoding.GetEncoding("ISO-8859-1");
  1315.             var utf = Encoding.UTF8;
  1316.  
  1317.             foreach (var path in paths)
  1318.             {
  1319.                 byte[] latin = Encoding.Convert(utf, iso, utf.GetBytes(path.Substring(inputdir.Length + 1)));
  1320.                 string root = iso.GetString(latin).ToLower();
  1321.  
  1322.                 var ext = Path.GetExtension(root).TrimStart('.');
  1323.  
  1324.                 if (excluded.Contains(ext))
  1325.                     continue; // ERROR illegal extension!
  1326.  
  1327.                 if (ext == "")
  1328.                     ext = " "; // WARNING missing extension
  1329.  
  1330.                 var name = Path.GetFileNameWithoutExtension(root);
  1331.  
  1332.                 if (name == "")
  1333.                     name = " "; // WARNING missing filename
  1334.  
  1335.                 var dir = Path.GetDirectoryName(root).Replace('\\', '/');
  1336.  
  1337.                 if (dir == "")
  1338.                     dir = " "; // WARNING missing directoryname
  1339.  
  1340.                 AddEntry(dir, name, ext, File.ReadAllBytes(path));
  1341.             }
  1342.         }
  1343.  
  1344.         public void Filter(string types, string paths = null, string names = null)
  1345.         {
  1346.             var fTypes = String.IsNullOrEmpty(types) ? new List<string>() : types.Split(',').Select(_ => _.Trim()).ToList();
  1347.             var fPaths = String.IsNullOrEmpty(paths) ? new List<string>() : paths.Split(',').Select(_ => _.Trim()).ToList();
  1348.             var fNames = String.IsNullOrEmpty(names) ? new List<string>() : names.Split(',').Select(_ => _.Trim()).ToList();
  1349.  
  1350.             foreach (string etype in Entries.Keys.ToList())
  1351.             {
  1352.                 if (fTypes.Count > 0)
  1353.                 {
  1354.                     if (!fTypes.Contains(etype))
  1355.                     {
  1356.                         Entries.Remove(etype);
  1357.                         continue;
  1358.                     }
  1359.                 }
  1360.  
  1361.                 if (fPaths.Count > 0)
  1362.                     Entries[etype].RemoveAll(_ => !fPaths.Exists(_.DirectoryName.StartsWith));
  1363.  
  1364.                 if (fNames.Count > 0)
  1365.                     Entries[etype].RemoveAll(_ => !fNames.Exists(_.FileName.Equals));
  1366.             }
  1367.         }
  1368.     }
  1369.  
  1370.     public class PackageEntry
  1371.     {
  1372.         public string FileName { get; set; }
  1373.         public string DirectoryName { get; set; }
  1374.         public string TypeName { get; set; }
  1375.         public uint CRC32 { get; set; }
  1376.         public uint Length { get; set; }
  1377.         public uint Offset { get; set; }
  1378.         public ushort ArchiveIndex { get; set; }
  1379.         public uint TotalLength { get { return SmallData == null ? Length : Length + (uint)SmallData.Length; } }
  1380.         public byte[] SmallData { get; set; }
  1381.  
  1382.         public string GetFileName()
  1383.         {
  1384.             return TypeName == " " ? FileName : FileName + "." + TypeName;
  1385.         }
  1386.  
  1387.         public string GetFullPath()
  1388.         {
  1389.             return DirectoryName == " " ? GetFileName() : DirectoryName + Package.DirectorySeparatorChar + GetFileName();
  1390.         }
  1391.  
  1392.         public override string ToString()
  1393.         {
  1394.             return String.Format("{0} crc=0x{1:x2} metadatasz={2} fnumber={3} ofs=0x{4:x2} sz={5}",
  1395.               GetFullPath(), CRC32, SmallData.Length, ArchiveIndex, Offset, Length);
  1396.         }
  1397.     }
  1398.  
  1399.     public class ArchiveMD5SectionEntry
  1400.     {
  1401.         public uint ArchiveIndex { get; set; }
  1402.         public uint Offset { get; set; }
  1403.         public uint Length { get; set; }
  1404.         public byte[] Checksum { get; set; }
  1405.     }
  1406.  
  1407.     public static class Crc32
  1408.     {
  1409.         // CRC polynomial 0xEDB88320.
  1410.         private static readonly uint[] Table =
  1411.         {
  1412.            0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4,
  1413.            0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
  1414.            0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
  1415.            0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
  1416.            0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A,
  1417.            0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
  1418.            0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
  1419.            0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
  1420.            0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
  1421.            0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
  1422.            0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5,
  1423.            0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
  1424.            0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6,
  1425.            0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
  1426.            0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
  1427.            0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
  1428.            0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C,
  1429.            0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
  1430.            0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31,
  1431.            0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
  1432.            0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
  1433.            0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
  1434.            0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7,
  1435.            0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
  1436.            0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8,
  1437.            0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
  1438.         };
  1439.  
  1440.         public static uint Compute(byte[] buffer)
  1441.         {
  1442.             return ~buffer.Aggregate(0xFFFFFFFF, (current, t) => (current >> 8) ^ Table[t ^ (current & 0xff)]);
  1443.         }
  1444.     }
  1445.  
  1446. }
  1447.  
  1448.  
Tags: MOD DOTA2 qol
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement