Пример #1
0
        public void Initialize()
        {
            Dictionary <ushort, NDSDirectory> directories = new Dictionary <ushort, NDSDirectory>();
            Dictionary <ushort, NDSFile>      files       = new Dictionary <ushort, NDSFile>();

            // The maximum address we can access while reading metaentries.
            uint dirEntriesAddrLimit = BitConverter.ToUInt32(RawFNT.Slice(0, sizeof(uint)).GetAsArrayCopy(), startIndex: 0);
            // We create a dictionary for children before setting them because folders/files may be out of order
            // and parents may not exist before referencing them.
            var childrenStructure = new Dictionary <ushort, List <ushort> >();

            // Everything before this entry in the FAT is an overlay file.
            ushort firstDataFileID = BitConverter.ToUInt16(RawFNT.Slice(sizeof(uint), sizeof(ushort)).GetAsArrayCopy(), startIndex: 0);

            // 0xFFFF is just a fictional ID used for convenience.
            RootOverlayDirectory = new NDSDirectory(this, 0xFFFF, "overlay");
            for (ushort overlayID = 0; overlayID < firstDataFileID; overlayID++)
            {
                RootOverlayDirectory.ChildrenFiles.Add(new NDSFile(this, overlayID, $"overlay_{overlayID}")
                {
                    Parent = RootOverlayDirectory
                });
            }

            for (int currentRelativeAddress = 0; currentRelativeAddress < dirEntriesAddrLimit; currentRelativeAddress += DirectoryEntrySize)
            {
                ByteSlice dirEntry = RawFNT.Slice(currentRelativeAddress, DirectoryEntrySize);
                // The address of the first named entry inside of this directory.
                uint namedEntryAddress = BitConverter.ToUInt32(dirEntry.Slice(0, sizeof(uint)).GetAsArrayCopy(), startIndex: 0);
                // The ID of the first file in this directory.
                ushort firstFileID = BitConverter.ToUInt16(dirEntry.Slice(sizeof(uint), sizeof(ushort)).GetAsArrayCopy(), startIndex: 0);
                // The ID of the parent of this directory. If folder is root, this will not start with 0xF000.
                ushort parentDirID = BitConverter.ToUInt16(dirEntry.Slice(sizeof(uint) + sizeof(ushort), sizeof(ushort)).GetAsArrayCopy(), startIndex: 0);
                // The ID of the directory that all these files belong to.
                ushort containerDirID = (ushort)(currentRelativeAddress / DirectoryEntrySize + EntryDirectoryFlag);

                ByteSlice namedEntry = RawFNT.Slice((int)namedEntryAddress, RawFNT.Size);
                readNamedEntry(namedEntry, firstFileID);

                void readNamedEntry(ByteSlice namedEntry, ushort fileID)
                {
                    if (namedEntry[0] == 0)
                    {
                        return;
                    }

                    bool   isDirectory = (namedEntry[0] & 0b10000000) > 0;
                    byte   nameLength  = (byte)(namedEntry[0] & 0b01111111);
                    string name        = Encoding.UTF8.GetString(namedEntry.Slice(1, nameLength).GetAsArrayCopy());

                    if (isDirectory)
                    {
                        ushort entryID = BitConverter.ToUInt16(namedEntry.Slice(1 + nameLength, sizeof(ushort)).GetAsArrayCopy(), startIndex: 0);
                        namedEntry.SliceEnd = namedEntry.SliceStart + 1 + nameLength + sizeof(ushort);
                        var directory = new NDSDirectory(this, entryID, name);
                        directories.Add(entryID, directory);

                        // If the parent is not a directory, then this entry represents the root directory (/data/)
                        if ((parentDirID & EntryDirectoryFlag) == 0)
                        {
                            RootDataDirectory = directory;
                        }
                        else
                        {
                            childrenStructure.TryGetValue(containerDirID, out var parent);
                            if (parent == null)
                            {
                                childrenStructure.Add(containerDirID, new List <ushort>()
                                {
                                    entryID
                                });
                            }
                            else
                            {
                                parent.Add(entryID);
                            }
                        }
                    }
                    else
                    {
                        namedEntry.SliceEnd = namedEntry.SliceStart + 1 + nameLength;
                        var file = new NDSFile(this, fileID, name);
                        if (files.ContainsKey(fileID))
                        {
                            files[fileID] = file;
                            Console.WriteLine($"[NDSFilesystem.Initialize] The file {fileID} was repeated!");
                        }
                        else
                        {
                            files.Add(fileID, file);
                        }

                        childrenStructure.TryGetValue(containerDirID, out var parent);
                        if (parent == null)
                        {
                            childrenStructure.Add(containerDirID, new List <ushort>()
                            {
                                fileID
                            });
                        }
                        else
                        {
                            parent.Add(fileID);
                        }
                    }

                    // Read next entry
                    readNamedEntry(RawFNT.Slice(namedEntry.SliceEnd - RawFNT.SliceStart, RawFNT.Size), (ushort)(fileID + 1));
                }
            }

            foreach (var parentRelations in childrenStructure)
            {
                directories.TryGetValue(parentRelations.Key, out var parent);
                if (parent == null)
                {
                    Console.WriteLine($"[NDSFilesystem.Initialize] Parent 0x{parentRelations.Key:X} does not exist as a directory!! (First child: {files[parentRelations.Value.First()].Name})");
                    continue;
                }
                else
                {
                    foreach (var child in parentRelations.Value)
                    {
                        if ((child & EntryDirectoryFlag) > 0)
                        {
                            parent.ChildrenDirectories.Add(directories[child]);
                            directories[child].Parent = parent;
                        }
                        else
                        {
                            parent.ChildrenFiles.Add(files[child]);
                            files[child].Parent = parent;
                        }
                    }
                }
            }
        }
