Compressed/Encrypted Content Archive

From XNAWiki
Jump to: navigation, search

For the following to work, you will first need to download:

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);
        }
    }
}