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(); }