Created
December 20, 2023 20:12
-
-
Save a9udn9u/d7b4d2dd79c33aef9ac805f1603a59d1 to your computer and use it in GitHub Desktop.
Convert RDR 2 photo mode file to JPG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // @ts-check | |
| const fsp = require('node:fs/promises'); | |
| const {basename, join} = require('path'); | |
| const INPUT_FILE_PREFIX = 'PRDR'; | |
| const OUTPUT_FILE_SUFFIX = '.jpg'; | |
| const BUFSIZE = 4192; // 4 KB | |
| const usage = () => { | |
| const {argv} = process; | |
| console.error("USAGE:", basename(argv[0]), basename(argv[1]), "FILE_OR_DIR"); | |
| console.error(` FILE_OR_DIR: Input file name must starts with "${INPUT_FILE_PREFIX}"`); | |
| } | |
| const getInputs = async () => { | |
| const fileOrDir = process.argv[2]; | |
| if (!fileOrDir) { | |
| usage(); | |
| return []; | |
| } | |
| let stat; | |
| try { | |
| stat = await fsp.stat(process.argv[2]); | |
| } catch (ex) { | |
| console.error("Can't open file:", fileOrDir); | |
| return []; | |
| } | |
| if (stat.isFile() && basename(fileOrDir).startsWith(INPUT_FILE_PREFIX)) { | |
| return [fileOrDir]; | |
| } | |
| if (stat.isDirectory()) { | |
| const inputs = (await fsp.readdir(fileOrDir, {withFileTypes: true})) | |
| .filter(e => e.isFile()) | |
| .filter(e => e.name.startsWith(INPUT_FILE_PREFIX)) | |
| .filter(e => !e.name.endsWith(OUTPUT_FILE_SUFFIX)) | |
| .map(e => join(fileOrDir, e.name)); | |
| if (!inputs.length) { | |
| usage(); | |
| } | |
| return inputs; | |
| } | |
| usage(); | |
| return []; | |
| } | |
| const getOutputFile = inputPath => inputPath + OUTPUT_FILE_SUFFIX; | |
| const extract = async (state) => { | |
| const {input, output, buffer} = state; | |
| const {bytesRead} = await input.read(state.buffer, 0, state.buffer.byteLength); | |
| state.bufferOffset = 0; | |
| state.bufferLength = bytesRead; | |
| for (let i = 0; i < bytesRead; i++) { | |
| state.currentByte = buffer[i]; | |
| if (!state.dataBegins && state.previousByte == 0xff && state.currentByte == 0xd8) { | |
| // Image data starts at previous byte, inclusive | |
| state.bufferOffset = i - 1; | |
| state.bufferLength -= state.bufferOffset; | |
| state.dataBegins = true; | |
| } | |
| if (!state.dataEnds && state.previousByte == 0xff && state.currentByte == 0xd9) { | |
| // Image data ends at previous byte, exclusive | |
| state.bufferLength = i - 1 - state.bufferOffset; | |
| state.dataEnds = true; | |
| break; | |
| } | |
| state.previousByte = state.currentByte; | |
| } | |
| if (state.dataBegins && state.bufferLength > 0) { | |
| await output.write(buffer, state.bufferOffset, state.bufferLength); | |
| } | |
| if (!state.dataEnds && bytesRead !== 0) { | |
| await extract(state); | |
| } | |
| } | |
| // Main routine | |
| (async () => { | |
| (await getInputs()).forEach(async prdr => { | |
| const input = await fsp.open(prdr, 'r'); | |
| const output = await fsp.open(getOutputFile(prdr), 'w'); | |
| await extract({ | |
| input, | |
| output, | |
| buffer: Buffer.alloc(BUFSIZE), | |
| bufferOffset: 0, | |
| bufferLength: 0, | |
| dataBegins: false, | |
| dataEnds: false, | |
| currentByte: 0x0, | |
| previousByte: 0x0, | |
| }); | |
| await input.close(); | |
| await output.close(); | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment