public bool Sign(byte[] XboxHDKey, bool skipContentHashing, out string errorMessage) { if (Equals(null, XboxHDKey) || XboxHDKey.Length != 0x10) { errorMessage = "Bad key"; return(false); } byte[] buffer, hash; //calculate content hashes if (!skipContentHashing) { object result; if (!GetSection(Types.SectionType.TableOfContents, out result)) { errorMessage = "Failed to parse section data"; return(false); } Types.Section section = Sections[1]; Types.TableOfContents fileTable = (Types.TableOfContents)result; //parsed toc data foreach (Types.TableEntry file in fileTable.entries) { if (!file.Exists(Path.GetDirectoryName(FilePath))) { errorMessage = string.Format("Content file not found: {0}", file.fileName); return(false); } uint hashStartOffset = file.fileHashOffset; uint hashLength = file.fileHashLength; uint size = (uint)new FileInfo(Path.Combine(Path.GetDirectoryName(FilePath), file.fileName)).Length; if (size != file.fileSize) { //errorMessage = string.Format("The size of the file \"{0}\" is invalid\n\nExpected size: {1} ({2} bytes)\nActual size: {3} ({4} bytes)", file.fileName, file.fileSize.RoundBytes(), file.fileSize, size.RoundBytes(), size); //return false; //instead of returning an error here I decided to correct the file size, in case of modders :) buffer = BitConverter.GetBytes(size); IO.Position = (((section.sectionOffset + fileTable.entryTableOffset) + file.entryOffset) + 0x8); IO.Write(buffer, 0, 4); //file size //set hash to cover entire file IO.Write(new byte[4], 0, 4); //hash start offset IO.Write(buffer, 0, 4); //hash length hashStartOffset = 0; hashLength = size; } buffer = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(FilePath), file.fileName)); hash = XCalcSig.CalculateDigest(buffer, (int)hashStartOffset, (int)hashLength); IO.Position = ((section.sectionOffset + 0xC) + (file.fileHashIndex * 0x14)); IO.Write(hash, 0, 0x14); } IO.Flush(); } //calculate section hashes foreach (Types.Section section in Sections) { if (!section.IsAllocated) { continue; } buffer = new byte[section.sectionLength]; IO.Position = section.sectionOffset; IO.Read(buffer, 0, (int)section.sectionLength); hash = XCalcSig.CalculateDigest(buffer); IO.Position = section.hashOffset; IO.Write(hash, 0, 0x14); } IO.Flush(); //calculate header "signature" buffer = new byte[(Header.headerSize - 0x14)]; IO.Position = 0x14; IO.Read(buffer, 0, (int)(Header.headerSize - 0x14)); hash = XCalcSig.CalculateNonRoamable(BitConverter.GetBytes(Header.titleId), XboxHDKey, buffer); IO.Position = 0; IO.Write(hash, 0, 0x14); IO.Flush(); buffer = null; hash = null; errorMessage = null; return(true); }
/// <summary> /// Parses and returns a section /// </summary> /// <param name="type">Section type to parse</param> /// <param name="result">Contains parsed section type. Must be casted to proper type</param> /// <returns>Whether parse was successful</returns> public bool GetSection(Types.SectionType type, out object result) { result = null; if (Header.headerType == Types.HeaderType.Content && type == Types.SectionType.Unknown) { return(false); } if (Header.headerType == Types.HeaderType.Update) { if (type == Types.SectionType.Language || type == Types.SectionType.Optional) { return(false); } } byte[] sectionData = null; //buffer to hold section data foreach (Types.Section section in Sections) { if (section.sectionType == type) { sectionData = section.sectionData; break; } } if (Equals(sectionData, null) || sectionData.Length == 0 && type != Types.SectionType.Optional) { return(false); } if (type == Types.SectionType.Optional || type == Types.SectionType.Unknown) { result = sectionData; return(true); } if (type == Types.SectionType.Language) { result = sectionData.ToUnicode().ToString(CultureInfo.InvariantCulture); return(true); } if (type == Types.SectionType.TableOfContents) { Types.TableOfContents toc = new Types.TableOfContents(); using (Stream stream = new MemoryStream(sectionData)) { byte[] buffer = new byte[0x4]; stream.Read(buffer, 0, 4); toc.entryCount = buffer.ToUInt32(false); stream.Read(buffer, 0, 4); toc.unknown_0 = buffer.ToUInt32(false); stream.Read(buffer, 0, 4); toc.entryTableOffset = buffer.ToUInt32(false); List <ushort> chain = new List <ushort>(); chain.Add(0); //always exists ushort a, b; while (chain.Count < toc.entryCount) //i doubt this is the proper way to parse the entry chain but this works { for (int i = 0; i < chain.Count; i++) { stream.Position = (toc.entryTableOffset + chain[i]); stream.Read(buffer, 0, 2); a = buffer.ToUInt16(false); //entry pointer a stream.Read(buffer, 0, 2); b = buffer.ToUInt16(false); //entry pointer b if (a != 0 && !chain.Contains(a)) { chain.Add(a); } if (b != 0 && !chain.Contains(b)) { chain.Add(b); } } } toc.entryChain = chain.ToArray(); List <Types.TableEntry> entries = new List <Types.TableEntry>(); Types.TableEntry entry; foreach (ushort offset in toc.entryChain) { stream.Position = ((toc.entryTableOffset + offset) + 0x4); entry = new Types.TableEntry(); entry.entryOffset = offset; Array.Resize <byte>(ref buffer, 4); stream.Read(buffer, 0, 2); entry.unk_0 = buffer.ToUInt16(false); stream.Read(buffer, 0, 2); entry.filenameLength = buffer.ToUInt16(false); stream.Read(buffer, 0, 4); entry.fileSize = buffer.ToUInt32(false); stream.Read(buffer, 0, 4); entry.fileHashOffset = buffer.ToUInt32(false); stream.Read(buffer, 0, 4); entry.fileHashLength = buffer.ToUInt32(false); stream.Read(buffer, 0, 2); entry.fileHashIndex = buffer.ToUInt16(false); Array.Resize <byte>(ref buffer, entry.filenameLength); stream.Read(buffer, 0, entry.filenameLength); entry.fileName = buffer.ToUTF8(); stream.Position = ((entry.fileHashIndex * 0x14) + 0xC); entry.fileHash = new byte[0x14]; stream.Read(entry.fileHash, 0, 0x14); entries.Add(entry); } toc.entries = entries.ToArray(); } result = toc; return(true); } return(false); }