initial commit

This commit is contained in:
2021-04-17 04:12:25 +03:00
commit bcb1ff0477
11 changed files with 745 additions and 0 deletions

10
src/buffertrim.rs Normal file
View File

@ -0,0 +1,10 @@
fn is_not_empty(e: &u8) -> bool {
*e != 0x00
}
pub fn trim(buffer: &[u8]) -> Vec<u8> {
let begin = buffer.iter().position(is_not_empty);
let end = buffer.iter().rev().position(is_not_empty).map(|j| buffer.len() - j);
let vec = begin.and_then(|i| end.map(|j| buffer[i..j].iter().cloned().collect())).unwrap_or(Vec::new());
vec
}

92
src/console.rs Normal file
View File

@ -0,0 +1,92 @@
#[cfg(windows)]
pub fn enable_ansi_support() -> Result<(), u32> {
// ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
use std::ffi::OsStr;
use std::iter::once;
use std::os::windows::ffi::OsStrExt;
use std::ptr::null_mut;
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
unsafe {
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
let console_out_name: Vec<u16> = OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
let console_handle = CreateFileW(
console_out_name.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
);
if console_handle == INVALID_HANDLE_VALUE
{
return Err(GetLastError());
}
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
let mut console_mode: u32 = 0;
if 0 == GetConsoleMode(console_handle, &mut console_mode)
{
return Err(GetLastError());
}
// VT processing not already enabled?
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
if 0 == SetConsoleMode(console_handle, console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
{
return Err(GetLastError());
}
}
}
return Ok(());
}
pub fn set_ctrl_c_handler() {
use std::sync;
use std::process;
let running = sync::Arc::new(sync::atomic::AtomicBool::new(true));
let r = running.clone();
ctrlc::set_handler(move || {
r.store(false, sync::atomic::Ordering::SeqCst);
}).expect("> Error setting Ctrl-C handler");
println!("> Press Ctrl + C for exit");
while running.load(sync::atomic::Ordering::SeqCst) {}
println!("> Exiting...");
process::exit(0);
}
pub fn get_raw_command_line() -> String {
// use winapi::um::processenv::GetCommandLineW;
unsafe {
let line = GetCommandLineW();
let mut cursor = line;
let mut length = 0;
while *cursor != 0 {
length += 1;
cursor = cursor.add(1);
}
let array: &[u16] = std::slice::from_raw_parts(line, length);
String::from_utf16(array).expect("Invalid unicode")
}
}
#[link(name="kernel32")]
extern "system" {
fn GetCommandLineW() -> *const u16;
}

205
src/lib.rs Normal file
View File