Пример #2
0
        public static byte[] Compress(ByteSlice inputData)
        {
            // I recommend you read the LZ77 COMPRESSION chapter in the README to understand better what's happening.
            // For a brief explanation about how the actual compression method works, take a brief look at
            // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/fb98aa28-5cd7-407f-8869-a6cef1ff1ccb.

            BinaryWriter writer = new BinaryWriter(new MemoryStream());

            // First, write the uncompressed size (First byte is unknown, so we write 0x10, next three bytes are the size)
            writer.Write((uint)((inputData.Size << 8) | 0x10));

            for (int inputIndex = 0; inputIndex < inputData.Size;)
            {
                // Remember where to place the decision byte once we do 8 iterations of the compression algorithm.
                long decisionByteOffset = writer.BaseStream.Position;

                // Write a placeholder for the decision byte
                writer.Write((byte)0);
                byte decisionByte = 0;
                for (int decisionBit = 7; decisionBit >= 0; decisionBit--)
                {
                    // The maximum LZ77 length we can have is 0xF + matchMinLength and the maximum offset is 0xFFF,
                    // because the information is encoded in a short like `F FFF` where the first F is the length
                    // and the three other Fs are the offset.
                    const int matchMinLength = 3;
                    const int matchMaxOffset = 0xFFF;
                    const int matchMaxLength = 0xF + matchMinLength;
                    SearchForMatch(inputData, inputIndex, matchMaxOffset, matchMaxLength, out int matchPos, out int matchLength);

                    if (matchLength >= matchMinLength)
                    {
                        // We found a proper match, so we can reference previous data.
                        // First, let's set the decision bit so we know we're referencing data.
                        decisionByte |= (byte)(1 << decisionBit);
                        int relativeMatchPos = inputIndex - matchPos - 1;

                        ushort refPointer = (ushort)(((matchLength - matchMinLength) << 12) | (relativeMatchPos & 0xFFF));
                        refPointer = (ushort)((refPointer << 8) | (refPointer >> 8)); // Convert to Big Endian
                        writer.Write(refPointer);
                        inputIndex += matchLength;
                    }
                    else
                    {
                        // We didn't find any match, so we just copy a byte from the input data.
                        writer.Write(inputData[inputIndex++]);
                    }

                    if (inputIndex >= inputData.Size)
                    {
                        break;
                    }
                }

                // Write the decision byte and go back to the end of the stream.
                writer.Seek((int)decisionByteOffset, SeekOrigin.Begin);
                writer.Write(decisionByte);
                writer.Seek(0, SeekOrigin.End);
            }

            return(((MemoryStream)writer.BaseStream).ToArray());
        }
