private void RunImportJob(IEnumerable <string> files) { bool didSomething = false; var basePath = PathManager.MakeAbsolutePath(Global.Config.PathEntries.FirmwaresPathFragment, null); string errors = ""; foreach (var f in files) { using var hf = new HawkFile(f); if (hf.IsArchive) { // blech. the worst extraction code in the universe. string extractPath = $"{Path.GetTempFileName()}.dir"; DirectoryInfo di = Directory.CreateDirectory(extractPath); try { foreach (var ai in hf.ArchiveItems) { hf.BindArchiveMember(ai); var stream = hf.GetStream(); var ms = new MemoryStream(); Util.CopyStream(hf.GetStream(), ms, stream.Length); string outfile = ai.Name; string myname = Path.GetFileName(outfile); outfile = Path.Combine(extractPath, myname); File.WriteAllBytes(outfile, ms.ToArray()); hf.Unbind(); if (_cbAllowImport.Checked || Manager.CanFileBeImported(outfile)) { didSomething |= RunImportJobSingle(basePath, outfile, ref errors); } } } finally { di.Delete(true); } } else { if (_cbAllowImport.Checked || Manager.CanFileBeImported(hf.CanonicalFullPath)) { didSomething |= RunImportJobSingle(basePath, f, ref errors); } } } if (!string.IsNullOrEmpty(errors)) { MessageBox.Show(errors, "Error importing these files"); } if (didSomething) { DoScan(); } }
void RunImportJob(IEnumerable <string> files) { bool didSomething = false; var basepath = PathManager.MakeAbsolutePath(Global.Config.PathEntries.FirmwaresPathFragment, null); string errors = ""; foreach (var f in files) { using (var hf = new HawkFile(f)) { if (hf.IsArchive) { //blech. the worst extraction code in the universe. string extractpath = System.IO.Path.GetTempFileName() + ".dir"; DirectoryInfo di = null; di = System.IO.Directory.CreateDirectory(extractpath); try { foreach (var ai in hf.ArchiveItems) { hf.BindArchiveMember(ai); var stream = hf.GetStream(); var ms = new MemoryStream(); Util.CopyStream(hf.GetStream(), ms, stream.Length); string outfile = ai.Name; string myname = Path.GetFileName(outfile); outfile = Path.Combine(extractpath, myname); File.WriteAllBytes(outfile, ms.ToArray()); hf.Unbind(); didSomething |= RunImportJobSingle(basepath, outfile, ref errors); } } finally { di.Delete(true); } } else { didSomething |= RunImportJobSingle(basepath, f, ref errors); } } } if (errors != "") { System.Windows.Forms.MessageBox.Show(errors, "Error importing these files"); } if (didSomething) { DoScan(); } }
public static XmlGame Create(HawkFile f) { try { var x = new XmlDocument(); x.Load(f.GetStream()); var y = x.SelectSingleNode("./BizHawk-XMLGame"); if (y == null) { return(null); } var ret = new XmlGame { GI = { System = y.Attributes["System"].Value, Name = y.Attributes["Name"].Value, Status = RomStatus.Unknown }, Xml = x }; var n = y.SelectSingleNode("./LoadAssets"); if (n != null) { var HashStream = new MemoryStream(); int?OriginalIndex = null; foreach (XmlNode a in n.ChildNodes) { string filename = a.Attributes["FileName"].Value; byte[] data = new byte[0]; if (filename[0] == '|') { // in same archive var ai = f.FindArchiveMember(filename.Substring(1)); if (ai != null) { if (OriginalIndex == null) { OriginalIndex = f.GetBoundIndex(); } f.Unbind(); f.BindArchiveMember(ai); data = f.GetStream().ReadAllBytes(); } else { throw new Exception("Couldn't load XMLGame Asset \"" + filename + "\""); } } else { // relative path var fullpath = Path.GetDirectoryName(f.CanonicalFullPath.Split('|').First()) ?? string.Empty; fullpath = Path.Combine(fullpath, filename.Split('|').First()); try { using (var hf = new HawkFile(fullpath)) { if (hf.IsArchive) { var archiveItem = hf.ArchiveItems.First(ai => ai.Name == filename.Split('|').Skip(1).First()); hf.Unbind(); hf.BindArchiveMember(archiveItem); data = hf.GetStream().ReadAllBytes(); } else { data = File.ReadAllBytes(fullpath.Split('|').First()); } } } catch { throw new Exception("Couldn't load XMLGame LoadAsset \"" + filename + "\""); } } ret.Assets.Add(new KeyValuePair <string, byte[]>(filename, data)); using (var sha1 = System.Security.Cryptography.SHA1.Create()) { sha1.TransformFinalBlock(data, 0, data.Length); HashStream.Write(sha1.Hash, 0, sha1.Hash.Length); } } ret.GI.Hash = HashStream.GetBuffer().HashSHA1(0, (int)HashStream.Length); HashStream.Close(); if (OriginalIndex != null) { f.Unbind(); f.BindArchiveMember((int)OriginalIndex); } } else { ret.GI.Hash = "0000000000000000000000000000000000000000"; } return(ret); } catch (Exception ex) { throw new InvalidOperationException(ex.ToString()); } }
public RomGame(HawkFile file, string patch) { if (!file.Exists) { throw new Exception("The file needs to exist, yo."); } Extension = file.Extension; var stream = file.GetStream(); int fileLength = (int)stream.Length; // read the entire contents of the file into memory. // unfortunate in the case of large files, but thats what we've got to work with for now. // if we're offset exactly 512 bytes from a 1024-byte boundary, // assume we have a header of that size. Otherwise, assume it's just all rom. // Other 'recognized' header sizes may need to be added. int headerOffset = fileLength % BankSize; if (headerOffset.In(0, 512) == false) { Console.WriteLine("ROM was not a multiple of 1024 bytes, and not a recognized header size: {0}. Assume it's purely ROM data.", headerOffset); headerOffset = 0; } else if (headerOffset > 0) { Console.WriteLine("Assuming header of {0} bytes.", headerOffset); } // read the entire file into FileData. FileData = new byte[fileLength]; stream.Read(FileData, 0, fileLength); // if there was no header offset, RomData is equivalent to FileData // (except in cases where the original interleaved file data is necessary.. in that case we'll have problems.. // but this whole architecture is not going to withstand every peculiarity and be fast as well. if (headerOffset == 0) { RomData = FileData; } else { // if there was a header offset, read the whole file into FileData and then copy it into RomData (this is unfortunate, in case RomData isnt needed) int romLength = fileLength - headerOffset; RomData = new byte[romLength]; Buffer.BlockCopy(FileData, headerOffset, RomData, 0, romLength); } if (file.Extension == ".SMD") { RomData = DeInterleaveSMD(RomData); } if (file.Extension == ".Z64" || file.Extension == ".N64" || file.Extension == ".V64") { RomData = MutateSwapN64(RomData); } // note: this will be taking several hashes, of a potentially large amount of data.. yikes! GameInfo = Database.GetGameInfo(RomData, file.Name); CheckForPatchOptions(); if (patch != null) { using (var patchFile = new HawkFile(patch)) { patchFile.BindFirstOf("IPS"); if (patchFile.IsBound) { IPS.Patch(RomData, patchFile.GetStream()); } } } }
public BootGodDb(string basePath) { // notes: there can be multiple each of prg,chr,wram,vram // we aren't tracking the individual hashes yet. string xmlPath = Path.Combine(basePath, "NesCarts.xml"); string x7zPath = Path.Combine(basePath, "NesCarts.7z"); bool loadXml = File.Exists(xmlPath); using var nesCartFile = new HawkFile(loadXml ? xmlPath : x7zPath); if (!loadXml) { nesCartFile.BindFirst(); } var stream = nesCartFile.GetStream(); // in anticipation of any slowness annoying people, and just for shits and giggles, i made a super fast parser int state = 0; var xmlReader = XmlReader.Create(stream); CartInfo currCart = null; string currName = null; while (xmlReader.Read()) { switch (state) { case 0: if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "game") { currName = xmlReader.GetAttribute("name"); state = 1; } break; case 2: if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "board") { currCart.BoardType = xmlReader.GetAttribute("type"); currCart.Pcb = xmlReader.GetAttribute("pcb"); int mapper = int.Parse(xmlReader.GetAttribute("mapper")); if (validate && mapper > 255) { throw new Exception("didnt expect mapper>255!"); } // we don't actually use this value at all; only the board name state = 3; } break; case 3: if (xmlReader.NodeType == XmlNodeType.Element) { switch (xmlReader.Name) { case "prg": currCart.PrgSize += (short)ParseSize(xmlReader.GetAttribute("size")); break; case "chr": currCart.ChrSize += (short)ParseSize(xmlReader.GetAttribute("size")); break; case "vram": currCart.VramSize += (short)ParseSize(xmlReader.GetAttribute("size")); break; case "wram": currCart.WramSize += (short)ParseSize(xmlReader.GetAttribute("size")); if (xmlReader.GetAttribute("battery") != null) { currCart.WramBattery = true; } break; case "pad": currCart.PadH = byte.Parse(xmlReader.GetAttribute("h")); currCart.PadV = byte.Parse(xmlReader.GetAttribute("v")); break; case "chip": currCart.Chips.Add(xmlReader.GetAttribute("type")); if (xmlReader.GetAttribute("battery") != null) { currCart.WramBattery = true; } break; } } else if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "board") { state = 4; } break; case 4: if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "cartridge") { _sha1Table[currCart.Sha1].Add(currCart); currCart = null; state = 5; } break; case 5: case 1: if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "cartridge") { currCart = new CartInfo(); currCart.System = xmlReader.GetAttribute("system"); currCart.Sha1 = "sha1:" + xmlReader.GetAttribute("sha1"); currCart.Name = currName; state = 2; } if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.Name == "game") { currName = null; state = 0; } break; } } //end xmlreader loop }
protected override void RunImport() { Result.Movie.HeaderEntries[HeaderKeys.Core] = CoreNames.Bsnes; var hf = new HawkFile(SourceFile.FullName); // .LSMV movies are .zip files containing data files. if (!hf.IsArchive) { Result.Errors.Add("This is not an archive."); return; } var ss = new LibsnesCore.SnesSyncSettings { LeftPort = LibsnesControllerDeck.ControllerType.Gamepad, RightPort = LibsnesControllerDeck.ControllerType.Gamepad }; _deck = new LibsnesControllerDeck(ss); string platform = "SNES"; foreach (var item in hf.ArchiveItems) { if (item.Name == "authors") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string authors = Encoding.UTF8.GetString(stream.ReadAllBytes()); string authorList = ""; string authorLast = ""; using (var reader = new StringReader(authors)) { string line; // Each author is on a different line. while ((line = reader.ReadLine()) != null) { string author = line.Trim(); if (author != "") { if (authorLast != "") { authorList += $"{authorLast}, "; } authorLast = author; } } } if (authorList != "") { authorList += "and "; } if (authorLast != "") { authorList += authorLast; } Result.Movie.HeaderEntries[HeaderKeys.Author] = authorList; hf.Unbind(); } else if (item.Name == "coreversion") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string coreVersion = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.Comments.Add($"CoreOrigin {coreVersion}"); hf.Unbind(); } else if (item.Name == "gamename") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string gameName = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.HeaderEntries[HeaderKeys.GameName] = gameName; hf.Unbind(); } else if (item.Name == "gametype") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string gametype = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); // TODO: Handle the other types. switch (gametype) { case "gdmg": platform = "GB"; break; case "ggbc": case "ggbca": platform = "GBC"; break; case "sgb_ntsc": case "sgb_pal": platform = "SNES"; Config.GbAsSgb = true; break; } bool pal = gametype == "snes_pal" || gametype == "sgb_pal"; Result.Movie.HeaderEntries[HeaderKeys.Pal] = pal.ToString(); hf.Unbind(); } else if (item.Name == "input") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string input = Encoding.UTF8.GetString(stream.ReadAllBytes()); int lineNum = 0; using (var reader = new StringReader(input)) { lineNum++; string line; while ((line = reader.ReadLine()) != null) { if (line == "") { continue; } // Insert an empty frame in lsmv snes movies // https://github.com/TASVideos/BizHawk/issues/721 // Both emulators send the input to bsnes core at the same V interval, but: // lsnes' frame boundary occurs at V = 241, after which the input is read; // BizHawk's frame boundary is just before automatic polling; // This isn't a great place to add this logic but this code is a mess if (lineNum == 1 && platform == "SNES") { // Note that this logic assumes the first non-empty log entry is a valid input log entry // and that it is NOT a subframe input entry. It seems safe to assume subframe input would not be on the first line Result.Movie.AppendFrame(EmptyLmsvFrame()); } ImportTextFrame(line, platform); } } hf.Unbind(); } else if (item.Name.StartsWith("moviesram.")) { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); byte[] movieSram = stream.ReadAllBytes(); if (movieSram.Length != 0) { Result.Errors.Add("Movies that begin with SRAM are not supported."); hf.Unbind(); return; } } else if (item.Name == "port1") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string port1 = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.HeaderEntries["port1"] = port1; ss.LeftPort = LibsnesControllerDeck.ControllerType.Gamepad; _deck = new LibsnesControllerDeck(ss); hf.Unbind(); } else if (item.Name == "port2") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string port2 = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.HeaderEntries["port2"] = port2; ss.RightPort = LibsnesControllerDeck.ControllerType.Gamepad; _deck = new LibsnesControllerDeck(ss); hf.Unbind(); } else if (item.Name == "projectid") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string projectId = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.HeaderEntries["ProjectID"] = projectId; hf.Unbind(); } else if (item.Name == "rerecords") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string rerecords = Encoding.UTF8.GetString(stream.ReadAllBytes()); int rerecordCount; // Try to parse the re-record count as an integer, defaulting to 0 if it fails. try { rerecordCount = int.Parse(rerecords); } catch { rerecordCount = 0; } Result.Movie.Rerecords = (ulong)rerecordCount; hf.Unbind(); } else if (item.Name.EndsWith(".sha256")) { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string rom = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); int pos = item.Name.LastIndexOf(".sha256"); string name = item.Name.Substring(0, pos); Result.Movie.HeaderEntries[$"SHA256_{name}"] = rom; hf.Unbind(); } else if (item.Name == "savestate") { Result.Errors.Add("Movies that begin with a savestate are not supported."); return; } else if (item.Name == "subtitles") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string subtitles = Encoding.UTF8.GetString(stream.ReadAllBytes()); using (var reader = new StringReader(subtitles)) { string line; while ((line = reader.ReadLine()) != null) { var subtitle = ImportTextSubtitle(line); if (!string.IsNullOrEmpty(subtitle)) { Result.Movie.Subtitles.AddFromString(subtitle); } } } hf.Unbind(); } else if (item.Name == "starttime.second") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string startSecond = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.HeaderEntries["StartSecond"] = startSecond; hf.Unbind(); } else if (item.Name == "starttime.subsecond") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string startSubSecond = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.HeaderEntries["StartSubSecond"] = startSubSecond; hf.Unbind(); } else if (item.Name == "systemid") { hf.BindArchiveMember(item.Index); var stream = hf.GetStream(); string systemId = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); Result.Movie.Comments.Add($"{EmulationOrigin} {systemId}"); hf.Unbind(); } } Result.Movie.HeaderEntries[HeaderKeys.Platform] = platform; Result.Movie.SyncSettingsJson = ConfigService.SaveWithType(ss); Config.PreferredCores["SNES"] = CoreNames.Bsnes; // TODO: convert to snes9x if it is the user's preference }
/// <exception cref="Exception"><paramref name="file"/> does not exist</exception> public RomGame(HawkFile file, string patch) { if (!file.Exists) { throw new Exception("The file needs to exist, yo."); } Extension = file.Extension.ToUpperInvariant(); var stream = file.GetStream(); int fileLength = (int)stream.Length; // read the entire contents of the file into memory. // unfortunate in the case of large files, but that's what we've got to work with for now. // if we're offset exactly 512 bytes from a 1024-byte boundary, // assume we have a header of that size. Otherwise, assume it's just all rom. // Other 'recognized' header sizes may need to be added. int headerOffset = fileLength % BankSize; if (headerOffset.In(0, 128, 512) == false) { Console.WriteLine("ROM was not a multiple of 1024 bytes, and not a recognized header size: {0}. Assume it's purely ROM data.", headerOffset); headerOffset = 0; } else if (headerOffset > 0) { Console.WriteLine("Assuming header of {0} bytes.", headerOffset); } // read the entire file into FileData. FileData = new byte[fileLength]; stream.Position = 0; stream.Read(FileData, 0, fileLength); // if there was no header offset, RomData is equivalent to FileData // (except in cases where the original interleaved file data is necessary.. in that case we'll have problems.. // but this whole architecture is not going to withstand every peculiarity and be fast as well. if (headerOffset == 0) { RomData = FileData; } else if (file.Extension == ".dsk" || file.Extension == ".tap" || file.Extension == ".tzx" || file.Extension == ".pzx" || file.Extension == ".csw" || file.Extension == ".wav" || file.Extension == ".cdt") { // these are not roms. unfortunately if treated as such there are certain edge-cases // where a header offset is detected. This should mitigate this issue until a cleaner solution is found // (-Asnivor) RomData = FileData; } else { // if there was a header offset, read the whole file into FileData and then copy it into RomData (this is unfortunate, in case RomData isn't needed) int romLength = fileLength - headerOffset; RomData = new byte[romLength]; Buffer.BlockCopy(FileData, headerOffset, RomData, 0, romLength); } if (file.Extension == ".smd") { RomData = DeInterleaveSMD(RomData); } if (file.Extension == ".z64" || file.Extension == ".n64" || file.Extension == ".v64") { RomData = MutateSwapN64(RomData); } // note: this will be taking several hashes, of a potentially large amount of data.. yikes! GameInfo = Database.GetGameInfo(RomData, file.Name); if (GameInfo.NotInDatabase && headerOffset == 128 && file.Extension == ".a78") { // if the game is not in the DB, add the header back in so the core can use it // for now only .A78 games, but probably should be for other systems as well RomData = FileData; } CheckForPatchOptions(); if (patch != null) { using var patchFile = new HawkFile(patch); patchFile.BindFirstOf(".ips"); if (patchFile.IsBound) { RomData = IPS.Patch(RomData, patchFile.GetStream()); } } }
/// <summary> /// Load Header information only for displaying file information in dialogs such as play movie /// TODO - consider not loading the SavestateBinaryBase64Blob key? /// </summary> public bool PreLoadHeaderAndLength(HawkFile hawkFile) { Loaded = false; var file = new FileInfo(hawkFile.CanonicalFullPath); if (file.Exists == false) { return(false); } Header.Clear(); _log.Clear(); var origStreamPosn = hawkFile.GetStream().Position; hawkFile.GetStream().Position = 0; // Reset to start // No using block because we're sharing the stream and need to give it back undisposed. var sr = new StreamReader(hawkFile.GetStream()); for (;;) { // read to first space (key/value delimeter), or pipe, or EOF int first = sr.Read(); if (first == -1) { break; } // EOF if (first == '|') // pipe: begin input log { // NOTE - this code is a bit convoluted due to its predating the basic outline of the parser which was upgraded in may 2014 var line = '|' + sr.ReadLine(); // how many bytes are left, total? long remain = sr.BaseStream.Length - sr.BaseStream.Position; // try to find out whether we use \r\n or \n // but only look for 1K characters. bool usesR = false; for (int i = 0; i < 1024; i++) { int c = sr.Read(); if (c == -1) { break; } if (c == '\r') { usesR = true; break; } if (c == '\n') { break; } } int lineLen = line.Length + 1; // account for \n if (usesR) { lineLen++; // account for \r } _preloadFramecount = (int)(remain / lineLen); // length is remaining bytes / length per line _preloadFramecount++; // account for the current line break; } else { // a header line. finish reading key token, to make sure it isn't one of the FORBIDDEN keys var sbLine = new StringBuilder(); sbLine.Append((char)first); for (;;) { int c = sr.Read(); if (c == -1 || c == '\n' || c == ' ') { break; } sbLine.Append((char)c); } var line = sbLine.ToString(); // ignore these suckers, theyre way too big for preloading. seriously, we will get out of memory errors. var skip = line == HeaderKeys.SAVESTATEBINARYBASE64BLOB; if (skip) { // skip remainder of the line sr.DiscardBufferedData(); var stream = sr.BaseStream; for (;;) { int c = stream.ReadByte(); if (c == -1 || c == '\n') { break; } } // proceed to next line continue; } var remainder = sr.ReadLine(); sbLine.Append(' '); sbLine.Append(remainder); line = sbLine.ToString(); if (string.IsNullOrWhiteSpace(line) || Header.ParseLineFromFile(line)) { continue; } Header.Comments.Add(line); } } hawkFile.GetStream().Position = origStreamPosn; return(true); }
} = new List <string>(); // TODO: Hack work around, to avoid having to refactor Assets into a object array, should be refactored! /// <exception cref="InvalidOperationException">internal error</exception> public static XmlGame Create(HawkFile f) { try { var x = new XmlDocument(); x.Load(f.GetStream()); var y = x.SelectSingleNode("./BizHawk-XMLGame"); if (y == null) { return(null); } var ret = new XmlGame { GI = { System = y.Attributes["System"].Value, Name = y.Attributes["Name"].Value, Status = RomStatus.Unknown }, Xml = x }; string fullPath = ""; var n = y.SelectSingleNode("./LoadAssets"); if (n != null) { var hashStream = new MemoryStream(); int?originalIndex = null; foreach (XmlNode a in n.ChildNodes) { string filename = a.Attributes["FileName"].Value; byte[] data; if (filename[0] == '|') { // in same archive var ai = f.FindArchiveMember(filename.Substring(1)); if (ai != null) { originalIndex ??= f.BoundIndex; f.Unbind(); f.BindArchiveMember(ai.Value); data = f.GetStream().ReadAllBytes(); } else { throw new Exception($"Couldn't load XMLGame Asset \"{filename}\""); } } else { // relative path fullPath = Path.GetDirectoryName(f.CanonicalFullPath.Split('|').First()) ?? ""; fullPath = Path.Combine(fullPath, filename.Split('|').First()); try { using var hf = new HawkFile(fullPath); if (hf.IsArchive) { var archiveItem = hf.ArchiveItems.First(ai => ai.Name == filename.Split('|').Skip(1).First()); hf.Unbind(); hf.BindArchiveMember(archiveItem); data = hf.GetStream().ReadAllBytes(); filename = filename.Split('|').Skip(1).First(); } else { data = File.ReadAllBytes(fullPath.Split('|').First()); } } catch { throw new Exception($"Couldn't load XMLGame LoadAsset \"{filename}\""); } } ret.Assets.Add(new KeyValuePair <string, byte[]>(filename, data)); ret.AssetFullPaths.Add(fullPath); var sha1 = SHA1Checksum.Compute(data); hashStream.Write(sha1, 0, sha1.Length); } ret.GI.Hash = SHA1Checksum.ComputeDigestHex(hashStream.GetBufferAsSpan()); hashStream.Close(); if (originalIndex != null) { f.Unbind(); f.BindArchiveMember((int)originalIndex); } } else { ret.GI.Hash = SHA1Checksum.Zero; } return(ret); } catch (Exception ex) { throw new InvalidOperationException(ex.ToString()); } }