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