static void TestOverwritePrompt() { var path = Bio.GetProgramPath(); var renamed = Bio.EnsureFileDoesNotExist(path, "overwrite_prompt"); Bio.Cout(renamed); renamed = Bio.EnsureFileDoesNotExist(path, "overwrite_prompt"); Bio.Cout(renamed); }
static void Main(string[] args) { Bio.Header("godotdec", "2.1.0", "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.Seek(-12, SeekOrigin.Current); var offset = inputStream.ReadInt64(); inputStream.BaseStream.Seek(-offset - 8, SeekOrigin.Current); CheckMagic(inputStream.ReadInt32()); } Bio.Cout($"Godot Engine version: {inputStream.ReadInt32()}.{inputStream.ReadInt32()}.{inputStream.ReadInt32()}.{inputStream.ReadInt32()}"); // Skip reserved bytes (16x Int32) inputStream.BaseStream.Seek(16 * 4, SeekOrigin.Current); 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.Seek(16, SeekOrigin.Current); //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"); } } var destination = Path.Combine(outputDirectory, fileEntry.path); destination = Bio.EnsureFileDoesNotExist(destination, "godotdec_overwrite"); inputStream.BaseStream.Seek(fileEntry.offset, SeekOrigin.Begin); if (destination == null) { continue; } try { var fileMode = FileMode.CreateNew; Directory.CreateDirectory(Path.GetDirectoryName(destination)); if (File.Exists(destination)) { fileMode = FileMode.Create; } using (var outputStream = new FileStream(destination, fileMode)) { Bio.CopyStream(inputStream.BaseStream, outputStream, (int)fileEntry.size, false); } } catch (Exception e) { Bio.Error(e); failed++; } } } Bio.Cout(); Bio.Cout(failed < 1? "All OK": failed + " files failed to extract"); Bio.Pause(); }