Skip to content

Instantly share code, notes, and snippets.

@ForNeVeR
Forked from Slaynash/Program.cs
Created August 25, 2026 17:48
Show Gist options
  • Select an option

  • Save ForNeVeR/cb099dea7db6088d2d5d4d4fd508c3e8 to your computer and use it in GitHub Desktop.

Select an option

Save ForNeVeR/cb099dea7db6088d2d5d4d4fd508c3e8 to your computer and use it in GitHub Desktop.
NSISBI Extractor (Unity Installer Extractor)

NSISBI extractor

This is a code draft, and isn't meant to be used in production!

This tool is capable of extracting NSISBI (Nullsoft Scriptable Installer System Big Installer) executables

Requirements

  • LZMA SDK 19.00
  • .net framework 4.8
  • Unsafe code permission (in Visual Studio)

Usage

UnityNSISReader.exe -f -o [-r]

  • r: Filter the results using a regex

Example: UnityNSISReader.exe "-fUnitySetup-Windows-IL2CPP-Support-for-Editor-2020.3.19f1.exe" "-oil2cpp_2020.3.19f1" "-rVariations/.*_il2cpp/UnityPlayer.*(dll|pdb)"

Credits

  • 7-zip: Parts of the source code, licenced under GNU LGPL and/or BSD 3-clause
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
namespace UnityNSISReader
{
class Program
{
private const uint FhFlagsMask = 255;
private const uint FhSig = 0xDEADBEEF;
private const uint FhInt1 = 0x6C6C754E, FhInt2 = 0x74666F73, FhInt3 = 0x74736E49;
private static byte[] Utf8Limits = new[] { (byte)0xC0, (byte)0xE0, (byte)0xF0, (byte)0xF8, (byte)0xFC };
private const uint SignatureSize = 16;
private const uint StartHeaderSize = 28;
private const uint MaskIsCompressed = unchecked ((uint)(1 << 31));
// nsisbi stores block sizes as an INT64, so the "is compressed" flag moves to bit 63
// (FIRST_INT_FLAG in Source/exehead/fileform.h)
private const long MaskIsCompressed64 = unchecked ((long)(1UL << 63));
// Framing of the multithreaded compressors, from Source/mtw/mt_lzma_common.h
private const int MtBlockHeaderSize = 3;
private const int MtBlockDataSize = 1 << 22;
private const uint PageSize = 16 * 4;
private const uint NumCommandParams = 6;
private const uint CommandSize = 4 + NumCommandParams * 4;
// MAX_ENTRY_OFFSETS in Source/exehead/fileform.h
private const uint NumCommandParamsBI = 8;
private static string sourceFile;
private static string outDir;
private static string filter;
private static long m_length;
private static long m_pos;
private static long g_filehdrsize = -1;
private static bool IsUnicode;
private static uint NumStringChars;
private static unsafe byte* pData;
private static unsafe BlockHeader* bhPages;
private static unsafe BlockHeader* bhSections;
private static unsafe BlockHeader* bhEntries;
private static unsafe BlockHeader* bhStrings;
private static unsafe BlockHeader* bhLangTables;
private static unsafe BlockHeader* bhCtlColors;
private static unsafe BlockHeader* bhFonts;
private static unsafe BlockHeader* bhDatas;
// The source file is read through a handle rather than a byte[]: installers are routinely
// larger than Array.MaxLength, and a block is only ever needed one chunk at a time.
private static Microsoft.Win32.SafeHandles.SafeFileHandle input;
private static int firstIntSize = 4;
private static bool mtFramed;
private static readonly SevenZip.Compression.LZMA.Decoder lzmaDecoder = new SevenZip.Compression.LZMA.Decoder();
private static readonly byte[] lzmaProps = new byte[5];
private static byte[] chunkBuffer = new byte[MtBlockDataSize + MtBlockDataSize / 10 + 1024];
private static readonly byte[] copyBuffer = new byte[1 << 20];
private static readonly string[] VarStrings = new[]
{
"CMDLINE",
"INSTDIR",
"OUTDIR",
"EXEDIR",
"LANGUAGE",
"TEMP",
"PLUGINSDIR",
"EXEPATH", // NSIS 2.26+
"EXEFILE", // NSIS 2.26+
"HWNDPARENT",
"_CLICK", // is set from page->clicknext
"_OUTDIR" // NSIS 2.04+
};
private static readonly int NumInternalVars = 20 + VarStrings.Length;
private static readonly string[] PageTypes = new[]
{
"license",
"components",
"directory",
"instfiles",
"uninstConfirm",
"COMPLETED",
"custom"
};
static int Main(string[] args)
{
foreach (string arg in args)
{
if (arg.StartsWith("-f"))
{
sourceFile = arg.Substring(2);
if (!File.Exists(sourceFile))
throw new FileNotFoundException("Failed to find the file " + sourceFile);
}
else if (arg.StartsWith("-o"))
{
outDir = arg.Substring(2);
Directory.CreateDirectory(outDir);
}
else if (arg.StartsWith("-r"))
{
filter = arg.Substring(2);
}
}
if (string.IsNullOrEmpty(sourceFile) || string.IsNullOrEmpty(outDir))
{
Console.WriteLine("Usage: UnityNSISReader.exe -f<FileName> -o<OutputPath> [-r<regex>]\n\tr: Filter the results using a regex");
return -1;
}
Console.WriteLine("filter: " + filter);
// One line per extracted file is a lot of tiny unbuffered writes when the installer
// holds 30k+ of them.
StreamWriter stdout = new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding, 1 << 16);
stdout.AutoFlush = false;
Console.SetOut(stdout);
using (input = File.OpenHandle(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.RandomAccess))
{
try
{
return Unpack();
}
finally
{
stdout.Flush();
}
}
}
private static unsafe int Unpack()
{
m_length = RandomAccess.GetLength(input);
Console.WriteLine("Checking header");
byte[] scan = new byte[1 << 20];
fixed (byte* s = scan)
{
while (m_pos < m_length && g_filehdrsize < 0)
{
int got = ReadAtMost(m_pos, scan, 0, (int)Math.Min(scan.Length, m_length - m_pos));
if (got <= 0)
break;
for (int off = 0; off + StartHeaderSize <= got; off += 512)
{
FirstHeader* h = (FirstHeader*)(s + off);
if (
(h->flags & ~FhFlagsMask) == 0 &&
h->siginfo == FhSig &&
h->nsinst2 == FhInt3 &&
h->nsinst1 == FhInt2 &&
h->nsinst0 == FhInt1
) {
g_filehdrsize = m_pos + off;
Console.WriteLine($"Found nsis data at {g_filehdrsize:X}");
break;
}
}
m_pos += got;
}
}
if (g_filehdrsize < 0)
{
Console.Error.WriteLine("Failed to locate NSIS data in the target file");
return -1;
}
byte[] fhBuffer = new byte[64];
if (ReadAtMost(g_filehdrsize, fhBuffer, 0, (int)Math.Min(fhBuffer.Length, m_length - g_filehdrsize)) < sizeof(FirstHeader))
{
Console.Error.WriteLine("The file ends inside the NSIS first header");
return -1;
}
fixed (byte* fh = fhBuffer)
{
FirstHeader* h = (FirstHeader*)fh;
Console.WriteLine("Installer: " + ((h->flags & (uint)NFlags.kUninstall) == 0));
// FH_FLAGS_NSISBI_INSTALL is bit 16 on its own, bit 32 is FH_FLAGS_HAS_EXTERNAL_FILE
bool isNSISBI = (h->flags & (uint)NFlags.kNsisBiInstall) != 0;
Console.WriteLine("NSISBI: " + isNSISBI);
if ((h->flags & (uint)NFlags.kHasExternalFile) != 0)
{
Console.Error.WriteLine("The data block lives in an external file, which is not supported");
return -1;
}
uint headerSize = (uint)h->length_of_header;
uint arcSize = (uint)h->length_of_all_following_data;
Console.WriteLine("Header Size: " + headerSize);
Console.WriteLine("Arc Size: " + arcSize);
if (arcSize <= StartHeaderSize)
{
Console.Error.WriteLine("Arc size is less than StartHeaderSize. This should not happen !");
return -1;
}
// nsisbi appends datablock_lowpart/highpart to the first header, so the data block
// starts 8 bytes further along and every block size becomes an INT64.
firstIntSize = isNSISBI ? 8 : 4;
mtFramed = isNSISBI;
long dataOffset = g_filehdrsize + (isNSISBI ? sizeof(FirstHeader) : (int)StartHeaderSize);
byte[] sigBuffer = new byte[64];
if (ReadAtMost(dataOffset, sigBuffer, 0, (int)Math.Min(sigBuffer.Length, m_length - dataOffset)) < firstIntSize + 8)
{
Console.Error.WriteLine("The file ends inside the first data block");
return -1;
}
bool headerIsCompressed;
long headerBlockSize = ReadBlockSize(sigBuffer, 0, out headerIsCompressed);
uint dictionarySize = 1;
fixed (byte* sig = sigBuffer)
{
// A non-solid archive prefixes every block with its own size; a solid one runs
// straight into a single stream, where that size field would be meaningless.
if (!headerIsCompressed && headerBlockSize != headerSize)
{
if (IsLZMA(sig, ref dictionarySize))
Console.Error.WriteLine("Header is LZMA compressed (Solid)");
else if (IsBZip2(sig))
Console.Error.WriteLine("Header is BZip2 compressed (Solid)");
else
Console.Error.WriteLine("Header is Deflate compressed (Solid)");
Console.Error.WriteLine("Solid archives are not supported");
return -1;
}
if (!headerIsCompressed)
Console.WriteLine("Header is NOT compressed");
else if (mtFramed)
Console.WriteLine("Header is LZMA compressed (Non-Solid, chunked)");
else if (IsLZMA(sig + firstIntSize, ref dictionarySize))
Console.WriteLine("Header is LZMA compressed (Non-Solid)");
else
{
Console.Error.WriteLine(IsBZip2(sig + firstIntSize)
? "Header is BZip2 compressed (Non-Solid), which is not supported"
: "Header is Deflate compressed (Non-Solid), which is not supported");
return -1;
}
}
Console.WriteLine("Compressed Header Size: " + headerBlockSize.ToString("X4"));
byte[] _data;
using (MemoryStream headerStream = new MemoryStream((int)headerSize))
{
ExtractBlock(dataOffset, headerStream);
if (headerStream.Length != headerSize)
{
Console.Error.WriteLine($"Decompressed header is {headerStream.Length} bytes, expected {headerSize}");
return -1;
}
_data = headerStream.ToArray();
}
// File offsets in the entries are relative to the end of the header block.
long filesDataOffset = dataOffset + firstIntSize + headerBlockSize;
fixed (byte* p = _data)
{
pData = p;
bhPages = (BlockHeader*)(p + 4 + 8 * 0);
bhSections = (BlockHeader*)(p + 4 + 8 * 1);
bhEntries = (BlockHeader*)(p + 4 + 8 * 2);
bhStrings = (BlockHeader*)(p + 4 + 8 * 3);
bhLangTables = (BlockHeader*)(p + 4 + 8 * 4);
bhCtlColors = (BlockHeader*)(p + 4 + 8 * 5);
bhFonts = (BlockHeader*)(p + 4 + 8 * 6);
bhDatas = (BlockHeader*)(p + 4 + 8 * 7);
Console.WriteLine("Entries: " + bhEntries->num + " starting at " + bhEntries->offset + " (Valid: " + (bhEntries->offset < headerSize) + ")");
Console.WriteLine("Strings: " + bhStrings->num + " starting at " + bhStrings->offset + " (Valid: " + (bhStrings->offset < headerSize) + ")");
Console.WriteLine("LangTables: " + bhLangTables->num + " starting at " + bhLangTables->offset + " (Valid: " + (bhLangTables->offset < headerSize) + ")");
if (
bhEntries->offset > headerSize ||
bhStrings->offset > headerSize ||
bhLangTables->offset > headerSize
)
{
Console.Error.WriteLine("BlockHeader offsets are out of bounds!");
return -1;
}
if (bhLangTables->offset < bhStrings->offset)
{
Console.Error.WriteLine("bhLangTables is before bhStrings");
return -1;
}
uint stringTableSize = bhLangTables->offset - bhStrings->offset;
Console.WriteLine("stringTableSize: " + stringTableSize);
if (stringTableSize < 2)
{
Console.Error.WriteLine("stringTableSize is less than 2 (" + stringTableSize + ")");
return -1;
}
byte* strData = p + bhStrings->offset;
if (strData[stringTableSize - 1] != 0)
{
Console.Error.WriteLine("byte at stringTableSize-1 is not 0");
return -1;
}
IsUnicode = *(short*)strData == 0;
Console.WriteLine("Unicode: " + IsUnicode + " (" + *(short*)strData + ")");
NumStringChars = stringTableSize;
if (IsUnicode)
{
if ((stringTableSize & 1) != 0)
{
Console.Error.WriteLine("(stringTableSize & 1) is not 0");
return -1;
}
NumStringChars >>= 1;
if ((strData[stringTableSize - 2]) != 0)
{
Console.Error.WriteLine("(strData[stringTableSize - 2]) is not 0");
return -1;
}
}
Console.WriteLine("NumStringChars: " + NumStringChars);
uint numParams = isNSISBI ? NumCommandParamsBI : NumCommandParams;
uint commandSize = 4 + numParams * 4;
if (bhEntries->num > (1 << 25))
{
Console.Error.WriteLine("bhEntries is too big (1)");
return -1;
}
if (bhEntries->num * commandSize > headerSize - bhEntries->offset)
{
Console.Error.WriteLine("bhEntries is too big (2)");
return -1;
}
Console.WriteLine("Done checking header");
Console.WriteLine();
Console.WriteLine();
// Commands
byte* cmdPtr = p + bhEntries->offset;
string currentOutPath = "";
HashSet<string> files = new HashSet<string>();
long extracted = 0, written = 0;
uint[] _params = new uint[numParams];
for (int kkk = 0; kkk < bhEntries->num; kkk++, cmdPtr += commandSize)
{
uint commandId = *(uint*)cmdPtr; // TODO Handle commands shifting depending on the NSIS version
for (int i = 0; i < numParams; ++i)
_params[i] = *(uint*)(cmdPtr + 4 + 4 * i);
switch (commandId)
{
case 11:
// EW_CREATEDIR only moves the output directory when parm1 is set,
// otherwise it is a plain mkdir and must leave it alone.
bool isSetOutPath = (_params[1] != 0);
if (!isSetOutPath)
continue;
string outPath = ReadString2(_params[0]).Replace("$_OUTDIR\\", "").Replace("$_OUTDIR", "").Replace("$INSTDIR\\", "");
if (outPath == "$_OUTDIR" || outPath == "$INSTDIR")
outPath = "";
if (outPath.EndsWith("/"))
outPath = outPath.Substring(0, outPath.Length - 1);
currentOutPath = outPath;
continue;
case 20:
string fileName = ReadString2(_params[1]);
// nsisbi splits the data block offset over parm2/parm3 (exec.c:
// offset.LowPart = parm2, offset.HighPart = parm3)
long position = isNSISBI ? (_params[2] | ((long)_params[3] << 32)) : _params[2];
string filePathLocal = Path.Combine(currentOutPath, fileName).Replace('\\', '/');
if (!files.Add(filePathLocal))
continue;
if (!string.IsNullOrEmpty(filter) && !Regex.IsMatch(filePathLocal, filter))
continue;
Console.WriteLine(filePathLocal);
string filePath = Path.Combine(outDir, filePathLocal);
Directory.CreateDirectory(Directory.GetParent(filePath).FullName);
using (FileStream outFile = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 1 << 20))
written += ExtractBlock(filesDataOffset + position, outFile);
extracted++;
continue;
}
}
Console.WriteLine();
Console.WriteLine($"Extracted {extracted} of {files.Count} files, {written} bytes");
}
}
Console.WriteLine();
return 0;
}
private static int ReadAtMost(long offset, byte[] buffer, int start, int count)
{
int done = 0;
while (done < count)
{
int read = RandomAccess.Read(input, buffer.AsSpan(start + done, count - done), offset + done);
if (read <= 0)
break;
done += read;
}
return done;
}
private static void ReadExactly(long offset, byte[] buffer, int start, int count)
{
if (ReadAtMost(offset, buffer, start, count) != count)
throw new EndOfStreamException($"Wanted {count} bytes at {offset:X} but the file ends first");
}
private static long ReadBlockSize(byte[] buffer, int start, out bool compressed)
{
if (firstIntSize == 8)
{
long size = MemoryMarshal.Read<long>(buffer.AsSpan(start, 8));
compressed = (size & MaskIsCompressed64) != 0;
return size & ~MaskIsCompressed64;
}
uint size32 = MemoryMarshal.Read<uint>(buffer.AsSpan(start, 4));
compressed = (size32 & MaskIsCompressed) != 0;
return size32 & ~MaskIsCompressed;
}
/// Writes the payload of the block at blockOffset to dst, and returns its length.
private static long ExtractBlock(long blockOffset, Stream dst)
{
byte[] sizeBuffer = new byte[8];
ReadExactly(blockOffset, sizeBuffer, 0, firstIntSize);
bool compressed;
long size = ReadBlockSize(sizeBuffer, 0, out compressed);
long offset = blockOffset + firstIntSize;
if (!compressed)
return CopyRaw(offset, size, dst);
if (mtFramed)
return DecodeChunkedLzma(offset, size, dst);
return DecodeLzma(offset, size, dst, -1);
}
private static long CopyRaw(long offset, long size, Stream dst)
{
long left = size;
while (left > 0)
{
int count = (int)Math.Min(left, copyBuffer.Length);
ReadExactly(offset, copyBuffer, 0, count);
dst.Write(copyBuffer, 0, count);
offset += count;
left -= count;
}
return size;
}
/// The multithreaded compressors cut a payload into independent LZMA streams, each preceded
/// by a 3 byte length and holding at most MT_LZMA_BLOCK_DATA_SIZE of output. A zero length
/// ends the chain.
private static long DecodeChunkedLzma(long offset, long size, Stream dst)
{
long end = offset + size;
long written = 0;
byte[] lengthBuffer = new byte[MtBlockHeaderSize];
while (offset + MtBlockHeaderSize <= end)
{
ReadExactly(offset, lengthBuffer, 0, MtBlockHeaderSize);
offset += MtBlockHeaderSize;
int chunkSize = lengthBuffer[0] | (lengthBuffer[1] << 8) | (lengthBuffer[2] << 16);
if (chunkSize == 0)
break;
if (offset + chunkSize > end)
throw new InvalidDataException($"Chunk at {offset:X} is {chunkSize} bytes, which overruns its block");
if (chunkSize > chunkBuffer.Length)
chunkBuffer = new byte[chunkSize];
ReadExactly(offset, chunkBuffer, 0, chunkSize);
offset += chunkSize;
written += DecodeLzmaChunk(chunkBuffer, chunkSize, dst, MtBlockDataSize);
}
return written;
}
private static long DecodeLzma(long offset, long size, Stream dst, long outSize)
{
if (size > int.MaxValue)
throw new InvalidDataException($"Unchunked LZMA stream at {offset:X} is too big ({size} bytes)");
if (size > chunkBuffer.Length)
chunkBuffer = new byte[size];
ReadExactly(offset, chunkBuffer, 0, (int)size);
return DecodeLzmaChunk(chunkBuffer, (int)size, dst, outSize);
}
/// Every stream carries its own properties and end of stream marker, so a single decoder
/// can be reused for all of them instead of reallocating a dictionary per file.
private static long DecodeLzmaChunk(byte[] data, int size, Stream dst, long outSize)
{
if (size <= lzmaProps.Length)
throw new InvalidDataException($"LZMA stream of {size} bytes is too short to hold its properties");
Array.Copy(data, lzmaProps, lzmaProps.Length);
lzmaDecoder.SetDecoderProperties(lzmaProps);
long before = dst.Position;
using (MemoryStream src = new MemoryStream(data, lzmaProps.Length, size - lzmaProps.Length, false))
lzmaDecoder.Code(src, dst, size - lzmaProps.Length, outSize, null);
long written = dst.Position - before;
if (outSize >= 0 && written > outSize)
throw new InvalidDataException($"LZMA stream produced {written} bytes, more than the {outSize} it should");
return written;
}
private static unsafe string ReadString2(uint pos)
{
if (pos < 0)
return $"$(LSTR_{-(pos + 1)})";
else if (pos >= NumStringChars)
return "$_ERROR_STR_" + pos;
else
{
if (IsUnicode)
return GetNsisStringUnicode(pData + bhStrings->offset + pos * 2);
else
return GetNsisString(pData + bhStrings->offset + pos);
}
}
private static string ReadString2_Raw(uint pos)
{
if (pos < 0)
return $"$(LSTR_{-(pos + 1)})";
else if (pos >= NumStringChars)
return "$_ERROR_STR_" + pos;
else
{
return $"S_{pos}";
}
}
private static unsafe string GetNsisStringUnicode(byte* p)
{
StringBuilder sb = new StringBuilder();
for (;;)
{
ushort c = *(ushort*)p;
p += 2;
//Console.Write($"[C{c:X4}]");
if (c == 0)
break;
if (false /*IsPark()*/)
{
if (c >= 0xE000 && c <= 0xE003)
{
ushort n = *(ushort*)p;
p += 2;
if (n == 0)
break;
if (c != 0xE000)
{
if (c == 0xE002)
sb.Append($"$(SSTR_{n & 0xFF}_{n >> 8})");
else
{
n &= 0x7FFF;
if (c == 0xE001)
sb.Append(GetVar(n));
else
sb.Append($"$(LSTR_{n})");
}
continue;
}
c = n;
}
}
else
{
if (c <= 4)
{
ushort n = *(ushort*)p;
//Console.Write($"[N{n:X4}]");
p += 2;
if (n == 0)
break;
if (c != 4)
{
if (c == 2)
sb.Append($"$(SSTR_{n & 0xFF}_{n >> 8})");
else
{
n = (ushort)((n & 0x7F) | (((ushort)(n >> 8) & 0x7F) << 7));
if (c == 3)
sb.Append(GetVar(n));
else
sb.Append($"$(LSTR_{n})");
}
continue;
}
c = n;
}
}
if (c < 0x80)
{
if (c == 9) sb.Append("$\\t");
else if (c == 10) sb.Append("$\\n");
else if (c == 13) sb.Append("$\\r");
else if (c == '"') sb.Append("$\\\"");
else if (c == '$') sb.Append("$$");
else sb.Append((char)c, 1);
//Console.Write($"[A:{(char)c}]");
continue;
}
short numAdds;
for (numAdds = 1; numAdds < 5; ++numAdds)
if (c < (1 << (numAdds * 5 + 6)))
{
//Console.Write($"[NAB]");
break;
}
sb.Append((char)(Utf8Limits[numAdds - 1] + (c >> (6 * numAdds))));
do
{
numAdds--;
sb.Append((char)(0x80 + ((c >> (6 * numAdds)) & 0x3F)));
}
while (numAdds != 0);
}
return sb.ToString();
}
private static unsafe string GetNsisString(byte* s)
{
string tmp = "";
for (;;)
{
byte c = *s++;
if (c == 0)
return tmp;
if (c <= 4)
{
byte c0 = *s++;
if (c0 == 0)
return tmp;
if (c != 4)
{
byte c1 = *s++;
if (c1 == 0)
return tmp;
if (c1 == 4)
tmp += $"$(SSTR_{c0}_{c1})";
else
{
uint n = (uint)((ushort)(c0 & 0x7F) | ((ushort)(c1 & 0x7F) << 7));
if (c == 3)
tmp += GetVar(n);
else
tmp += $"$(LSTR_{n})";
}
continue;
}
}
}
}
private static string GetVar(uint index)
{
return "$" + GetVar2(index);
}
private static string GetVar2(uint index)
{
string tmp = "";
if (index < 20)
{
if (index >= 10)
{
tmp += "R";
index -= 10;
}
tmp += index;
}
else
{
if (index < NumInternalVars)
{
tmp += VarStrings[index - 20];
}
else
{
tmp += $"_{index - NumInternalVars}_";
}
}
return tmp;
}
private static int GetVarIndex(uint strPos, ref uint resOffset)
{
resOffset = 0;
int varIndex = GetVarIndex(strPos);
if (varIndex < 0)
return varIndex;
if (IsUnicode)
{
if (NumStringChars - strPos < 2 * 2)
return -1;
resOffset = 2;
}
else
{
if (NumStringChars - strPos < 3)
return -1;
resOffset = 3;
}
return varIndex;
}
private static int GetVarIndex(uint strPos)
{
/*
if (strPos >= NumStringChars)
return -1;
if (IsUnicode)
{
if (NumStringChars - strPos < 3 * 2)
return -1;
byte* p = _data
}
*/
return -2;
}
private static unsafe bool IsLZMA_(byte* p, ref uint dictionary)
{
dictionary = *(uint*)(p + 1);
return p[0] == 0x5D &&
p[1] == 0x00 && p[2] == 0x00 &&
p[5] == 0x00 && (p[6] & 0x80) == 0x00;
}
private static unsafe bool IsBZip2(byte* p)
{
return (p[0] == 0x31 && p[1] < 14);
}
private static unsafe bool IsLZMA(byte* p, ref uint dictionary)
{
if (IsLZMA_(p, ref dictionary))
return true;
if (*(uint*)p <= 1 && IsLZMA_(p + 1, ref dictionary))
return true;
return false;
}
public static int SwapEndianness(int value)
{
var b1 = (value >> 0) & 0xff;
var b2 = (value >> 8) & 0xff;
var b3 = (value >> 16) & 0xff;
var b4 = (value >> 24) & 0xff;
return b1 << 24 | b2 << 16 | b3 << 8 | b4 << 0;
}
}
[StructLayout(LayoutKind.Sequential)]
struct FirstHeader
{
public uint flags;
public uint siginfo;
public int nsinst0, nsinst1, nsinst2;
public int length_of_header;
public int length_of_all_following_data;
// nsisbi only
public int datablock_lowpart;
public int datablock_highpart;
}
enum NFlags : uint
{
kUninstall = 1,
kSilent = 2,
kNoCrc = 4,
kForceCrc = 8,
kNsisBiInstall = 16,
kHasExternalFile = 32,
kIsStubInstaller = 64,
}
[StructLayout(LayoutKind.Sequential)]
struct Header
{
public uint flags;
public BlockHeader blockSections;
public BlockHeader blockEntries;
public BlockHeader blockStrings;
public BlockHeader blockLangTables;
public BlockHeader blockCtlColors;
public BlockHeader blockData;
public int installRegRootkey;
public int installRegKeyPtr, installRegValuePtr;
}
struct BlockHeader
{
public uint offset;
public uint num;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment