Exemple #1
0
        public BinaryReader(IBinarySource source, Endianness endianness = Endianness.Little)
        {
            source.ThrowIfNull(nameof(source));
            Source = source;

            Endianness = endianness;
            AssignReaders();
        }
Exemple #2
0
        public PEImage(IBinarySource source, bool disposeSource = true)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            _source        = source;
            _disposeSource = disposeSource;
            Read();
        }
Exemple #3
0
        public void Dispose()
        {
            if (_source != null)
            {
                if (_disposeSource)
                {
                    _source.Dispose();
                }

                _source = null;
            }
        }
 public static bool IsAssembly(IBinarySource source)
 {
     try
     {
         var pe       = new PEImage(source, false);
         var metadata = MemoryMappedMetadata.Load(pe);
         return(metadata.GetTableRowCount(Metadata.MetadataTableType.Assembly) > 0);
     }
     catch (Exception)
     {
         return(false);
     }
 }
Exemple #5
0
        /// <summary>
        /// Compresses an input sequence to a GBA-compatible LZ77 compressed sequence.
        /// </summary>
        /// <param name="source">source data</param>
        /// <param name="offset">source data offset</param>
        /// <param name="length">source data length</param>
        /// <returns>compressed sequence</returns>
        /// <remarks>
        /// An LZ77 stream is fairly straightforward. The first four bytes are a header:
        ///   [00]: Always 0x10
        ///   [01-03]: Length of uncompressed data in bytes (little endian)
        ///
        /// What follows is a sequence of blocks. Each block has an 8-bit mode header,
        /// ordered from MSB to LSB. Immediately following this header is a sequence of 8 chunks,
        /// whose meaning depends on the corresponding bit in the mode header.
        ///
        /// If the mode bit is 0, then the chunk is a single byte that should be copied directly
        /// to the uncompressed output.
        ///
        /// If the mode bit is 1, then we will be looking up a string from the partially-uncompressed
        /// output buffer and copying it to the end of the buffer. This string is described by
        /// a distance value and a length value, where the distance is how far backwards from the
        /// current output position the string begins and the length is how many bytes to copy.
        /// The chunk is a 16-bit value, where the lowest 12 bits is (distance - 1) and the
        /// highest 4 bits is (length - 3);
        ///
        /// Lookup strings are allowed to overlap the current input position.
        ///
        /// There is a hardware bug when uncompressing to VRAM where garbage data will be written if
        /// the distance is only 1. This has something to do with the VRAM bus being 16 bits wide
        /// or whatever. Anyway, for any data that is to be uncompressed directly to VRAM, then
        /// the CompressToVram flag should be true (and it is by default since this is by far the
        /// most common use case), and consequently the minimum distance should be 2. This is why
        /// compressed streams tend to have a lot of [F0 01] chunks rather than [F0 00].
        ///
        /// The specific algorithm being used is an optimized brute-force, since the encoding is
        /// so straightforward and the lookup window is fixed to a 12-bit distance and 4-bit length
        /// -- we can only search 0x1000 bytes backwards for strings of at most 18 bytes in length,
        /// which makes brute-forcing O(n) with the input size; albeit still a slow algorithm if
        /// using naive searching.
        ///
        /// The main optimization comes from using linked lists to store the starting position of
        /// each of the 256 possible starting bytes of the current input string.
        ///
        /// This implementation is thread-safe.
        /// </remarks>
        public virtual IBinarySource Compress(IBinarySource source, int offset, int length)
        {
            if (length < 0)
            {
                throw new ArgumentException(nameof(length));
            }

            if (offset + length > source.Length)
            {
                throw new ArgumentException(nameof(length));
            }

            LinkedList <int>[] lookup = new LinkedList <int> [256];
            for (int i = 0; i < 256; i++)
            {
                lookup[i] = new LinkedList <int>();
            }

            // Overall compressed buffer
            var compBuffer   = _compressBuffers.Value;
            int compPosition = 0;

            // Current mode byte (we'll be using 8 bits at a time)
            int modeByte  = 0;
            int modeIndex = 0;

            // tempBuffer contains the current sequence of chunks corresponding to the current
            // mode byte. Since the mode byte as 8 bits and each chunk has at most 16 bits, then
            // the largest possible chunk sequence is 8*16 bits = 16 bytes
            byte[] tempBuffer = new byte[16];
            int    tempIndex  = 0;

            int minimumDistance = CompressToVram ? 2 : 1;

            // Start by writing the header
            compBuffer[compPosition++] = 0x10;
            compBuffer[compPosition++] = (byte)(length & 0xFF);
            compBuffer[compPosition++] = (byte)((length >> 8) & 0xFF);
            compBuffer[compPosition++] = (byte)((length >> 16) & 0xFF);

            // First byte needs to be a direct copy (we don't have any strings yet in the
            // uncompressed buffer)
            byte firstValue = source[offset];

            addDirectCopyChunk(firstValue);
            lookup[firstValue].AddFirst(0);

            // Start the algorithm from the second byte
            for (int sourceIndex = 1; sourceIndex < length;)
            {
                byte sourceValue = source[offset + sourceIndex];

                // Find the longest string already in the uncompressed buffer
                // which starts with sourceValue
                var pastStringPositions = lookup[sourceValue];

                if (pastStringPositions.Count == 0)
                {
                    // No strings exist which start with sourceValue; use direct copy mode
                    addDirectCopyChunk(sourceValue);
                    pastStringPositions.AddFirst(sourceIndex);
                    sourceIndex++;
                }
                else
                {
                    // Search all past positions for longest string
                    int longestMatchPosition = 0;
                    int longestMatchLength   = 0;

                    int longestPossibleMatchLength = Math.Min(18, length - sourceIndex);

                    if (longestPossibleMatchLength >= 3)
                    {
                        foreach (int pastStringPosition in pastStringPositions)
                        {
                            // Don't bother looking past 0x1000 bytes away, we can't use them
                            if (sourceIndex - pastStringPosition > 0x1000)
                            {
                                break;
                            }

                            // String must be at least 1 byte away (or 2 for VRAM)
                            if (sourceIndex - pastStringPosition < minimumDistance)
                            {
                                continue;
                            }

                            // See how long this string match is
                            int matchLength = 1;
                            for (int i = 1; i < longestPossibleMatchLength; i++)
                            {
                                if (source[offset + sourceIndex + i] != source[offset + pastStringPosition + i])
                                {
                                    break;
                                }

                                matchLength++;
                            }

                            if (matchLength < 3)
                            {
                                continue;
                            }

                            if (matchLength > longestMatchLength)
                            {
                                // Record new best match
                                longestMatchPosition = pastStringPosition;
                                longestMatchLength   = matchLength;

                                // We can stop looking if we've hit the longest possible length
                                if (matchLength == longestPossibleMatchLength)
                                {
                                    break;
                                }
                            }
                        }
                    }

                    // Whether or not we found a string, the current byte is going to the uncompressed
                    // buffer, so add it to the lookup table
                    pastStringPositions.AddFirst(sourceIndex);

                    if (longestMatchLength > 0)
                    {
                        addLookupChunk(sourceIndex - longestMatchPosition, longestMatchLength);

                        // Add the rest of the string to the lookup table
                        for (int i = 1; i < longestMatchLength; i++)
                        {
                            byte match = source[offset + sourceIndex + i];
                            lookup[match].AddFirst(sourceIndex + i);
                        }

                        sourceIndex += longestMatchLength;
                    }
                    else
                    {
                        // Couldn't find a long enough string; fall back to direct copy
                        addDirectCopyChunk(sourceValue);
                        sourceIndex++;
                    }
                }
            }

            // Flush the mode byte since we're done
            if (modeIndex > 0)
            {
                flush();
            }

            return(new ByteArraySource(compBuffer, 0, compPosition));

            void addDirectCopyChunk(byte directCopyValue)
            {
                tempBuffer[tempIndex++] = directCopyValue;
                modeIndex++;

                if (modeIndex == 8)
                {
                    flush();
                }
            }

            void addLookupChunk(int lookupDistance, int lookupLength)
            {
                int lookupChunk = (lookupDistance - 1) | ((lookupLength - 3) << 12);

                tempBuffer[tempIndex++] = (byte)((lookupChunk >> 8) & 0xFF);
                tempBuffer[tempIndex++] = (byte)(lookupChunk & 0xFF);

                modeByte |= (1 << (7 - modeIndex));
                modeIndex++;

                if (modeIndex == 8)
                {
                    flush();
                }
            }

            void flush()
            {
                compBuffer[compPosition++] = (byte)modeByte;
                Array.Copy(tempBuffer, 0, compBuffer.Data, compPosition, tempIndex);
                compPosition += tempIndex;

                modeByte  = 0;
                modeIndex = 0;
                tempIndex = 0;
            }
        }
Exemple #6
0
        public virtual IBinarySource Decompress(IBinarySource source, int offset)
        {
            // Check for LZ77 signature
            if (source[offset++] != 0x10)
            {
                throw new Exception($"Expected LZ77 header");
            }

            // Read the block length
            int length = source[offset++];

            length += (source[offset++] << 8);
            length += (source[offset++] << 16);

            var decompressed   = new ByteArraySource(length);
            int decompPosition = 0;

            while (decompPosition < length)
            {
                byte mode = source[offset++];
                for (int i = 0; i < 8; i++)
                {
                    switch ((mode >> (7 - i)) & 1)
                    {
                    case 0:

                        // Direct copy
                        if (decompPosition >= length)
                        {
                            break;
                        }

                        decompressed[decompPosition++] = source[offset++];
                        break;

                    case 1:

                        // String lookup
                        int lookup = (source[offset++] << 8);
                        lookup += source[offset++];

                        int numBytes = ((lookup >> 12) & 0xF) + 3;
                        int distance = (lookup & 0xFFF);

                        for (int j = 0; j < numBytes; j++)
                        {
                            if (decompPosition >= length)
                            {
                                break;
                            }

                            decompressed[decompPosition] = decompressed[decompPosition - distance - 1];
                            decompPosition++;
                        }

                        break;

                    default:
                        break;
                    }
                }
            }

            return(decompressed);
        }