Пример #3
0
        // TODO: This is not the most elegant way to do this...
        /// <summary>
        /// Updates a byteslice with the same size as the ROM to reflect the filesystem's contents. This method will NOT change the header of the ROM,
        /// nor any sensitive data. For now, only updates the FAT & file data, not the FNT.
        /// </summary>
        public void PackTo(ByteSlice target)
        {
            ByteSlice targetRawFAT = target.Slice((int)ROM.Header.FATAddress, (int)ROM.Header.FATSize);
            ByteSlice targetHeader = ROM.Header.Data;

            targetHeader.Source = target.Source;
            ByteSlice targetRawFNT = RawFNT;

            targetRawFNT.Source = target.Source;
            const int fatEntrySize = 8;

            ByteSlice[] protectedMemoryRanges = new ByteSlice[]
            {
                targetHeader,
                targetRawFNT,
                targetRawFAT,
                target.Slice((int)ROM.Header.ARM9CodeAddress, (int)ROM.Header.ARM9CodeSize),
                target.Slice((int)ROM.Header.ARM7CodeAddress, (int)ROM.Header.ARM7CodeSize),
                target.Slice((int)ROM.Header.ARM9OverlayTableAddress, (int)ROM.Header.ARM9OverlayTableSize),
                target.Slice((int)ROM.Header.ARM7OverlayTableAddress, (int)ROM.Header.ARM7OverlayTableSize),
                target.Slice((int)ROM.Header.BannerAddress, (int)ROM.Header.BannerSize)
            };
            uint lastFileSaveAddress = 0;

            void SaveFile(NDSFile file)
            {
                int filesize = file.LatestVersionSize;

                ByteSlice GetFileSlice() => target.Slice((int)lastFileSaveAddress, filesize);
                IEnumerable <ByteSlice> calculateRangesIntersecting() =>
                from range in protectedMemoryRanges where range.Intersects(GetFileSlice()) select range;

                for (var rangesIntersecting = calculateRangesIntersecting();
                     rangesIntersecting.Any();
                     rangesIntersecting = calculateRangesIntersecting())
                {
                    lastFileSaveAddress = (uint)rangesIntersecting.First().SliceEnd;
                }
                Console.WriteLine($"Found space for file: {lastFileSaveAddress:X}");
                if (lastFileSaveAddress + filesize > target.SliceEnd)
                {
                    throw new Exception("No space in ROM to fit any more files!");
                }
                GetFileSlice().ReplaceWith(new ByteSlice(file.RetrieveLatestVersionData()));
                Console.WriteLine($"Changed {filesize}B.");

                var fileROMBounds = target.Slice((int)lastFileSaveAddress, filesize);

                // Change FAT entry (lower and upper bound)
                targetRawFAT.Slice(file.EntryID * fatEntrySize, sizeof(uint)).ReplaceWith(new ByteSlice(BitConverter.GetBytes(fileROMBounds.SliceStart)));
                targetRawFAT.Slice(file.EntryID * fatEntrySize + sizeof(uint), sizeof(uint)).ReplaceWith(new ByteSlice(BitConverter.GetBytes(fileROMBounds.SliceEnd)));

                lastFileSaveAddress += (uint)filesize;
            }

            void SaveDir(NDSDirectory dir)
            {
                foreach (var child in dir.ChildrenDirectories)
                {
                    SaveDir(child);
                }
                foreach (var child in dir.ChildrenFiles)
                {
                    SaveFile(child);
                }
            }

            SaveDir(RootOverlayDirectory);
            SaveDir(RootDataDirectory);
        }