public TagCache CreateTagCache(DirectoryInfo directory = null) { if (directory == null) { directory = Directory; } if (!directory.Exists) { directory.Create(); } var file = new FileInfo(Path.Combine(directory.FullName, "tags.dat")); TagCache cache = null; using (var stream = file.Create()) using (var writer = new BinaryWriter(stream)) { // Write the new resource cache file writer.Write(0); // padding writer.Write(32); // table offset writer.Write(0); // table entry count writer.Write(0); // padding writer.Write(0x01D0631BCC791704); // guid writer.Write(0); // padding writer.Write(0); // padding // Load the new resource cache file stream.Position = 0; cache = new TagCache(stream, new Dictionary <int, string>()); } return(cache); }
/// <summary> /// Creates a tag serialization context which serializes data into a tag. /// </summary> /// <param name="stream">The stream to write to.</param> /// <param name="cache">The cache file to write to.</param> /// <param name="stringIds">The stringID source to use.</param> /// <param name="tag">The tag to overwrite.</param> public TagSerializationContext(Stream stream, TagCache cache, StringIDCache stringIds, TagInstance tag) { _stream = stream; _cache = cache; _stringIds = stringIds; Tag = tag; }
public void Load(FileInfo file) { if (!file.Exists) { throw new FileNotFoundException(file.FullName); } if (file.Length < typeof(ModPackageHeader).GetSize()) { throw new FormatException(file.FullName); } using (var stream = file.OpenRead()) using (var reader = new EndianReader(stream, leaveOpen: true)) { var dataContext = new DataSerializationContext(reader); var deserializer = new TagDeserializer(CacheVersion.HaloOnline106708); Header = deserializer.Deserialize <ModPackageHeaderExtended>(dataContext); ReadMetadataSection(reader, dataContext, deserializer); ReadTagsSection(reader); ReadTagNamesSection(reader, dataContext, deserializer); ReadResourcesSection(reader); ReadMapFilesSection(reader); ReadCampaignFileSection(reader); Tags = new TagCache(TagsStream, TagNames); Resources = new ResourceCache(ResourcesStream); } }
// Utilities, I believe they don't belong here but I haven't found a better solution yet. I think GroupTag should store the string, // not the stringid, therefore we could hardcode the list of tag group types public bool TryGetCachedTag(int index, out CachedTag instance) { if (index < 0 || index >= TagCache.TagTable.Count()) { instance = null; return(false); } instance = TagCache.GetTag(index); return(true); }
public ModPackageExtended(FileInfo file = null) { if (file != null) { Load(file); } else { Tags = new TagCache(TagsStream, new Dictionary <int, string>()); Resources = new ResourceCache(ResourcesStream); Header.SectionTable = new ModPackageSectionTable(); } }
public ImportResourceCommand(OpenTagCache info) : base(CommandFlags.Inherit, "importresource", "Import as a new resource for a specified tag.", "importresource <cache file> <tag> <input file>\n", "For bitm, it only works with permutations. The path must end with a folder, and not a file.\n") { _info = info; _cache = info.Cache; _fileInfo = info.CacheFile; _stringIds = info.StringIDs; }
public GenerateLayoutsCommand(OpenTagCache info) : base(CommandFlags.Inherit, "genlayouts", "Generate tag layouts", "genlayouts <type> <output dir>", "Scans all tags in the file to guess tag layouts.\n" + "Layouts will be written to the output directory in the chosen format.\n" + "\n" + "Supported types: csharp, cpp") { _cache = info.Cache; _info = info; }
public HaloOnlineCacheContext(DirectoryInfo directory) : base(directory) { var tagNames = LoadTagNames(); using (var stream = OpenTagCacheRead()) TagCache = new TagCache(stream, tagNames); if (CacheVersion.Unknown == (Version = CacheVersionDetection.DetectFromTagCache(TagCache, out var closestVersion))) { Version = closestVersion; } Deserializer = new TagDeserializer(Version == CacheVersion.Unknown ? closestVersion : Version); Serializer = new TagSerializer(Version == CacheVersion.Unknown ? closestVersion : Version); StringIdResolver stringIdResolver = null; if (CacheVersionDetection.Compare(Version, CacheVersion.HaloOnline700123) >= 0) { stringIdResolver = new StringIdResolverMS30(); } else if (CacheVersionDetection.Compare(Version, CacheVersion.HaloOnline498295) >= 0) { stringIdResolver = new StringIdResolverMS28(); } else { stringIdResolver = new StringIdResolverMS23(); } using (var stream = OpenStringIdCacheRead()) StringIdCache = new StringIdCache(stream, stringIdResolver); TagGroup.Instances[new Tag("obje")] = new TagGroup(new Tag("obje"), Tag.Null, Tag.Null, GetStringId("object")); TagGroup.Instances[new Tag("item")] = new TagGroup(new Tag("item"), new Tag("obje"), Tag.Null, GetStringId("item")); TagGroup.Instances[new Tag("devi")] = new TagGroup(new Tag("devi"), new Tag("obje"), Tag.Null, GetStringId("device")); TagGroup.Instances[new Tag("unit")] = new TagGroup(new Tag("unit"), new Tag("obje"), Tag.Null, GetStringId("unit")); TagGroup.Instances[new Tag("rm ")] = new TagGroup(new Tag("rm "), Tag.Null, Tag.Null, GetStringId("render_method")); TagGroup.Instances[new Tag("test")] = new TagGroup(new Tag("test"), Tag.Null, Tag.Null, GetStringId("test_blah")); }
public TagCache CreateTagCache(Stream stream) { TagCache cache = null; using (var writer = new BinaryWriter(stream, Encoding.Default, true)) { // Write the new resource cache file writer.Write(0); // padding writer.Write(32); // table offset writer.Write(0); // table entry count writer.Write(0); // padding writer.Write(0x01D0631BCC791704); // guid writer.Write(0); // padding writer.Write(0); // padding // Load the new resource cache file stream.Position = 0; cache = new TagCache(stream, new Dictionary <int, string>()); } return(cache); }
public TagAnalyzer(TagCache cache) { _cache = cache; foreach (var group in cache.Tags.NonNull().Select(t => t.Group.Tag).Distinct()) _tagGroups.Add(group); }
/// <summary> /// Detects the engine that a tags.dat was built for. /// </summary> /// <param name="cache">The cache file.</param> /// <param name="closestGuess">On return, the closest guess for the engine's version.</param> /// <returns>The engine version if it is known for sure, otherwise <see cref="CacheVersion.Unknown"/>.</returns> public static CacheVersion DetectFromTagCache(TagCache cache, out CacheVersion closestGuess) { return(DetectFromTimestamp(cache.Timestamp, out closestGuess)); }
public bool TryGetTag(string name, out CachedTag result) { if (name.Length == 0) { result = null; return(false); } if (name == "null") { result = null; return(true); } if (name == "*") { if (TagCache.Count == 0) { result = null; return(false); } result = TagCache.TagTable.Last(); return(true); } if (name.StartsWith("*.")) { if (!name.TrySplit('.', out var startNamePieces) || !TryParseGroupTag(startNamePieces[1], out var starGroupTag)) { result = null; return(false); } result = TagCache.TagTable.Last(tag => tag != null && tag.IsInGroup(starGroupTag)); return(true); } if (name.StartsWith("0x")) { name = name.Substring(2); if (name.TrySplit('.', out var hexNamePieces)) { name = hexNamePieces[0]; } if (!int.TryParse(name, NumberStyles.HexNumber, null, out int tagIndex) || (TagCache.GetTag(tagIndex) == null)) { result = null; return(false); } result = TagCache.GetTag(tagIndex); return(true); } if (!name.TrySplit('.', out var namePieces) || !TryParseGroupTag(namePieces[namePieces.Length - 1], out var groupTag)) { throw new Exception($"Invalid tag name: {name}"); } //var tagName = namePieces[0]; var tagName = name.Substring(0, name.Length - (1 + namePieces[namePieces.Length - 1].Length)); foreach (var instance in TagCache.TagTable) { if (instance is null) { continue; } if (instance.IsInGroup(groupTag) && instance.Name == tagName) { result = instance; return(true); } } result = null; return(false); }
public override bool Execute(List<string> args) { if (args.Count != 1) return false; var destDir = new DirectoryInfo(args[0]); if (!destDir.Exists) { Write("Destination directory does not exist. Create it? [y/n] "); var answer = ReadLine().ToLower(); if (answer.Length == 0 || !(answer.StartsWith("y") || answer.StartsWith("n"))) return false; if (answer.StartsWith("y")) destDir.Create(); else return false; } WriteLine($"Generating cache files in \"{destDir.FullName}\"..."); var destTagsFile = new FileInfo(Combine(destDir.FullName, "tags.dat")); Write($"Generating {destTagsFile.FullName}..."); using (var tagCacheStream = destTagsFile.Create()) using (var writer = new BinaryWriter(tagCacheStream)) { writer.Write((int)0); // padding writer.Write((int)0); // tag list offset writer.Write((int)0); // tag count writer.Write((int)0); // padding writer.Write((long)130713360239499012); // timestamp writer.Write((long)0); // padding } WriteLine("done."); var destStringIDsFile = new FileInfo(Combine(destDir.FullName, "string_ids.dat")); Write($"Generating {destStringIDsFile.FullName}..."); using (var stringIDCacheStream = destStringIDsFile.Create()) using (var writer = new BinaryWriter(stringIDCacheStream)) { writer.Write((int)0); // string count writer.Write((int)0); // data size } WriteLine("done."); var resourceCachePaths = new string[] { Combine(destDir.FullName, "audio.dat"), Combine(destDir.FullName, "resources.dat"), Combine(destDir.FullName, "textures.dat"), Combine(destDir.FullName, "textures_b.dat"), Combine(destDir.FullName, "video.dat") }; foreach (var resourceCachePath in resourceCachePaths) { Write($"Generating {resourceCachePath}..."); using (var resourceCacheStream = File.Create(resourceCachePath)) using (var writer = new BinaryWriter(resourceCacheStream)) { writer.Write((int)0); // padding writer.Write((int)0); // table offset writer.Write((int)0); // resource count writer.Write((int)0); // padding } WriteLine("done."); } var dependencies = new Dictionary<int, TagInstance>(); LoadTagDependencies(0, ref dependencies); LoadTagDependencies(0x16, ref dependencies); LoadTagDependencies(0x27D7, ref dependencies); var destResourcesFile = new FileInfo(Combine(destDir.FullName, "resources.dat")); if (!destResourcesFile.Exists) { WriteLine($"Destination resource cache file does not exist: {destResourcesFile.FullName}"); return false; } var destTexturesFile = new FileInfo(Combine(destDir.FullName, "textures.dat")); if (!destTexturesFile.Exists) { WriteLine($"Destination texture cache file does not exist: {destTexturesFile.FullName}"); return false; } var destTexturesBFile = new FileInfo(Combine(destDir.FullName, "textures_b.dat")); if (!destTexturesBFile.Exists) { WriteLine($"Destination texture cache file does not exist: {destTexturesBFile.FullName}"); return false; } var destAudioFile = new FileInfo(Combine(destDir.FullName, "audio.dat")); if (!destAudioFile.Exists) { WriteLine($"Destination audio cache file does not exist: {destAudioFile.FullName}"); return false; } TagCache destTagCache; using (var stream = destTagsFile.OpenRead()) destTagCache = new TagCache(stream); DefinitionSet guessedVersion; var destVersion = Detect(destTagCache, out guessedVersion); if (destVersion == Unknown) { WriteLine($"Unrecognized target version! (guessed {GetVersionString(guessedVersion)})"); return true; } WriteLine($"Destination cache version: {GetVersionString(destVersion)}"); StringIDCache destStringIDCache; using (var stream = destStringIDsFile.OpenRead()) destStringIDCache = new StringIDCache(stream, Create(destVersion)); var destResources = new ResourceDataManager(); destResources.LoadCachesFromDirectory(destDir.FullName); var srcResources = new ResourceDataManager(); srcResources.LoadCachesFromDirectory(Info.CacheFile.DirectoryName); var destSerializer = new TagSerializer(destVersion); var destDeserializer = new TagDeserializer(destVersion); var destInfo = new OpenTagCache { Cache = destTagCache, CacheFile = destTagsFile, StringIDs = destStringIDCache, StringIDsFile = destStringIDsFile, Version = destVersion, Serializer = destSerializer, Deserializer = destDeserializer }; using (Stream srcStream = Info.OpenCacheRead(), destStream = destInfo.OpenCacheReadWrite()) { var maxDependency = dependencies.Keys.Max(); for (var i = 0; i <= maxDependency; i++) { var srcTag = Info.Cache.Tags[i]; if (srcTag == null) { destInfo.Cache.AllocateTag(); continue; } var srcData = Info.Cache.ExtractTagRaw(srcStream, srcTag); var destTag = destInfo.Cache.AllocateTag(srcTag.Group); destInfo.Cache.SetTagDataRaw(destStream, destTag, srcData); srcData = new byte[0]; } } WriteLine($"Done generating cache files in \"{destDir.FullName}\"."); return true; }
static void Main(string[] args) { CultureInfo.DefaultThreadCurrentCulture = CultureInfo.GetCultureInfo("en-US"); ConsoleHistory.Initialize(); // Get the file path from the first argument // If no argument is given, load tags.dat var filePath = (args.Length > 0) ? args[0] : "tags.dat"; // If there are extra arguments, use them to automatically execute a command List<string> autoexecCommand = null; if (args.Length > 1) autoexecCommand = args.Skip(1).ToList(); if (autoexecCommand == null) { Console.WriteLine("Tag Tool [{0}]", Assembly.GetExecutingAssembly().GetName().Version); Console.WriteLine(); Console.WriteLine("Please report any bugs and feature requests at"); Console.WriteLine("<https://github.com/camden-smallwood/TagTool/issues>."); Console.WriteLine(); Console.Write("Reading tags..."); } // Load the tag cache FileInfo fileInfo = null; TagCache cache = null; try { fileInfo = new FileInfo(filePath); using (var stream = fileInfo.Open(FileMode.Open, FileAccess.Read)) cache = new TagCache(stream); } catch(Exception e) { Console.WriteLine("ERROR: " + e.Message); ConsoleHistory.Dump("hott_*_tags_init.log"); return; } if (autoexecCommand == null) Console.WriteLine("{0} tags loaded.", cache.Tags.Count); // Version detection DefinitionSet closestVersion; var version = Definition.Detect(cache, out closestVersion); if (version != DefinitionSet.Unknown) { if (autoexecCommand == null) { var buildDate = DateTime.FromFileTime(cache.Timestamp); Console.WriteLine("- Detected target engine version {0}.", Definition.GetVersionString(closestVersion)); Console.WriteLine("- This cache file was built on {0} at {1}.", buildDate.ToShortDateString(), buildDate.ToShortTimeString()); } } else { Console.WriteLine("WARNING: The cache file's version was not recognized!"); Console.WriteLine("Using the closest known version {0}.", Definition.GetVersionString(closestVersion)); version = closestVersion; } // Load stringIDs Console.Write("Reading stringIDs..."); var stringIdPath = Path.Combine(fileInfo.DirectoryName ?? "", "string_ids.dat"); var resolver = StringIDResolverFactory.Create(version); StringIDCache stringIds = null; try { using (var stream = File.OpenRead(stringIdPath)) stringIds = new StringIDCache(stream, resolver); } catch (IOException) { Console.WriteLine("Warning: unable to open string_ids.dat!"); Console.WriteLine("Commands which require stringID values will be unavailable."); } catch (Exception e) { Console.WriteLine("ERROR: " + e.Message); ConsoleHistory.Dump("hott_*_string_ids_init.log"); return; } if (autoexecCommand == null && stringIds != null) { Console.WriteLine("{0} strings loaded.", stringIds.Strings.Count); Console.WriteLine(); } var info = new OpenTagCache { Cache = cache, CacheFile = fileInfo, StringIDs = stringIds, StringIDsFile = (stringIds != null) ? new FileInfo(stringIdPath) : null, Version = version, Serializer = new TagSerializer(version), Deserializer = new TagDeserializer(version), }; var tagNamesPath = "TagNames\\tagnames_" + Definition.GetVersionString(version) + ".csv"; if (File.Exists(tagNamesPath)) { using (var tagNamesStream = File.Open(tagNamesPath, FileMode.Open, FileAccess.Read)) { var reader = new StreamReader(tagNamesStream); while (!reader.EndOfStream) { var line = reader.ReadLine(); var separatorIndex = line.IndexOf(','); var indexString = line.Substring(2, separatorIndex - 2); int tagIndex; if (!int.TryParse(indexString, NumberStyles.HexNumber, null, out tagIndex)) tagIndex = -1; if (tagIndex < 0 || tagIndex >= cache.Tags.Count) continue; var nameString = line.Substring(separatorIndex + 1); if (nameString.Contains(" ")) { var lastSpaceIndex = nameString.LastIndexOf(' '); nameString = nameString.Substring(lastSpaceIndex + 1, nameString.Length - lastSpaceIndex - 1); } info.TagNames[tagIndex] = nameString; } reader.Close(); } } foreach (var tag in info.Cache.Tags) if (tag != null && !info.TagNames.ContainsKey(tag.Index)) info.TagNames[tag.Index] = $"0x{tag.Index:X4}"; // Create command context var contextStack = new CommandContextStack(); var tagsContext = TagCacheContextFactory.Create(contextStack, info); contextStack.Push(tagsContext); // If autoexecuting a command, just run it and return if (autoexecCommand != null) { if (!ExecuteCommand(contextStack.Context, autoexecCommand)) Console.WriteLine("Unrecognized command: {0}", autoexecCommand[0]); return; } Console.WriteLine("Enter \"help\" to list available commands. Enter \"exit\" to quit."); while (true) { // Read and parse a command Console.WriteLine(); Console.Write("{0}> ", contextStack.GetPath()); var commandLine = Console.ReadLine(); if (commandLine == null) break; string redirectFile; var commandArgs = ArgumentParser.ParseCommand(commandLine, out redirectFile); if (commandArgs.Count == 0) continue; // If "exit" or "quit" is given, pop the current context if (commandArgs[0] == "exit" || commandArgs[0] == "quit") { if (!contextStack.Pop()) break; // No more contexts - quit continue; } // Handle redirection var oldOut = Console.Out; StreamWriter redirectWriter = null; if (redirectFile != null) { redirectWriter = new StreamWriter(File.Open(redirectFile, FileMode.Create, FileAccess.Write)); Console.SetOut(redirectWriter); } // Try to execute it if (!ExecuteCommand(contextStack.Context, commandArgs)) { Console.WriteLine("Unrecognized command: {0}", commandArgs[0]); Console.WriteLine("Use \"help\" to list available commands."); } // Undo redirection if (redirectFile != null) { Console.SetOut(oldOut); redirectWriter.Dispose(); Console.WriteLine("Wrote output to {0}.", redirectFile); } } }
/// <summary> /// Detects the engine that a tags.dat was built for. /// </summary> /// <param name="cache">The cache file.</param> /// <param name="closestGuess">On return, the closest guess for the engine's version.</param> /// <returns>The engine version if it is known for sure, otherwise <see cref="DefinitionSet.Unknown"/>.</returns> public static DefinitionSet Detect(TagCache cache, out DefinitionSet closestGuess) { return DetectFromTimestamp(cache.Timestamp, out closestGuess); }