Exemple #7
0
        private static void TestSource(IBinarySource source)
        {
            var subSource = source.OffsetedSource(12, 24);

            {
                using var reader = source.CreateReader();
                Assert.AreEqual(0, reader.BaseStream.Position);
                Assert.AreEqual(1024, reader.BaseStream.Length);

                Assert.AreEqual(0, reader.ReadInt32());
                Assert.AreEqual(1, reader.ReadInt32());
                Assert.AreEqual(2, reader.ReadInt32());

                reader.BaseStream.Seek(-12, SeekOrigin.End);
                Assert.AreEqual(253, reader.ReadInt32());
                Assert.AreEqual(254, reader.ReadInt32());
                Assert.AreEqual(255, reader.ReadInt32());

                reader.BaseStream.Seek(-4, SeekOrigin.Current);
                Assert.AreEqual(255, reader.ReadInt32());
            }

            {
                using var reader = subSource.CreateReader();
                Assert.AreEqual(0, reader.BaseStream.Position);
                Assert.AreEqual(24, reader.BaseStream.Length);

                Assert.AreEqual(3, reader.ReadInt32());
                Assert.AreEqual(4, reader.ReadInt32());
                Assert.AreEqual(5, reader.ReadInt32());

                reader.BaseStream.Seek(-12, SeekOrigin.End);
                Assert.AreEqual(6, reader.ReadInt32());
                Assert.AreEqual(7, reader.ReadInt32());
                Assert.AreEqual(8, reader.ReadInt32());

                reader.BaseStream.Seek(-4, SeekOrigin.Current);
                Assert.AreEqual(8, reader.ReadInt32());
            }

            {
                using var reader = subSource.CreateReader();
                reader.BaseStream.Seek(0, SeekOrigin.End);
                Assert.Throws <EndOfStreamException>(() => reader.ReadInt32());
            }

            {
                using var reader = subSource.CreateReader();

                Assert.Throws <IOException>(() => reader.BaseStream.Seek(-1, SeekOrigin.Begin));
                Assert.Throws <IOException>(() => reader.BaseStream.Seek(-1, SeekOrigin.Current));
                Assert.Throws <IOException>(() => reader.BaseStream.Seek(-25, SeekOrigin.End));

                reader.BaseStream.Seek(1, SeekOrigin.End); // Seeking past end is fine

                // Verify we don't read anything when position is past the end of stream
                var temp = new byte[8];
                for (var i = 0; i < temp.Length; i++)
                {
                    temp[i] = 0xC3;
                }
                Assert.AreEqual(0, reader.Read(temp));
                Assert.IsTrue(temp.All(x => x == 0xC3));
            }
        }