static void Main(string[] args) { Bio.Header("BioLib Command Line Test Program", "1.0.0", "2020", ""); //TestOutputByteArray(); //TestOverwritePrompt(); Bio.Pause(); }
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(); }
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(); }