├── .gitignore ├── MapPacker ├── .editorconfig ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── AssetPacker.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── MapPacker.csproj ├── MapPacker.csproj.user ├── MapPacker.sln ├── favicon.ico ├── icons │ ├── icon_eagleone.png │ ├── icon_file.png │ ├── icon_folder.png │ ├── mappacker.png │ └── resizeIcon.png ├── sounds │ └── steam-message.wav └── style.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | MapPacker/bin/ 2 | MapPacker/obj/ 3 | MapPacker/.vs/ 4 | .vs/ 5 | -------------------------------------------------------------------------------- /MapPacker/.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | indent_style = tab 7 | indent_size = tab 8 | tab_size = 4 9 | 10 | # New line preferences 11 | end_of_line = crlf 12 | insert_final_newline = true 13 | 14 | 15 | #### C# Coding Conventions #### 16 | 17 | # Expression-bodied members 18 | csharp_style_expression_bodied_accessors = true:silent 19 | csharp_style_expression_bodied_constructors = false:silent 20 | csharp_style_expression_bodied_indexers = true:silent 21 | csharp_style_expression_bodied_lambdas = true:silent 22 | csharp_style_expression_bodied_local_functions = false:silent 23 | csharp_style_expression_bodied_methods = false:silent 24 | csharp_style_expression_bodied_operators = false:silent 25 | csharp_style_expression_bodied_properties = true:silent 26 | 27 | # Pattern matching preferences 28 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 29 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 30 | csharp_style_prefer_not_pattern = true:suggestion 31 | csharp_style_prefer_pattern_matching = true:silent 32 | csharp_style_prefer_switch_expression = true:suggestion 33 | 34 | # Null-checking preferences 35 | csharp_style_conditional_delegate_call = true:suggestion 36 | 37 | # Code-block preferences 38 | csharp_prefer_braces = true:silent 39 | 40 | # Expression-level preferences 41 | csharp_prefer_simple_default_expression = true:suggestion 42 | csharp_style_deconstructed_variable_declaration = true:suggestion 43 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 44 | csharp_style_inlined_variable_declaration = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 46 | csharp_style_prefer_index_operator = true:suggestion 47 | csharp_style_prefer_range_operator = true:suggestion 48 | csharp_style_throw_expression = true:suggestion 49 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 50 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 51 | 52 | # 'using' directive preferences 53 | csharp_using_directive_placement = outside_namespace:silent 54 | 55 | #### C# Formatting Rules #### 56 | 57 | # New line preferences 58 | csharp_new_line_before_catch = false 59 | csharp_new_line_before_else = false 60 | csharp_new_line_before_finally = false 61 | csharp_new_line_before_members_in_anonymous_types = false 62 | csharp_new_line_before_members_in_object_initializers = false 63 | csharp_new_line_before_open_brace = false 64 | csharp_new_line_between_query_expression_clauses = false 65 | 66 | # Indentation preferences 67 | csharp_indent_block_contents = true 68 | csharp_indent_braces = false 69 | csharp_indent_case_contents = true 70 | csharp_indent_case_contents_when_block = true 71 | csharp_indent_labels = no_change 72 | csharp_indent_switch_labels = true 73 | 74 | # Space preferences 75 | csharp_space_after_cast = false 76 | csharp_space_after_colon_in_inheritance_clause = true 77 | csharp_space_after_comma = true 78 | csharp_space_after_dot = false 79 | csharp_space_after_keywords_in_control_flow_statements = false 80 | csharp_space_after_semicolon_in_for_statement = true 81 | csharp_space_around_binary_operators = before_and_after 82 | csharp_space_around_declaration_statements = false 83 | csharp_space_before_colon_in_inheritance_clause = true 84 | csharp_space_before_comma = false 85 | csharp_space_before_dot = false 86 | csharp_space_before_open_square_brackets = false 87 | csharp_space_before_semicolon_in_for_statement = false 88 | csharp_space_between_empty_square_brackets = false 89 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 90 | csharp_space_between_method_call_name_and_opening_parenthesis = false 91 | csharp_space_between_method_call_parameter_list_parentheses = false 92 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 93 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 94 | csharp_space_between_method_declaration_parameter_list_parentheses = false 95 | csharp_space_between_parentheses = false 96 | csharp_space_between_square_brackets = false 97 | 98 | # Wrapping preferences 99 | csharp_preserve_single_line_blocks = true 100 | csharp_preserve_single_line_statements = true -------------------------------------------------------------------------------- /MapPacker/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MapPacker/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace MapPacker 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MapPacker/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /MapPacker/AssetPacker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Media; 7 | using System.Windows; 8 | 9 | namespace MapPacker { 10 | class AssetPacker{ 11 | 12 | public static HashSet assets = new HashSet(); 13 | private string assetPath; 14 | public string outputDirectory; 15 | private string vpkDirectory; 16 | private string vmapFile; 17 | 18 | public MainWindow parentForm; 19 | 20 | public AssetPacker(string assetDir, string sboxDir, string vmapFile) { 21 | this.assetPath = assetDir; 22 | this.vpkDirectory = sboxDir + "\\bin\\win64\\vpk.exe"; 23 | this.vmapFile = vmapFile; 24 | outputDirectory = Path.GetDirectoryName(vmapFile) + "\\" + Path.GetFileNameWithoutExtension(vmapFile); 25 | } 26 | 27 | private bool noNotf = false; 28 | private bool vpkFailed = false; 29 | 30 | public void GetAssets() { 31 | GetAssets(false); 32 | } 33 | 34 | public void GetAssets(bool noNotf = false) { 35 | 36 | if(noNotf) 37 | this.noNotf = noNotf; 38 | 39 | if(parentForm.Pack) { 40 | Directory.CreateDirectory(outputDirectory); 41 | } 42 | 43 | parentForm.SetProgress(10); 44 | 45 | // path where the map is 46 | string pathToMap = vmapFile; 47 | 48 | parentForm.PrintToConsole($"reading map file: {pathToMap}. This might take some time for big maps!"); 49 | GetAssetsFromMap(pathToMap, true); 50 | 51 | parentForm.SetProgress(30); 52 | 53 | if(assets.Count > 0) { 54 | parentForm.PrintToConsole("Found assets:"); 55 | } else { 56 | parentForm.PrintToConsole("No assets found in provided asset directory!"); 57 | parentForm.SetProgress(0); 58 | if(!this.noNotf) 59 | MessageBox.Show("No assets could be found!", "Alert", MessageBoxButton.OK, MessageBoxImage.Warning); 60 | return; 61 | } 62 | foreach(string asset in assets) { 63 | parentForm.PrintToConsole($"\t{asset}", "steam2004ControlText"); 64 | } 65 | 66 | parentForm.SetProgress(40); 67 | CopyFiles(); 68 | } 69 | 70 | public void ExecuteCommandAsync(string command) { 71 | try { 72 | //Asynchronously start the Thread to process the Execute command request. 73 | Thread objThread = new Thread(new ParameterizedThreadStart(ExecuteCommandSync)); 74 | //Make the thread as background thread. 75 | objThread.IsBackground = true; 76 | //Set the Priority of the thread. 77 | objThread.Priority = ThreadPriority.AboveNormal; 78 | //Start the thread. 79 | objThread.Start(command); 80 | } catch { 81 | } 82 | } 83 | 84 | public void ExecuteCommandSync(object command) { 85 | try { 86 | System.Diagnostics.ProcessStartInfo procStartInfo = 87 | _ = new System.Diagnostics.ProcessStartInfo(vpkDirectory, "/c"); 88 | 89 | procStartInfo.Arguments = $"{command}"; 90 | procStartInfo.RedirectStandardOutput = true; 91 | procStartInfo.UseShellExecute = false; 92 | procStartInfo.CreateNoWindow = true; 93 | System.Diagnostics.Process proc = new System.Diagnostics.Process(); 94 | proc.StartInfo = procStartInfo; 95 | proc.Start(); 96 | // Get the output into a string 97 | string result = proc.StandardOutput.ReadToEnd(); 98 | //parentForm.PrintToConsole($"{result}", "steam2004ControlText"); 99 | 100 | //vpk.exe does not output any important information and normal packing/unpacking does not allow for the verbose option..... 101 | 102 | } catch { 103 | } 104 | } 105 | 106 | public void CopyFiles() { 107 | 108 | if(!parentForm.Pack) { 109 | outputDirectory += "_content"; 110 | parentForm.PrintToConsole("\nAssets Copied: "); 111 | } else { 112 | ExtractVPK(); // extract first, pack after copying 113 | if(vpkFailed) { // treat as nopack operation of vpk.exe didn't run properly (FOR SOME BLOODY REASON) 114 | parentForm.PrintToConsole("\nAssets Copied: "); 115 | } else { 116 | parentForm.PrintToConsole("\nAssets Packed: "); 117 | } 118 | } 119 | 120 | int index = 0; 121 | foreach(string asset in assets) { 122 | index++; 123 | 124 | parentForm.SetProgress(40 + 30 * (int)Math.Round(index / (float)assets.Count)); 125 | 126 | string fileName = asset; 127 | try { 128 | string source = Path.Combine(assetPath, fileName); 129 | string destination = Path.Combine(outputDirectory, fileName); 130 | string directory = destination.Substring(0, destination.LastIndexOf("\\")); 131 | 132 | if(File.Exists(source)) { 133 | Directory.CreateDirectory(directory); 134 | File.Copy(source, destination, true); 135 | parentForm.PrintToConsole($"\t{asset}", "steam2004ControlText"); 136 | } else { 137 | // asset is in core files or another addon, ignore 138 | } 139 | } catch { 140 | // asset not found, broken or otherwise defunct, ignore 141 | } 142 | } 143 | if(parentForm.Pack && !vpkFailed) { 144 | PackVPK(); 145 | } else { 146 | parentForm.SetProgress(0); 147 | 148 | parentForm.PrintToConsole("\nAsset copy completed."); 149 | if(!this.noNotf) 150 | MessageBox.Show($"Content successfully copied! {(vpkFailed ? "\nvpk.exe failed! Content is located separate from the vpk." : "")}", "Complete", MessageBoxButton.OK, MessageBoxImage.Information); 151 | parentForm.SetCheckBoxEnabled(true); 152 | } 153 | } 154 | 155 | public void ExtractVPK() { 156 | parentForm.PrintToConsole("\nUnpacking vpk...\n"); 157 | //execute vpk file.vpk to extract 158 | string command = $"{vmapFile.Replace(".vmap", ".vpk")}"; 159 | ExecuteCommandSync(command); 160 | if(!Directory.Exists(outputDirectory) || vpkFailed) { //hacky check to see if vpk.exe ran properly... extra check is for debug 161 | vpkFailed = true; 162 | outputDirectory += "_content"; 163 | parentForm.PrintToConsole("\tvpk.exe failed to run! Treating this as a nopack operation instead... "); 164 | } 165 | if(parentForm.Pack && !vpkFailed) { // only "make" a backup if the original was extracted successfully, meaning it will be packed with content 166 | File.Move($"{vmapFile.Replace(".vmap", ".vpk")}", $"{vmapFile.Replace(".vmap", ".vpk.backup")}", true); 167 | parentForm.PrintToConsole("\nvpk unpacked\n"); 168 | } 169 | parentForm.SetProgress(95); 170 | } 171 | 172 | public void PackVPK() { 173 | // execute vpk outputDirectory to repack 174 | string command = $"{outputDirectory}"; 175 | ExecuteCommandSync(command); 176 | //parentForm.PrintToConsole("\nPacked vpk\n"); 177 | parentForm.SetProgress(0); 178 | 179 | // delete temp directory 180 | Directory.Delete(outputDirectory, true); 181 | 182 | //SoundPlayer player = new SoundPlayer(Properties.Resources.steam_message); 183 | //player.Play(); 184 | parentForm.PrintToConsole("\nAsset pack completed.\n"); 185 | if(!this.noNotf) 186 | MessageBox.Show("Map Successfully packed!", "Complete", MessageBoxButton.OK, MessageBoxImage.Information); 187 | parentForm.SetCheckBoxEnabled(true); 188 | } 189 | 190 | public void GetAssetsFromMap(string map, bool rootMap = false) { // this uses a full path, since it's kinda for the original map 191 | try { 192 | var mapData = File.ReadAllBytes($"{map}"); 193 | if(rootMap) 194 | parentForm.PrintToConsole($"Read Vmap file, parsing..."); 195 | AssetFile mapFile = AssetFile.From(mapData); 196 | mapFile.TrimAssetList(); // trim it to the space where assets are actually referenced, using "map_assets_references" markers 197 | 198 | foreach(var item in mapFile.SplitNull()) { 199 | if(item.EndsWith("vmat")) { 200 | GetAssetsFromMaterial(item); 201 | } else if(item.EndsWith("vmdl")) { 202 | GetAssetsFromModel(item); 203 | } else if(item.EndsWith("vpcf")) { 204 | GetAssetsFromParticle(item); 205 | } else if(item.EndsWith("vmap")) { 206 | var mapItem = CleanAssetPath(item); 207 | var path = TrimAddonPath(map); 208 | if(!($"{path}\\{item}" == map)) { // this will also deal with 3d skybox content 209 | GetAssetsFromMap($"{path}\\{item}"); // we can assume that prefabs will also be wherever the original map is 210 | GetAssetsFromMap($"{assetPath}\\{item}"); // prefabs *could* also be in the asset directory however 211 | } 212 | } else if (item.EndsWith("vpost")) { 213 | AddAsset(item); // post processing file 214 | } 215 | } 216 | } catch { 217 | // asset not in asset directory or not found 218 | } 219 | } 220 | 221 | // this is for all Model related asset types, including legacy support; vmdl, vmesh, vphys, vseq, vagrp and vanim 222 | public void GetAssetsFromModel(string item) { 223 | try { 224 | var data = File.ReadAllBytes($"{assetPath}\\{item}_c"); 225 | AddAsset(item); // add vmdl_c (or legacy reference) 226 | var material = AssetFile.From(data); 227 | foreach(var matItem in material.SplitNull()) { 228 | if(matItem.EndsWith("vmat")) { 229 | // add vmat_c referenced by the vmdl_c 230 | GetAssetsFromMaterial(matItem); // add vtex_c referenced by the vmat_c 231 | 232 | // LEGACY IMPORT SUPPORT 233 | } else if(matItem.EndsWith("vmesh")) { 234 | // add vmesh_c referenced by the vmdl_c 235 | GetAssetsFromModel(matItem); // add vmat_c referenced by the vmesh_c 236 | } else if(matItem.EndsWith("vphys")) { 237 | AddAsset(matItem); // add vphys_c referenced by a vmesh_c 238 | } else if(matItem.EndsWith("vseq")) { 239 | AddAsset(matItem); // add vseq_c referenced by a vmdl_c 240 | } else if(matItem.EndsWith("vagrp")) { 241 | // add vagrp_c referenced by a vmdl_c 242 | GetAssetsFromModel(matItem); // add vanim_c referenced by a vagrp 243 | } else if(matItem.EndsWith("vanim")) { 244 | AddAsset(matItem); // add vanim_c referenced by a vagrp_c 245 | } 246 | } 247 | } catch { 248 | // asset not in asset directory or not found 249 | } 250 | } 251 | 252 | public void GetAssetsFromMaterial(string item) { 253 | try { 254 | var materialData = File.ReadAllBytes($"{assetPath}\\{item}_c"); 255 | AddAsset(item); // add vmat_c 256 | var material = AssetFile.From(materialData); 257 | foreach(var texItem in material.SplitNull()) { 258 | if(texItem.EndsWith("vtex")) { 259 | AddAsset(texItem); // add vtex_c referenced by the vmat_c 260 | } 261 | } 262 | } catch { 263 | // asset not in asset directory or not found 264 | } 265 | } 266 | 267 | public void GetAssetsFromParticle(string item) { 268 | try { 269 | var materialData = File.ReadAllBytes($"{assetPath}\\{item}_c"); 270 | AddAsset(item); // add vpcf_c 271 | var material = AssetFile.From(materialData); 272 | foreach(var assetItem in material.SplitNull()) { 273 | if(assetItem.EndsWith("vtex")) { 274 | AddAsset(assetItem); // add vtex_c referenced by the vpcf_c 275 | } else if(assetItem.EndsWith("vmat")) { 276 | GetAssetsFromMaterial(assetItem); 277 | } else if(assetItem.EndsWith("vmdl")) { 278 | GetAssetsFromModel(assetItem); 279 | } else if(assetItem.EndsWith("vpcf")) { 280 | if(!(item == assetItem)) // if there's a self reference in the particle. this is often the case for some reason 281 | GetAssetsFromParticle(assetItem); 282 | } 283 | } 284 | } catch { 285 | // asset not in asset directory or not found 286 | } 287 | } 288 | 289 | public static void AddAsset(string item) { 290 | // basic clean 291 | var asset = CleanAssetPath(item); 292 | 293 | if(!asset.EndsWith("_c")) { 294 | // make sure the file is an asset file as we'd want it 295 | return; 296 | } 297 | assets.Add(asset); 298 | } 299 | 300 | public static string CleanAssetPath(string item) { 301 | var asset = item.Replace("\r\n", "").Replace("\r", "").Replace("\n", "").Replace("/", "\\"); 302 | 303 | // this is dumb 304 | if(asset.EndsWith("vmat")) { 305 | asset = asset.Replace("vmat", "vmat_c"); 306 | } else if(asset.EndsWith("vmdl")) { 307 | asset = asset.Replace("vmdl", "vmdl_c"); 308 | } else if(asset.EndsWith("vtex")) { 309 | asset = asset.Replace("vtex", "vtex_c"); 310 | } else if(asset.EndsWith("vpcf")) { 311 | asset = asset.Replace("vpcf", "vpcf_c"); 312 | } else if(asset.EndsWith("vsnd")) { 313 | asset = asset.Replace("vsnd", "vsnd_c"); 314 | } else if(asset.EndsWith("vphys")) { 315 | asset = asset.Replace("vphys", "vphys_c"); 316 | } else if(asset.EndsWith("vmesh")) { 317 | asset = asset.Replace("vmesh", "vmesh_c"); 318 | } else if(asset.EndsWith("vanim")) { 319 | asset = asset.Replace("vanim", "vanim_c"); 320 | } else if(asset.EndsWith("vpost")) { 321 | asset = asset.Replace("vpost", "vpost_c"); 322 | } else if(asset.EndsWith("vseq")) { 323 | asset = asset.Replace("vseq", "vseq_c"); 324 | } else if(asset.EndsWith("vagrp")) { 325 | asset = asset.Replace("vagrp", "vagrp_c"); 326 | } 327 | return asset; 328 | } 329 | 330 | public static string TrimAddonPath(string path) { 331 | // trim full path to .../addons/addonName/ 332 | var addonsIndex = path.LastIndexOf("addons"); 333 | var trim1 = path.Substring(0, addonsIndex + 7); 334 | var trim2 = path.Substring(addonsIndex + 7, path.Length - addonsIndex - 7); 335 | var addonName = trim2.Substring(0, trim2.IndexOf("\\")); 336 | return trim1 + addonName; 337 | } 338 | } 339 | 340 | // this should probably get reworked with asset type enums 341 | public class AssetFile { 342 | private string assetReference; 343 | public static AssetFile From(byte[] bytes) { 344 | AssetFile asset = new AssetFile(); 345 | asset.assetReference = Encoding.Default.GetString(bytes); 346 | return asset; 347 | } 348 | 349 | public string[] SplitNull() { 350 | string[] arr = { "\0" }; // lol why doesn't this have an overload for strings 351 | return assetReference.Split(arr, StringSplitOptions.RemoveEmptyEntries); 352 | } 353 | 354 | public string TrimAssetList(string marker = "map_asset_references") { 355 | var start = assetReference.IndexOf(marker); 356 | var end = assetReference.LastIndexOf(marker); 357 | var output = assetReference.Substring(start, end - start); 358 | return output; 359 | } 360 | 361 | // obsolete since we're doing prefab scans anyways 362 | public bool IsMapSkybox() { 363 | var splitStrings = this.SplitNull(); 364 | 365 | for(var i = 0; i < splitStrings.Length; i++) { 366 | var item = splitStrings[i]; 367 | if(item == "mapUsageType") { 368 | if(splitStrings[i + 1] == "skybox") { // skybox map type 369 | return true; 370 | } 371 | } 372 | } 373 | return false; 374 | } 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /MapPacker/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | #FFD8DED3 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 88 | 89 | 102 | 103 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 178 | 179 | 183 | 216 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 |