Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
This is a command line tool to interact with a SNES over USB, for example to transfer files to a flash cart's SD card. It has not been very thoroughly tested, so make sure you have backups of any important files on your SNES before using this.

It requires you to be running a separate program to manage the SNES communication. You can use either:

* [QUsb2Snes](https://skarsnik.github.io/QUsb2snes/)
* [SNI](https://github.com/alttpo/sni) (also included as part of Archipelago)

## Installation
If you're on windows, get it from the sidebar on the right of this github page.

Otherwise build it from source.

## Usage

`usb2snes-cli --help` for a description of all the available operations

Things you can do include: listing/writing/deleting files on the device, launching a specific rom, resetting the current
game, and returning to the main menu.

### upload-latest-sfc
This command lets you automatically select the newest .sfc file in some directory and send it to the device.
For example, you might use it like this:

`usb2snes-cli --upload-latest-sfc c:/users/<username>/Downloads incoming --wipe-target-dir`

This will take the newest sfc file in your Downloads directory and send it to the `incoming/` directory on the SNES. By including --wipe-target-dir, it makes it delete any other sfc files in `incoming/`

You might find it convenient to have a shortcut that does this if you play a lot of randos and you want a quick one-button way to get your newest rando rom onto your console.

## How to Build From Source
Install Rust from https://www.rust-lang.org/

Then do `cargo build`
91 changes: 86 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ use std::thread::sleep;
use std::io::prelude::*;
use std::fs::File;
use std::fs;
use std::path::Path;
use std::time::Duration;
use scan_fmt::scan_fmt;
use crate::usb2snes::usb2snes::SyncClient;

mod usb2snes;

Expand Down Expand Up @@ -70,7 +72,25 @@ struct Opt {
path_to_remove: Option<String>,

#[structopt(long = "devel", name = "Show all the transaction with the usb2snes server")]
devel: bool
devel: bool,

#[structopt(subcommand)]
command: Option<Command>
}

#[derive(StructOpt, Debug)]
enum Command {
#[structopt(name = "upload-latest-sfc", about = "Find the most recent .sfc file in local-source dir, and upload it to target-dir on the device")]
UploadLatestSfc {
#[structopt(name = "local-source-dir", help = "Directory on this computer to get the latest .sfc out of, e.g. your downloads folder")]
local_source_dir: String,

#[structopt(name = "target-dir", help = "Directory on the device to put the .sfc into")]
target_dir: String,

#[structopt(long = "wipe-target-dir", help = "Delete .sfc files in target-dir before copying a new one there")]
wipe_target_dir: bool
}
}

fn main() {
Expand Down Expand Up @@ -162,9 +182,8 @@ fn main() {
std::process::exit(1);
}
let local_path = opt.file_to_upload.unwrap();
let data = fs::read(local_path).expect("Error opening the file or reading the content");
let path = opt.path.unwrap();
usb2snes.send_file(&path, data);
let snes_path = opt.path.unwrap();
upload_file(local_path, snes_path, &mut usb2snes);
}
if opt.file_to_download != None {
let path:String = opt.file_to_download.unwrap();
Expand All @@ -174,7 +193,7 @@ fn main() {
let f = File::create(local_path);
let mut f = match f {
Ok(file) => file,
Err(err) => panic!("Probleme opening the file {:?} : {:?}", path, err),
Err(err) => panic!("Problem opening the file {:?} : {:?}", path, err),
};
f.write_all(&data).expect("Can't write the data to the file");
}
Expand All @@ -183,5 +202,67 @@ fn main() {
println!("Removing : {:?}", path);
usb2snes.remove_path(&path);
}
match opt.command {
Some(Command::UploadLatestSfc {local_source_dir, target_dir, wipe_target_dir }) => {
do_upload_latest_sfc(&mut usb2snes, local_source_dir, target_dir, wipe_target_dir).unwrap();
},
None => {}
}

}
}

fn upload_file(local_path:String, snes_path:String, usb2snes: &mut SyncClient) {
print!("Sending file {} to snes at {}", local_path, snes_path);
let data = fs::read(local_path).expect("Error opening the file or reading the content");
usb2snes.send_file(&snes_path, data);
}

fn do_upload_latest_sfc(usb2snes: &mut SyncClient, local_source_dir:String, target_dir:String, wipe_target_dir:bool)
-> Result<(), Box<dyn std::error::Error>> {
println!("Uploading latest sfc from {:?} to {:?}. with wipe-target-dir={:?}",
local_source_dir,
target_dir,
wipe_target_dir);
let local_dir_path = Path::new(&local_source_dir);

let mut newest_seconds_since_mod = u64::MAX;
let mut newest_file_name:Option<String> = None;
for entry in fs::read_dir(local_dir_path)
.expect(&format!("Can't read the given local dir {:?}", local_source_dir)) {
let entry = entry?;

let file_path = entry.path();
let f_name = entry.file_name().to_string_lossy().into_owned();
if !f_name.ends_with(".sfc") {
continue
}

let metadata = fs::metadata(&file_path)?;
let seconds_since_mod = metadata.modified()?.elapsed()?.as_secs();
if seconds_since_mod < newest_seconds_since_mod {
newest_seconds_since_mod = seconds_since_mod;
newest_file_name = Some(f_name);
}
}
let file_name_to_send = newest_file_name.expect("No sfc found in local dir");
println!("Newest sfc file found: {:?}", file_name_to_send);

if wipe_target_dir {
let roms = usb2snes.ls(&target_dir);
for rom in roms {
if rom.name.ends_with(".sfc") {
let snes_path = format!("{}/{}", target_dir, rom.name);
println!("Deleting snes file {}", snes_path);
usb2snes.remove_path(&snes_path);
}
}

}

let local_path:String = format!("{}/{}", local_source_dir, file_name_to_send);
let snes_path:String = format!("{}/{}", target_dir, file_name_to_send);
upload_file(local_path, snes_path, usb2snes);

Ok(())
}