Compressed/Encrypted Content Archive
From XNAWiki
For the following to work, you will first need to download:
- https://sites.google.com/a/nomad-net.info/dev/articles/sevenzipinterface/ - C# 7zip Interface
- http://www.7-zip.org/sdk.html - LZMA SDK
Configuration:
- Add from the SevenZip project gained from nomad's website: Ole32, SevenZipFormat.cs, and SevenZipInterface.cs.
- Add the 7za.dll file from the LZMA SDK to your project's root level, set it's properties for: Build Action = Content, Copy to Output = Copy if newer. I actually added to this the same project I put the SevenZip stuff in to keep it all together.
Required Callback Interfaces
- ArchiveOpenCallback - doesn't really do anything, just required for some of the formats 7zip supports.
class ArchiveOpenCallback : IArchiveOpenCallback { #region IArchiveOpenCallback Members public void SetTotal(IntPtr files, IntPtr bytes) { } public void SetCompleted(IntPtr files, IntPtr bytes) { } #endregion }
- ArchiveExtractCallback - called into when a file is being extracted, if you do not use encrypted archives then remove the ICrytoGetTextPassword stuff.
class ArchiveExtractCallback : IProgress, IArchiveExtractCallback, ICryptoGetTextPassword { public uint FileIndex; public string FileName; public OutStreamWrapper Stream; public AutoResetEvent ReadEvent; public ArchiveExtractCallback(uint fileIndex, string fileName) { FileIndex = fileIndex; FileName = fileName; ReadEvent = new AutoResetEvent(false); } #region IArchiveExtractCallback Members public void SetTotal(ulong total) { } public void SetCompleted(ref ulong completeValue) { } public int GetStream(uint index, out ISequentialOutStream outStream, AskMode askExtractMode) { if (index == FileIndex && askExtractMode == AskMode.kExtract) { Stream = new OutStreamWrapper(new MemoryStream()); outStream = Stream; } else outStream = null; return 0; } public void PrepareOperation(AskMode askExtractMode) { } public void SetOperationResult(OperationResult resultEOperationResult) { ReadEvent.Set(); // let's the game thread know it's safe to continue } #endregion #region ICryptoGetTextPassword Members public int CryptoGetTextPassword(out string password) { password = "secret"; // this is where it's asking for the password return 0; } #endregion }
PackedContentManager
- This should be used wherever your loading content from a compressed/encrypted archive.
- The compressed/encrypted archive should have all of the COMPILED content inside of it.
- It is not thread safe at all, the Load calls should be coming from the same thread that created the content project.
- The decompression/decryption however does happen on it's own thread, so a reset event is used to keep track of that.
- The OpenStream method will fall back on the content manager's default functionality if the file requested is not inside the archive.
class PackedContentManager : ContentManager { static string SevenZipDllPath { get { return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "7za.dll"); } } SevenZipFormat m_format; InStreamWrapper m_inStream; IInArchive m_archive; IArchiveOpenCallback m_callback; ulong m_checkPos; Hashtable m_hash; // used for file-index lookups public PackedContentManager(IServiceProvider serviceProvider, string archivePath, KnownSevenZipFormat archiveFormat) : base(serviceProvider) { m_format = new SevenZipFormat(SevenZipDllPath); m_archive = m_format.CreateInArchive(SevenZipFormat.GetClassIdFromKnownFormat(archiveFormat)); m_inStream = new InStreamWrapper(File.OpenRead(archivePath)); m_callback = new ArchiveOpenCallback(); m_checkPos = 128 * 1024; m_archive.Open(m_inStream, ref m_checkPos, m_callback); m_hash = new Hashtable(); uint count = m_archive.GetNumberOfItems(); for (uint i = 0; i < count; i++) { PropVariant name = new PropVariant(); m_archive.GetProperty(i, ItemPropId.kpidPath, ref name); string strName = (name.GetObject() as string).ToLower(); int xnbIndex = strName.IndexOf(".xnb"); strName = strName.Remove(xnbIndex, 4); m_hash.Add(strName, i); } } uint[] m_extractIndices = new uint[1]; protected override Stream OpenStream(string assetName) { string nameLower = assetName.ToLower(); if (m_hash.ContainsKey(nameLower)) { uint index = (uint)m_hash[nameLower]; m_extractIndices[0] = index; ArchiveExtractCallback extractCallback = new ArchiveExtractCallback(index, assetName); m_archive.Extract(m_extractIndices, 1, 0, extractCallback); extractCallback.ReadEvent.WaitOne(); // wait for decompress/decryption // reset stream position for the content reader extractCallback.Stream.BaseStream.Position = 0L; return extractCallback.Stream.BaseStream; } return base.OpenStream(assetName); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { Marshal.ReleaseComObject(m_archive); } } }