Example #1
0
        static void Main(string[] args)
        {
            Bio.Header("BioLib Command Line Test Program", "1.0.0", "2020", "");

            //TestOutputByteArray();
            //TestOverwritePrompt();

            Bio.Pause();
        }
Example #2
0
        static void Main(string[] args)
        {
            const string USAGE = "[<options>...] <installer> [<output_directory>]\n\n" +
                                 "<options>:\n" +
                                 "  -v <version>\tExtract as installer version <version>. Auto-detection might not always work correctly, so it is possible to explicitly set the installer version.\n\n" +
                                 "  -db\tDump blocks. Save additional installer data like registry changes, license files and the uninstaller. This is considered raw data and might not be readable or usable.\n\n" +
                                 "  -si\tSimulate extraction without writing files to disk.";

            Bio.Header("cicdec - A Clickteam Install Creator unpacker", VERSION, "2019-2020", "Extracts files from installers made with Clickteam Install Creator", USAGE);

            inputFile = ParseCommandLine(args);
            var inputStream       = File.OpenRead(inputFile);
            var binaryReader      = new BinaryReader(inputStream);
            var inputStreamLength = inputStream.Length;

            // First we need to find the data section. The simplest way to do so
            // is searching for the signature 0x77, 0x77, 0x67, 0x54, 0x29, 0x48
            var startOffset = FindStartOffset(binaryReader);

            if (startOffset < 0)
            {
                Bio.Error("Failed to find overlay signature.", Bio.EXITCODE.INVALID_INPUT);
            }

            Bio.Cout("Starting extraction at offset " + startOffset + "\n");

            inputStream.Position = startOffset;

            // The data section consists of a varying number of data blocks,
            // whose headers give information about the type of data contained inside.
            while (inputStream.Position + BLOCK_HEADER_SIZE <= inputStreamLength)
            {
                var blockId = binaryReader.ReadUInt16();
                inputStream.Skip(2);                 // unknown
                var blockSize    = binaryReader.ReadUInt32();
                var blockType    = (BLOCK_TYPE)blockId;
                var nextBlockPos = inputStream.Position + blockSize;
                Bio.Cout(string.Format("Reading block 0x{0:X} {1,-16} with size {2}", blockId, (BLOCK_TYPE)blockId, blockSize));
                var outputFileName = string.Format("Block 0x{0:X} {1}.bin", blockId, (BLOCK_TYPE)blockId);

                if (blockType == BLOCK_TYPE.FILE_DATA)
                {
                    // Data block should always be last, but better be safe and parse all other blocks before
                    dataBlockStartPosition = inputStream.Position;

                    if (dumpBlocks)
                    {
                        using (var ms = inputStream.Extract((int)blockSize)) {
                            SaveToFile(ms, outputFileName);
                        }
                    }

                    continue;
                }
                else if (blockType == BLOCK_TYPE.FILE_LIST)
                {
                    fileListStream = UnpackStream(binaryReader, blockSize);
                    if (fileListStream == null)
                    {
                        Bio.Error("Failed to decompress file list", Bio.EXITCODE.RUNTIME_ERROR);
                    }

                    if (dumpBlocks)
                    {
                        SaveToFile(fileListStream, outputFileName);
                    }

                    fileListStream.MoveToStart();
                }
                else if (dumpBlocks)
                {
                    using (var decompressedStream = UnpackStream(binaryReader, blockSize)) {
                        SaveToFile(decompressedStream, outputFileName);
                    }
                }

                Bio.Debug("Pos: " + inputStream.Position + ", expected: " + nextBlockPos);
                inputStream.Position = nextBlockPos;
            }

            if (fileListStream == null)
            {
                Bio.Error("File list could not be read. Please send a bug report if you want the file to be supported in a future version.", Bio.EXITCODE.NOT_SUPPORTED);
            }

            // Install Creator supports external data files, instead of integrating
            // the files into the executable. This actually means the data block is
            // saved as a separate file, which we just need to read.
            var dataFilePath = Path.Combine(Path.GetDirectoryName(inputFile), Path.GetFileNameWithoutExtension(inputFile) + ".D01");

            if (File.Exists(dataFilePath))
            {
                Bio.Debug("External data file found");
                using (var dataFileStream = File.OpenRead(dataFilePath))
                    using (var offsetStream = new OffsetStream(dataFileStream, 4))             // Data files seem to have a 4 byte header
                        using (var concatenatedStream = inputStream.Concatenate(offsetStream)) {
                            ParseFileList(fileListStream, concatenatedStream.Length);
                            using (var concatenatedStreamBinaryReader = new BinaryReader(concatenatedStream)) {
                                ExtractFiles(concatenatedStream, concatenatedStreamBinaryReader);
                            }
                        }
            }
            else if (dataBlockStartPosition < 0)
            {
                Bio.Error("Could not find data block in installer and there is no external data file. The installer is likely corrupt.", Bio.EXITCODE.RUNTIME_ERROR);
            }
            else
            {
                ParseFileList(fileListStream, inputStreamLength);
                ExtractFiles(inputStream, binaryReader);
            }

            if (failedExtractions > 0)
            {
                if (failedExtractions == filesInfos.Count)
                {
                    Bio.Error("Extraction failed. The installer is either encrypted or a version, which is currently not supported.", Bio.EXITCODE.NOT_SUPPORTED);
                }
                Bio.Warn(failedExtractions + " files failed to extract.");
            }
            else
            {
                Bio.Cout("All OK");
            }

            Bio.Pause();
        }
