/// <summary> /// Extracts files from partitions, this is not random access. The iso is read in it's entirety /// </summary> public ExtractResult ExtractRecoveryFilesWii() { List <ExtractRecoveryResult> result = new List <ExtractRecoveryResult>(); List <WiiPartitionInfo> toExtract = new List <WiiPartitionInfo>(); WiiDiscHeaderSection hdr = null; WiiPartitionHeaderSection pHdr = null; NStream target = null; bool extracting = false; int channel = 1; string lastPartitionId = null; PartitionType lastPartitionType = PartitionType.Other; string fileName = null; string tmpFileName = null; int extracted = 0; Crc crc = new Crc(); Stream crcStream = null; long imageSize = this.NStream.RecoverySize; //for Wii: pHdr.PartitionDataLength bool isIso = false; //force to always scrub //Path.GetExtension(_name).ToLower() == ".iso"; foreach (IWiiDiscSection s in this.EnumerateSectionsFix(false, true, false)) { if (s is WiiDiscHeaderSection) { hdr = (WiiDiscHeaderSection)s; Log?.Log(string.Format("Processing: {0}", SourceFileName)); toExtract = hdr.Partitions.Where(a => a.Type != PartitionType.Data && a.Type != PartitionType.GameData).ToList(); if (toExtract.Count() == 0) { Log?.Log(string.Format(" Skipped: No Recovery Partitions to extract - {0}", SourceFileName)); break; } } else if (s is WiiPartitionSection) { WiiPartitionSection ps = (WiiPartitionSection)s; if (!toExtract.Any(a => a.DiscOffset == ps.DiscOffset)) { Log?.Log(string.Format(" Skipping {0} Partition: [{1}]...", ps.Header.Type.ToString(), SourceFiles.CleanseFileName(ps.Header.Id).PadRight(4))); continue; } extracting = true; pHdr = ps.Header; extracting = true; Log?.Log(string.Format(" {0} of {1} - Extracting {2} Recovery Partition: [{3}]", (extracted + 1).ToString(), toExtract.Count().ToString(), ps.Header.Type.ToString(), SourceFiles.CleanseFileName(ps.Header.Id).PadRight(4))); crcStream = new CryptoStream(ByteStream.Zeros, crc, CryptoStreamMode.Write); crc.Initialize(); WriteRecoveryPartitionData(crcStream, isIso, ps, channel, out tmpFileName, out fileName, out target); lastPartitionId = ps.Id; lastPartitionType = ps.Header.Type; } else if (s is WiiFillerSection) { JunkStream junk = new JunkStream(lastPartitionType != PartitionType.Data ? hdr.ReadString(0, 4) : lastPartitionId, hdr.Read8(6), lastPartitionType == PartitionType.Update ? 0 : imageSize); WiiFillerSection fs = (WiiFillerSection)s; if (extracting) { int storeType; if ((storeType = WriteRecoveryPartitionFiller(crcStream, junk, fs.DiscOffset, pHdr.Type == PartitionType.Update, false, fs, target, tmpFileName, ref fileName, crc, false)) != 0) { result.Add(new ExtractRecoveryResult() { FileName = fileName, Extracted = true, Type = PartitionType.Other, IsNew = storeType == 2, IsGameCube = false }); } if (pHdr.Type != PartitionType.Update) { channel++; } extracted++; extracting = false; bool complete = (extracted == toExtract.Count()); if (complete) { break; } } } } return(createExtractResult((Region)hdr.ReadUInt32B(0x4e000), result.ToArray())); }
public void Write(Context ctx, Stream inStream, Stream outStream, Coordinator pc) { try { WiiDiscHeaderSection hdr = null; WiiPartitionHeaderSection pHdr = null; string lastPartitionId = null; PartitionType lastPartitionType = PartitionType.Other; NCrc crc = new NCrc(); Crc updateCrc = new Crc(); bool updateRemoved = false; string updateTmpFileName = null; string updateFileName = null; bool extractingUpdate = false; CryptoStream updateCrcStream = null; NStream updateTarget = null; CryptoStream target = null; MemorySection removedUpdateFiller = null; int preservedHashCount = 0; NkitInfo nkitDiscInfo = new NkitInfo(); long fstFileAlignment = -1; WiiPartitionSection lastPart = null; long dstPos = 0; long imageSize = pc.OutputSize; //for Wii: pHdr.PartitionDataLength string ignoreJunkId; pc.WriterCheckPoint1WriteReady(out ignoreJunkId); //wait until read has written the header and set the length NDisc disc = new NDisc(_log, inStream); foreach (IWiiDiscSection s in disc.EnumerateSections(imageSize)) //no fixing, image should be good //ctx.ImageLength { if (s is WiiDiscHeaderSection) { hdr = (WiiDiscHeaderSection)s; hdr.Write8(0x60, 1); hdr.Write8(0x61, 1); fstFileAlignment = ctx?.Settings?.PreserveFstFileAlignment?.FirstOrDefault(a => a.Item1 == hdr.Id8)?.Item2 ?? -1; target = new CryptoStream(outStream, crc, CryptoStreamMode.Write); target.Write(hdr.Data, 0, hdr.Data.Length); //write the header nkitDiscInfo.BytesData += hdr.Size; dstPos += hdr.Size; crc.Snapshot("Disc Header"); } else if (s is WiiPartitionSection) { WiiPartitionSection ps = (WiiPartitionSection)s; WiiHashStore hashes = new WiiHashStore(ps.PartitionDataLength); ScrubManager scrub = ps.Header.ScrubManager; NkitInfo nkitPartInfo = new NkitInfo(); if (ps.Header.Type == PartitionType.Update && ctx.Settings.NkitUpdatePartitionRemoval && hdr.Partitions.Count() > 1) //only remove update if there's more partitions { updateRemoved = true; extractingUpdate = true; pHdr = ps.Header; extractingUpdate = true; updateCrcStream = new CryptoStream(ByteStream.Zeros, updateCrc, CryptoStreamMode.Write); crc.Initialize(); nkitPartInfo.BytesData += disc.WriteRecoveryPartitionData(updateCrcStream, false, ps, 0, out updateTmpFileName, out updateFileName, out updateTarget); _log?.LogDetail(string.Format("Extracted and Removed {0} Recovery Partition: [{1}]", ps.Header.Type.ToString(), SourceFiles.CleanseFileName(ps.Header.Id).PadRight(4))); removedUpdateFiller = new MemorySection(new byte[0x8000]); removedUpdateFiller.Write(0, hdr.Read(0x40000, 0x100)); //backup the original partition table in case it's non standard in some way } else { target.Write(ps.Header.Data, 0, (int)ps.Header.Size); nkitDiscInfo.BytesData += ps.Header.Size; crc.Snapshot(ps.Id + " Hdr"); crc.Crcs.Last().PatchData = ps.Header.Data; dstPos += ps.Header.Size; ps.NewDiscOffset = dstPos; //long written = 0; using (StreamCircularBuffer decrypted = new StreamCircularBuffer(ps.PartitionDataLength, null, null, output => { //read decrypted partition foreach (WiiPartitionGroupSection pg in ps.Sections) { nkitPartInfo.BytesHashesData += (pg.Size / 0x8000 * 0x400); //size of input hashes if (pg.PreserveHashes()) { nkitPartInfo.BytesHashesPreservation += hashes.Preserve(pg.Offset, pg.Decrypted, pg.Size); if (++preservedHashCount >= 1500) //too many will bomb the patch caching when converting back. Something is wrong { throw pc.SetWriterException(new HandledException("Over 1500 hashes preserved, aborting as image is corrupt or poorly scrubbed.")); } } #if !DECRYPT for (int i = 0; i < pg.Size / 0x8000; i++) { output.Write(pg.Decrypted, (i * 0x8000) + 0x400, 0x7c00); } #else output.Write(pg.Decrypted, 0, (int)pg.Size); #endif } })) { long len = partitionWrite(decrypted, crc, target, ps, ctx, pc, nkitPartInfo, scrub, hashes, fstFileAlignment); ps.NewPartitionDataLength = len; dstPos += len; lastPart = ps; } } NkitFormat.LogNkitInfo(nkitPartInfo, _log, ps.Id, false); lastPartitionId = ps.Id; lastPartitionType = ps.Header.Type; } else if (s is WiiFillerSection) { WiiFillerSection fs = (WiiFillerSection)s; ScrubManager scrub = new ScrubManager(null); JunkStream junk = new JunkStream(lastPartitionType != PartitionType.Data ? hdr.ReadString(0, 4) : lastPartitionId, hdr.Read8(6), lastPartitionType == PartitionType.Update ? 0 : imageSize); if (lastPartitionType == PartitionType.Update && updateRemoved) { //preserve the original partition table and update filename target.Write(removedUpdateFiller.Data, 0, (int)removedUpdateFiller.Size); //remove update partition by adding a 32k placeholder. Avoid having a non update partition at 0x50000 nkitDiscInfo.BytesPreservationDiscPadding += removedUpdateFiller.Size; nkitDiscInfo.BytesGaps += fs.Size; dstPos += removedUpdateFiller.Size; crc.Snapshot(string.Format("{0}Replacement Filler", string.IsNullOrEmpty(lastPartitionId) ? "" : (" " + SourceFiles.CleanseFileName(lastPartitionId).PadRight(4)))); if (extractingUpdate) { int storeType; if ((storeType = disc.WriteRecoveryPartitionFiller(updateCrcStream, junk, fs.DiscOffset, true, true, fs, updateTarget, updateTmpFileName, ref updateFileName, updateCrc, true)) != 0) { _log.LogDetail(string.Format("{0}Update recovery partition stored: {1}", storeType == 2 ? "Other " : "", updateFileName)); } extractingUpdate = false; } } else { if (fs.Size != 0) { using (StreamCircularBuffer filler = new StreamCircularBuffer(fs.Size, null, null, output => { foreach (WiiFillerSectionItem item in ((WiiFillerSection)s).Sections) { output.Write(item.Data, 0, (int)item.Size); } })) { Gap gap = new Gap(fs.Size, false); long srcPos = fs.DiscOffset; long gapLen = gap.Encode(filler, ref srcPos, lastPartitionType == PartitionType.Update ? fs.Size : 0x1cL, fs.Size, junk, scrub, target, _log); nkitDiscInfo.BytesPreservationData += gapLen; nkitDiscInfo.BytesGaps += fs.Size; dstPos += gapLen; if (lastPart != null) { lastPart.NewPartitionDataLength += gapLen; } } } //pad partition to 32k int gapLen2 = (int)(dstPos % 0x8000 == 0 ? 0 : 0x8000 - (dstPos % 0x8000)); ByteStream.Zeros.Copy(target, gapLen2); nkitDiscInfo.BytesPreservationDiscPadding += gapLen2; dstPos += gapLen2; if (lastPart != null) { lastPart.NewPartitionDataLength += gapLen2; } if (lastPart != null) { lastPart.Header.WriteUInt32B(0x2bc, (uint)(lastPart.NewPartitionDataLength / 4)); //updates the array in the crc data hdr.Partitions.First(a => a.DiscOffset == lastPart.DiscOffset).DiscOffset = (lastPart.NewDiscOffset - 0x20000); } crc.Snapshot(string.Format("{0}{1}Files+Filler", lastPartitionId ?? "", string.IsNullOrEmpty(lastPartitionId) ? "" : " ")); } } } NkitFormat.LogNkitInfo(nkitDiscInfo, _log, hdr.Id, true); foreach (CrcItem ci in crc.Crcs.Where(a => a.PatchData != null)) { ci.PatchCrc = Crc.Compute(ci.PatchData); } NCrc readerCrcs; uint validationCrc; pc.WriterCheckPoint2Complete(out readerCrcs, out validationCrc, hdr.Data, dstPos); //wait until reader has completed and get crc patches. if (updateRemoved && crc.Crcs.Length > 2) //freeloader wii only has update partition { hdr.RemoveUpdatePartition(crc.Crcs[2].Offset); } hdr.UpdateOffsets(); hdr.WriteString(0x200, 8, "NKIT v01"); //header and version hdr.Write8(0x60, 1); hdr.Write8(0x61, 1); hdr.WriteUInt32B(0x208, readerCrcs.FullCrc(true)); //original crc hdr.WriteUInt32B(0x210, (uint)(imageSize / 4L)); //ctx.ImageLength hdr.WriteUInt32B(0x218, updateCrc.Value); //Update crc - Only if removed crc.Crcs[0].PatchCrc = Crc.Compute(hdr.Data); crc.Crcs[0].PatchData = hdr.Data; hdr.WriteUInt32B(0x20C, CrcForce.Calculate(crc.FullCrc(true), dstPos, readerCrcs.FullCrc(true), 0x20C, 0)); //magic to force crc crc.Crcs[0].PatchCrc = Crc.Compute(hdr.Data); //update with magic applied pc.WriterCheckPoint3ApplyPatches(crc, false, crc.FullCrc(true), crc.FullCrc(true), this.VerifyIsWrite, "NKit Written"); } catch (Exception ex) { throw pc.SetWriterException(ex, "NkitWriterWii.Write - Convert"); } }
public void Read(Context ctx, NStream inStream, Stream outStream, Coordinator pc) { try { WiiDiscHeaderSection hdr = (WiiDiscHeaderSection)inStream.DiscHeader; string idVer = hdr.ReadString(0x200, 8); if (idVer != "NKIT v01") { throw new Exception(string.Format("{0} not supported by this version", idVer)); } bool isNkit = idVer.StartsWith("NKIT"); uint nkitCrc = hdr.ReadUInt32B(0x208); long imageSize = hdr.ReadUInt32B(0x210) * 4L; string junkId = hdr.ReadString(0x214, 4); uint updatePartitionCrc = hdr.ReadUInt32B(0x218); MemorySection updateRemovedFiller = null; long discOffset = 0; List <NkitPartitionPatchInfo> patchInfos = new List <NkitPartitionPatchInfo>(); discOffset += hdr.Size; string lastPartitionId = null; PartitionType lastPartitionType = PartitionType.Other; NCrc crc = new NCrc(); long dstPos = 0; long srcPos = hdr.Size; ScrubManager scrubFiller = new ScrubManager(null); bool isRecoverable = false; using (NDisc disc = new NDisc(_log, inStream)) { hdr.WriteUInt32B(0x200, 0); hdr.WriteUInt32B(0x204, 0); hdr.WriteUInt32B(0x208, 0); hdr.WriteUInt32B(0x20C, 0); hdr.WriteUInt32B(0x210, 0); hdr.WriteUInt32B(0x214, 0); hdr.WriteUInt32B(0x218, 0); hdr.Write8(0x60, 0); hdr.Write8(0x61, 0); CryptoStream crcStream = new CryptoStream(outStream, crc, CryptoStreamMode.Write); //wrap to calculate crc crcStream.Write(hdr.Data, 0, hdr.Data.Length); //write the header pc.ReaderCheckPoint1PreWrite(null, nkitCrc); //size that will be output from this read dstPos += hdr.Size; crc.Snapshot("Disc Header"); foreach (WiiPartitionInfo part in hdr.Partitions) //already sorted { if (updatePartitionCrc != 0 && srcPos == hdr.Size) //write update partition out { updateRemovedFiller = MemorySection.Read(inStream, hdr.Partitions.First().DiscOffset - srcPos); srcPos += updateRemovedFiller.Size; WiiPartitionInfo firstPart = WiiDiscHeaderSection.CreatePartitionInfos(updateRemovedFiller, 0)?.FirstOrDefault(a => a.Type != PartitionType.Update); string updateFileName = RecoveryData.GetUpdatePartition(ctx.Settings, updatePartitionCrc); if (updateFileName != null) { using (FileStream pf = File.OpenRead(updateFileName)) { pf.Copy(crcStream, pf.Length); dstPos += pf.Length; } } else { string msg = string.Format("!! Update partition *_{0} missing - Adding filler. It may be Recoverable", updatePartitionCrc.ToString("X8")); isRecoverable = true; _log?.LogDetail(msg); //throw pc.SetReaderException(new HandledException("Failed to convert: " + msg)); } ByteStream.Zeros.Copy(crcStream, firstPart.DiscOffset - dstPos); //fill full gap dstPos += firstPart.DiscOffset - dstPos; } NkitPartitionPatchInfo patchInfo = new NkitPartitionPatchInfo() { HashGroups = new Dictionary <long, MemorySection>() }; patchInfos.Add(patchInfo); if (part.DiscOffset > srcPos) { dstPos += writeFiller(ref srcPos, dstPos, dstPos + 0x1cL, inStream, crcStream, new JunkStream(lastPartitionType != PartitionType.Data ? hdr.ReadString(0, 4) : lastPartitionId, hdr.Read8(6), lastPartitionType == PartitionType.Update ? 0 : imageSize), scrubFiller); inStream.Copy(ByteStream.Zeros, part.DiscOffset - srcPos); //padded to 0x8000 srcPos += part.DiscOffset - srcPos; } part.DiscOffset = dstPos; //restore the original position patchInfo.DiscOffset = dstPos; patchInfo.PartitionHeader = MemorySection.Read(inStream, 0x20000); srcPos += patchInfo.PartitionHeader.Size; long size = patchInfo.PartitionHeader.ReadUInt32B(0x2bc) * 4L; LongRef origSize = new LongRef() { Value = 0 }; WiiPartitionGroupSection wp = null; WiiPartitionHeaderSection wh = new WiiPartitionHeaderSection(hdr, null, part.DiscOffset, patchInfo.PartitionHeader.Data, patchInfo.PartitionHeader.Data.Length); MemorySection ph = new MemorySection(new byte[0x8000 * 64]); long remaining = long.MaxValue;//set after first block read int groupIndex = 0; WiiHashStore hashes = new WiiHashStore(); patchInfo.ScrubManager = wh.ScrubManager; bool patchBlock = false; StreamCircularBuffer prtStream = null; try { using (prtStream = new StreamCircularBuffer(0, null, null, output => srcPos += partitionStreamWrite(origSize, inStream, output, size, ctx.Dats, patchInfo, hashes, pc))) { int gs = 0; int ge = 0; int i = 0; MemoryStream patchBlocks = null; while (remaining > 0) { int blocks = (int)Math.Min(64L, remaining / 0x7c00); for (int b = 0; b < blocks; b++) { prtStream.Read(ph.Data, (b * 0x8000) + 0x400, 0x7c00); //load aligned with no hashes if (remaining == long.MaxValue) //first loop { remaining = origSize.Value; if (ph.ReadString(0x400 + 0, 4) == "\0\0\0\0") { gs = -1; ge = -1; blocks = (int)Math.Min(64L, remaining / 0x7c00); lastPartitionId = ph.ReadString(0x400 + 0, 4); patchInfo.PartitionHeader.WriteUInt32B(0x2bc, (uint)(NStream.DataToHashedLen(origSize.Value) / 4)); //restore real size crcStream.Write(patchInfo.PartitionHeader.Data, 0, patchInfo.PartitionHeader.Data.Length); dstPos += patchInfo.PartitionHeader.Size; } else { gs = (int)((ph.ReadUInt32B(0x400 + 0x424) * 4L) / (0x7c00L * 64)); ge = (int)(((ph.ReadUInt32B(0x400 + 0x424) * 4L) + (ph.ReadUInt32B(0x400 + 0x428) * 4L)) / (0x7c00L * 64)); if ((int)((part.DiscOffset + (ph.ReadUInt32B(0x400 + 0x428) * 4L)) % (0x7c00L * 64)) == 0) { ge--; //don't load the next group if the data will end on the last byte } blocks = (int)Math.Min(64L, remaining / 0x7c00); lastPartitionId = ph.ReadString(0x400 + 0, 4); patchInfo.PartitionHeader.WriteUInt32B(0x2bc, ph.ReadUInt32B(0x400 + 0x210)); //restore real size crcStream.Write(patchInfo.PartitionHeader.Data, 0, patchInfo.PartitionHeader.Data.Length); dstPos += patchInfo.PartitionHeader.Size; ph.WriteUInt32B(0x400 + 0x200, 0); ph.WriteUInt32B(0x400 + 0x204, 0); ph.WriteUInt32B(0x400 + 0x208, 0); ph.WriteUInt32B(0x400 + 0x20C, 0); ph.WriteUInt32B(0x400 + 0x210, 0); ph.WriteUInt32B(0x400 + 0x214, 0); ph.WriteUInt32B(0x400 + 0x218, 0); } wp = new WiiPartitionGroupSection(hdr, wh, ph.Data, part.DiscOffset, blocks * 0x8000, false); wh.Initialise(wp, origSize.Value); } } if (blocks < 64) { Array.Clear(ph.Data, blocks * 0x8000, ph.Data.Length - (blocks * 0x8000)); //clear remaining blocks } wp.Populate(groupIndex, ph.Data, dstPos, blocks * 0x8000); int scrubbed = 0; for (int bi = 0; bi < blocks; bi++) { wp.MarkBlockDirty(bi); byte byt; if (patchInfo.ScrubManager.IsBlockScrubbedScanMode(wp.Offset + (bi * 0x8000), out byt)) { wp.SetScrubbed(bi, byt); scrubbed++; } } bool isFstBlocks = i >= gs && i <= ge; bool reqHashes = hashes.IsPreserved(wp.Offset); //test with 0 partition based offset repairBlocks(wp, scrubbed, blocks, false, isFstBlocks); //only test if the hashes aren't preserved (only preserved for scrubbed/customs) if (reqHashes) //store with disc based offset { patchInfo.HashGroups.Add(wp.Offset + part.DiscOffset + patchInfo.PartitionHeader.Size, new MemorySection((byte[])wp.Decrypted.Clone())); //fetch the stored hashed that couldn't be recreated } groupIndex++; bool inFstArea = i >= gs && i <= ge; if (!patchBlock && (gs == i || reqHashes)) { patchBlocks = new MemoryStream(); crc.Snapshot(lastPartitionId + " Data"); patchBlock = true; } else if (patchBlock && !inFstArea && !reqHashes) { crc.Snapshot(lastPartitionId + " Patch"); crc.Crcs.Last().PatchData = patchBlocks.ToArray(); patchBlocks.Dispose(); patchBlock = false; } #if DECRYPT outStream.Write(wp.Decrypted, 0, blocks * 0x8000); if (i >= gs && i <= ge) { fstBlocks.Write(wp.Decrypted, 0, blocks * 0x8000); } #else crcStream.Write(wp.Encrypted, 0, blocks * 0x8000); if (patchBlock) { patchBlocks.Write(wp.Encrypted, 0, blocks * 0x8000); } #endif remaining -= (blocks * 0x7c00); dstPos += (blocks * 0x8000); i++; } if (patchBlock) { crc.Snapshot(lastPartitionId + " Patch"); crc.Crcs.Last().PatchData = patchBlocks.ToArray(); patchBlocks.Dispose(); } } if (origSize.Value != prtStream.WriterPosition) { throw pc.SetReaderException(new HandledException("Partition read did not write the full amount to the circular buffer before completing")); } } catch (Exception ex) { if (prtStream?.WriterException != null) { throw pc.SetReaderException(prtStream.WriterException, "Failed reading filesystem"); } throw pc.SetReaderException(ex, "Failed converting the filesystem");; //writer exception } srcPos += hashes.ReadPatchData(part.DiscOffset + patchInfo.PartitionHeader.Size, patchInfo.HashGroups, inStream); //read hash patches lastPartitionType = part.Type; } if (srcPos < inStream.Length) { JunkStream partJunk = new JunkStream(lastPartitionType != PartitionType.Data ? hdr.ReadString(0, 4) : lastPartitionId, hdr.Read8(6), lastPartitionType == PartitionType.Update ? 0 : imageSize); dstPos += writeFiller(ref srcPos, dstPos, lastPartitionType == PartitionType.Update ? imageSize : dstPos + 0x1cL, inStream, crcStream, partJunk, scrubFiller); } } crc.Snapshot("End"); if (updatePartitionCrc != 0) { hdr.Write((int)WiiDiscHeaderSection.PartitionTableOffset, updateRemovedFiller.Data, 0, (int)WiiDiscHeaderSection.PartitionTableLength); //restore the exact partition table if update was removed } else { hdr.UpdateOffsets(); //just update the table with the new offsets } crc.Crcs[0].PatchData = hdr.Data; foreach (CrcItem ci in crc.Crcs.Where(a => a.PatchData != null)) { NkitPartitionPatchInfo patchInfo = patchInfos.FirstOrDefault(a => ci.Offset >= a.DiscOffset + a.PartitionHeader.Size && ci.Offset < a.DiscOffset + a.PartitionHeader.Size + a.Size); if (patchInfo != null) { patchGroups(patchInfo, hdr, ci.Offset, ci.PatchData); } ci.PatchCrc = Crc.Compute(ci.PatchData); } if (imageSize != dstPos) { throw pc.SetReaderException(new HandledException("Nkit image read output {0} bytes not the expected {1}!", dstPos.ToString(), imageSize.ToString())); } pc.ReaderCheckPoint2Complete(crc, isRecoverable, nkitCrc, crc.FullCrc(true), this.VerifyIsWrite, hdr.Data, nkitCrc == crc.FullCrc(true) ? "NKit Valid" : "NKit Invalid"); pc.ReaderCheckPoint3Complete(); } catch (Exception ex) { throw pc.SetReaderException(ex, "NkitReaderWii.Read - Read and convert"); } }