Tar-like archive in pure C# (.NET 2.0 compatible)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Text; | |
namespace SimplePatchToolCore | |
{ | |
public class SimpleArchive | |
{ | |
// Archive Structure | |
// 4 bytes: <NumberOfDirectoriesInt> | |
// for i = 0 to <NumberOfDirectoriesInt> | |
// 4 bytes: <LengthOfDirectoryNameInt> | |
// <LengthOfDirectoryNameInt> bytes: Directory name | |
// | |
// for each file | |
// 4 bytes: Index of the file's directory | |
// 4 bytes: <LengthOfFilenameInt> | |
// <LengthOfFilenameInt> bytes: Filename | |
// 8 bytes: <LengthOfFileContentsLong> | |
// <LengthOfFileContentsLong> bytes: File contents | |
public const int END_OF_STREAM = -13; | |
private FileStream fs; | |
private readonly List<string> directories; | |
private readonly bool littleEndian; | |
private readonly UTF8Encoding textEncoding; | |
private readonly byte[] intBytes; | |
private readonly byte[] longBytes; | |
private readonly byte[] fileBytes; | |
private byte[] stringBytes; | |
public SimpleArchive() | |
{ | |
directories = new List<string>( 128 ); | |
littleEndian = BitConverter.IsLittleEndian; | |
textEncoding = new UTF8Encoding(); | |
intBytes = new byte[4]; | |
longBytes = new byte[8]; | |
fileBytes = new byte[8 * 1024]; | |
stringBytes = new byte[512]; | |
} | |
public void Pack( string directory, string archivePath ) | |
{ | |
directories.Clear(); | |
if( directory[directory.Length - 1] != '/' && directory[directory.Length - 1] != '\\' ) | |
directory += Path.DirectorySeparatorChar; | |
using( fs = new FileStream( archivePath, FileMode.Create ) ) | |
{ | |
IndexDirectoriesRecursively( new DirectoryInfo( directory ), "" ); | |
WriteInt( directories.Count ); | |
for( int i = 0; i < directories.Count; i++ ) | |
WriteString( directories[i] ); | |
for( int i = 0; i < directories.Count; i++ ) | |
{ | |
DirectoryInfo directoryInfo = new DirectoryInfo( directory + directories[i].Replace( '/', Path.DirectorySeparatorChar ) ); | |
if( !directoryInfo.Exists ) // Shouldn't happen | |
continue; | |
PackFiles( directoryInfo, i ); | |
} | |
} | |
} | |
public void Unpack( string archivePath, string directory ) | |
{ | |
directories.Clear(); | |
if( directory[directory.Length - 1] != '/' && directory[directory.Length - 1] != '\\' ) | |
directory += Path.DirectorySeparatorChar; | |
using( fs = new FileStream( archivePath, FileMode.Open, FileAccess.Read ) ) | |
{ | |
int numberOfDirectories = ReadInt(); | |
for( int i = 0; i < numberOfDirectories; i++ ) | |
{ | |
string directoryName = ReadString().Replace( '/', Path.DirectorySeparatorChar ); | |
directories.Add( directoryName ); | |
Directory.CreateDirectory( directory + directoryName ); | |
} | |
UnpackFiles( directory ); | |
} | |
} | |
private void IndexDirectoriesRecursively( DirectoryInfo directory, string relativePath ) | |
{ | |
directories.Add( relativePath ); | |
DirectoryInfo[] subDirectories = directory.GetDirectories(); | |
for( int i = 0; i < subDirectories.Length; i++ ) | |
IndexDirectoriesRecursively( subDirectories[i], string.Concat( relativePath, "/", subDirectories[i].Name ) ); | |
} | |
private void PackFiles( DirectoryInfo directory, int directoryIndex ) | |
{ | |
FileInfo[] files = directory.GetFiles(); | |
for( int i = 0; i < files.Length; i++ ) | |
{ | |
FileInfo file = files[i]; | |
WriteInt( directoryIndex ); | |
WriteString( file.Name ); | |
WriteFileBytes( file ); | |
} | |
} | |
private void UnpackFiles( string directory ) | |
{ | |
while( true ) | |
{ | |
int directoryIndex = ReadInt(); | |
if( directoryIndex == END_OF_STREAM ) | |
break; | |
string filename = ReadString(); | |
ReadFileBytes( new FileInfo( string.Concat( directory, directories[directoryIndex], Path.DirectorySeparatorChar, filename ) ) ); | |
} | |
} | |
private void WriteInt( int value ) | |
{ | |
byte[] bytes = BitConverter.GetBytes( value ); | |
if( littleEndian ) | |
Array.Reverse( bytes ); | |
fs.Write( bytes, 0, 4 ); | |
} | |
private void WriteLong( long value ) | |
{ | |
byte[] bytes = BitConverter.GetBytes( value ); | |
if( littleEndian ) | |
Array.Reverse( bytes ); | |
fs.Write( bytes, 0, 8 ); | |
} | |
private void WriteString( string value ) | |
{ | |
byte[] bytes = textEncoding.GetBytes( value ); | |
WriteInt( bytes.Length ); | |
fs.Write( bytes, 0, bytes.Length ); | |
} | |
private void WriteFileBytes( FileInfo value ) | |
{ | |
WriteLong( value.Length ); | |
using( FileStream input = value.OpenRead() ) | |
{ | |
int bytesRead; | |
while( ( bytesRead = input.Read( fileBytes, 0, fileBytes.Length ) ) > 0 ) | |
{ | |
fs.Write( fileBytes, 0, bytesRead ); | |
} | |
} | |
} | |
private int ReadInt() | |
{ | |
if( fs.Read( intBytes, 0, 4 ) < 4 ) | |
return END_OF_STREAM; | |
if( littleEndian ) | |
Array.Reverse( intBytes ); | |
return BitConverter.ToInt32( intBytes, 0 ); | |
} | |
private long ReadLong() | |
{ | |
if( fs.Read( longBytes, 0, 8 ) < 8 ) | |
return END_OF_STREAM; | |
if( littleEndian ) | |
Array.Reverse( longBytes ); | |
return BitConverter.ToInt64( longBytes, 0 ); | |
} | |
private string ReadString() | |
{ | |
int length = ReadInt(); | |
if( stringBytes.Length < length ) | |
stringBytes = new byte[length]; | |
if( fs.Read( stringBytes, 0, length ) < length ) | |
return null; | |
return textEncoding.GetString( stringBytes, 0, length ); | |
} | |
private void ReadFileBytes( FileInfo file ) | |
{ | |
long length = ReadLong(); | |
using( FileStream output = file.Create() ) | |
{ | |
int bytesRead; | |
while( length > 0L && ( bytesRead = fs.Read( fileBytes, 0, length < fileBytes.Length ? (int) length : fileBytes.Length ) ) > 0 ) | |
{ | |
output.Write( fileBytes, 0, bytesRead ); | |
length -= bytesRead; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Compatible with
Usage
Notes