Example #3
0
        static void Main(string[] args)
        {
            Bio.Header("godotdec", VERSION, "2018-2020", "A simple unpacker for Godot Engine package files (.pck|.exe)",
                       "[<options>] <input_file> [<output_directory>]\n\nOptions:\n-c\t--convert\tConvert textures and audio files");

            if (Bio.HasCommandlineSwitchHelp(args))
            {
                return;
            }
            ParseCommandLine(args.ToList());

            var failed = 0;

            using (var inputStream = new BinaryReader(File.Open(inputFile, FileMode.Open))) {
                if (inputStream.ReadInt32() != MAGIC)
                {
                    inputStream.BaseStream.Seek(-4, SeekOrigin.End);

                    CheckMagic(inputStream.ReadInt32());

                    inputStream.BaseStream.Skip(-12);
                    var offset = inputStream.ReadInt64();
                    inputStream.BaseStream.Skip(-offset - 8);

                    CheckMagic(inputStream.ReadInt32());
                }

                Bio.Cout($"Godot Engine version: {inputStream.ReadInt32()}.{inputStream.ReadInt32()}.{inputStream.ReadInt32()}.{inputStream.ReadInt32()}");

                // Skip reserved bytes (16x Int32)
                inputStream.BaseStream.Skip(16 * 4);

                var fileCount = inputStream.ReadInt32();
                Bio.Cout($"Found {fileCount} files in package");
                Bio.Cout("Reading file index");

                var fileIndex = new List <FileEntry>();
                for (var i = 0; i < fileCount; i++)
                {
                    var pathLength = inputStream.ReadInt32();
                    var path       = Encoding.UTF8.GetString(inputStream.ReadBytes(pathLength));
                    var fileEntry  = new FileEntry(path.ToString(), inputStream.ReadInt64(), inputStream.ReadInt64());
                    fileIndex.Add(fileEntry);
                    Bio.Debug(fileEntry);
                    inputStream.BaseStream.Skip(16);
                    //break;
                }

                if (fileIndex.Count < 1)
                {
                    Bio.Error("No files were found inside the archive", Bio.EXITCODE.RUNTIME_ERROR);
                }
                fileIndex.Sort((a, b) => (int)(a.offset - b.offset));

                var fileIndexEnd = inputStream.BaseStream.Position;
                for (var i = 0; i < fileIndex.Count; i++)
                {
                    var fileEntry = fileIndex[i];
                    Bio.Progress(fileEntry.path, i + 1, fileIndex.Count);
                    //break;
                    if (fileEntry.offset < fileIndexEnd)
                    {
                        Bio.Warn("Invalid file offset: " + fileEntry.offset);
                        continue;
                    }

                    // TODO: Only PNG compression is supported
                    if (convertAssets)
                    {
                        // https://github.com/godotengine/godot/blob/master/editor/import/resource_importer_texture.cpp#L222
                        if (fileEntry.path.EndsWith(".stex") && fileEntry.path.Contains(".png"))
                        {
                            fileEntry.Resize(32);
                            fileEntry.ChangeExtension(".stex", ".png");
                            Bio.Debug(fileEntry);
                        }
                        // https://github.com/godotengine/godot/blob/master/core/io/resource_format_binary.cpp#L836
                        else if (fileEntry.path.EndsWith(".oggstr"))
                        {
                            fileEntry.Resize(279, 4);
                            fileEntry.ChangeExtension(".oggstr", ".ogg");
                        }
                        // https://github.com/godotengine/godot/blob/master/scene/resources/audio_stream_sample.cpp#L518
                        else if (fileEntry.path.EndsWith(".sample"))
                        {
                            // TODO
                            Bio.Warn("The file type '.sample' is currently not supported");
                        }
                    }

                    inputStream.BaseStream.Position = fileEntry.offset;
                    var destination = Path.Combine(outputDirectory, fileEntry.path);

                    try {
                        Action <Stream, Stream> copyFunction = (input, output) => input.Copy(output, (int)fileEntry.size);
                        inputStream.BaseStream.WriteToFile(destination, PROMPT_ID, copyFunction);
                    }
                    catch (Exception e) {
                        Bio.Error(e);
                        failed++;
                    }
                }
            }

            Bio.Cout();
            Bio.Cout(failed < 1? "All OK": failed + " files failed to extract");
            Bio.Pause();
        }