Skip to content

Commit

Permalink
Add GetMemoryMappedStreamIfPossible
Browse files Browse the repository at this point in the history
Fixes #10
  • Loading branch information
xPaw committed Jan 7, 2024
1 parent 6a04a2e commit a6c4dd2
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 3 deletions.
38 changes: 38 additions & 0 deletions ValvePak/ValvePak.Test/MemoryMapTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using NUnit.Framework;
using SteamDatabase.ValvePak;

namespace Tests
{
[TestFixture]
public class MemoryMappedTest
{
[Test]
public void ReturnsMemoryMappedViewStream()
{
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "steamdb_test_dir.vpk");

using var package = new Package();
package.Read(path);

using var stream = package.GetMemoryMappedStreamIfPossible(package.FindEntry("kitten.jpg"));

Assert.That(stream, Is.InstanceOf<MemoryMappedViewStream>());
}

[Test]
public void ReturnsMemoryStream()
{
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "steamdb_test_single.vpk");

using var package = new Package();
package.Read(path);

using var stream = package.GetMemoryMappedStreamIfPossible(package.FindEntry("kitten.jpg"));

Assert.That(stream, Is.InstanceOf<MemoryStream>());
}
}
}
26 changes: 26 additions & 0 deletions ValvePak/ValvePak/Package.Read.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
using System.Text;

Expand Down Expand Up @@ -343,6 +344,31 @@ private Stream GetFileStream(ushort archiveIndex)
return stream;
}

/// <summary>
/// Returns <see cref="MemoryMappedViewStream"/> when possible, otherwise reads entry into a byte array and returns <see cref="MemoryStream"/>.
/// This only works on split packages (<see cref="IsDirVPK"/>) and when entries have no preload bytes.
/// </summary>
/// <param name="entry">Package entry.</param>
/// <returns>Stream for a given package entry contents.</returns>
public Stream GetMemoryMappedStreamIfPossible(PackageEntry entry)
{
if (!IsDirVPK || entry.ArchiveIndex == 0x7FFF || entry.SmallData.Length > 0)
{
ReadEntry(entry, out var output, false);

return new MemoryStream(output);
}

if (!MemoryMappedPaks.TryGetValue(entry.ArchiveIndex, out var stream))
{
var path = $"{FileName}_{entry.ArchiveIndex:D3}.vpk";
stream = MemoryMappedFile.CreateFromFile(path, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);
MemoryMappedPaks[entry.ArchiveIndex] = stream;
}

return stream.CreateViewStream(entry.Offset, entry.Length, MemoryMappedFileAccess.Read);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private string ReadNullTermUtf8String(MemoryStream ms)
{
Expand Down
18 changes: 15 additions & 3 deletions ValvePak/ValvePak/Package.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;

namespace SteamDatabase.ValvePak
{
Expand All @@ -17,6 +18,7 @@ public partial class Package : IDisposable
public const char DirectorySeparatorChar = '/';

private BinaryReader Reader;
private readonly Dictionary<int, MemoryMappedFile> MemoryMappedPaks = [];

/// <summary>
/// Gets the file name.
Expand Down Expand Up @@ -111,10 +113,20 @@ public void Dispose()

protected virtual void Dispose(bool disposing)
{
if (disposing && Reader != null)
if (disposing)
{
Reader.Dispose();
Reader = null;
if (Reader != null)
{
Reader.Dispose();
Reader = null;
}

foreach (var stream in MemoryMappedPaks.Values)
{
stream.Dispose();
}

MemoryMappedPaks.Clear();
}
}

Expand Down

0 comments on commit a6c4dd2

Please sign in to comment.