@ -0,0 +1,205 @@
mod buffertrim;
use dirs;
use colored::*;
use serde::Deserialize;
use fs::File;
use io::Read;
use std::{fs, io, path::{Path, PathBuf}};
pub fn run(config: Config) -> Result<(), String> {
let mut profiles = Vec::new();
let paths = fs::read_dir(config.profile_path).unwrap();
for path in paths {
profiles.push(path.unwrap().path());
}
if profiles.len() <= 0 {
return Err(format!("{}: {} {} \n{}", "> Error".red().bold(), "Profile folder not found.".white(), "You must run the game at least once!".white().bold(), "> Aborting.".red()));
}
let scrns = grab_scrn_files(&profiles);
if scrns.len() <= 0 {
return Err(format!("{}: {} \n{}", "> Error".red().bold(), "Screenshot files not found".white(), "> Aborting.".red()));
}
println!("{} {} {} \n{}", ">".green(), scrns.len().to_string().green(), "Screenshots found.".green(), "> Converting...".green().bold());
let mut images = Vec::new();
for file in &scrns {
let image = convert_file(file.to_path_buf()).unwrap();
images.push(image);
}
for image in &images {
match fs::write(Path::new(&config.export_path).join(image.metadata.uid.to_string() + ".jpg"), &image.image_data) {
Ok(ok) => ok,
Err(_err) => {
return Err(format!("{}: {} \n{}", "> Error".red().bold(), "System cannot find or create the specified path", "> Aborting.".red()))
}
}
}
println!("{}", "> Converting completed!".green());
println!("{} {}", "> Files saved to".green().bold(), Path::new(&config.export_path).to_string_lossy().bright_blue());
println!("{}", "> Removing old files...".green());
for path in scrns {
let _remove = fs::remove_file(path).unwrap();
}
Ok(())
}
pub struct Config {
pub profile_path: PathBuf,
pub export_path: PathBuf,
}
impl Config {
pub fn new() -> Result<Config, io::Error> {
let home_dir = dirs::home_dir().unwrap();
let profile_path = Path::new(&home_dir).join("Documents").join("Rockstar Games").join("Red Dead Redemption 2").join("Profiles");
let export_path = Path::new(&home_dir).join("Documents").join("Rockstar Games").join("Red Dead Redemption 2").join("Screenshots");
let _create_dirs = fs::create_dir_all(&export_path);
Ok(Config {profile_path, export_path})
}
pub fn set_custom_export_path<'a>(&'a mut self, path: &[String]) -> Result<&'a mut Config, String> {
if path.len() < 2 {
return Err(format!("{}", "Not enough arguments"));
}
let path = path[1].clone();
let custom_export_path = Path::new(&path).to_path_buf();
// TODO: Folders containing Cyrillic characters and spaces are created incorrectly i. e. anything after a space is ignored
// let _create_dirs = fs::create_dir_all(&custom_export_path);
self.export_path = custom_export_path;
Ok(self)
}
}
fn grab_scrn_files(profile_paths: &[PathBuf]) -> Vec<PathBuf> {
let mut prdr3s = Vec::new();
for path in profile_paths {
match fs::read_dir(path) {
Err(err) => println!("! {:?}", err.kind()),
Ok(paths) => for path in paths {
let file_path = path.unwrap().path();
let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
if file_stem.chars().count() >= 5 {
let file_stem_substr = &file_stem[0..5];
if file_stem_substr == "PRDR3" {
prdr3s.push(file_path)
}
}
}
}
}
prdr3s
}
fn read_file_buf(path: PathBuf) -> Result<Vec<u8>, io::Error> {
let mut file = File::open(path)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
return Ok(data);
}
fn find_index_in_buf(buffer: &Vec<u8>, index: &[u8]) -> usize {
let index = buffer.windows(index.len()).position(|window| window == index).unwrap();
index
}
fn grab_image_from_buf(buffer: &Vec<u8>) -> Vec<u8> {
let first_index = find_index_in_buf(&buffer, b"JPEG");
let last_index = find_index_in_buf(&buffer, b"JSON");
let image_data = buffertrim::trim(&buffer[first_index + 12..last_index]);
image_data
}
fn grab_metadata_from_buf(buffer: &Vec<u8>) -> Vec<u8> {
let first_index = find_index_in_buf(&buffer, b"JSON");
let last_index = find_index_in_buf(&buffer, b"TITL");
let metadata = buffertrim::trim(&buffer[first_index + 8..last_index]);
metadata
}
#[derive(Deserialize, Debug)]
struct Loc {
x: f64,
y: f64,
z: f64
}
#[derive(Deserialize, Debug)]
struct Time {
hour: i32,
minute: i32,
second: i32,
day: i32,
month: i32,
year: i32
}
#[derive(Deserialize, Debug)]
struct Meta {
horse: Option<Vec<i32>>
}
#[derive(Deserialize, Debug)]
struct ImageMetadata {
loc: Loc,
regionname: usize,
districtname: usize,
statename: usize,
nm: String,
sid: String,
crewid: usize,
mid: String,
mode: String,
meme: bool,
mug: bool,
uid: usize,
time: Time,
creat: usize,
slf: bool,
drctr: bool,
rsedtr: bool,
inphotomode: bool,
advanced: bool,
width: usize,
height: usize,
size: usize,
sign: usize,
meta: Meta
}
#[derive(Deserialize, Debug)]
struct Image {
metadata: ImageMetadata,
image_data: Vec<u8>
}
fn convert_file(path: PathBuf) -> Result<Image, io::Error> {
let mut file_buf = read_file_buf(path).unwrap();
file_buf = buffertrim::trim(&file_buf);
let image_data = grab_image_from_buf(&file_buf);
let metadata = grab_metadata_from_buf(&file_buf);
let parsed_metadata: ImageMetadata = serde_json::from_slice(&metadata).unwrap();
Ok(Image { metadata: parsed_metadata, image_data })
}

23
src/main.rs Normal file
View File

@ -0,0 +1,23 @@
mod buffertrim;
mod console;
use rdr2_screenshot_converter::Config;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let mut config = Config::new().unwrap();
if args.len() >= 2 {
config.set_custom_export_path(&args).unwrap();
}
let _ansi_support = console::enable_ansi_support();
if let Err(e) = rdr2_screenshot_converter::run(config) {
eprintln!("{}", e);
}
let _ctrl_c_handler = console::set_ctrl_c_handler();
}