use crate::{
sacd_reader::SacdReader,
sacd_ripper::{
ServerRequest, ServerResponse, server_request::Type as req_type,
server_response::Type as resp_type,
},
};
use anyhow::{Context, Result};
use log::{debug, info, trace};
use prost::Message;
use std::fs::File;
use std::io::{BufWriter, Read, Write};
use std::net::{IpAddr, SocketAddr, TcpStream};
use std::path::Path;
pub struct NetReader {
stream: TcpStream,
}
impl Drop for NetReader {
fn drop(&mut self) {
self.close_reader();
}
}
impl SacdReader for NetReader {
fn read_data(&mut self, start_lsn: u32, sector_count: u32) -> Result<Vec<u8>> {
self.read_data(start_lsn, sector_count)
}
fn get_total_sectors(&mut self) -> Result<u32> {
self.get_total_sectors()
}
}
impl NetReader {
pub fn open_network_reader(ip_addr: IpAddr, port: u16) -> Result<Self> {
let socket_addr = SocketAddr::new(ip_addr, port);
let stream = TcpStream::connect(socket_addr).context("couldn't connect to server")?;
stream
.set_nodelay(true)
.context("couldn't set TCP_NODELAY")?;
let mut handle = NetReader { stream };
let req = ServerRequest {
r#type: req_type::DiscOpen as i32,
sector_offset: Some(0),
sector_count: Some(0),
};
let response = handle.send_req(req)?;
if response.result != 0 || response.r#type != resp_type::DiscOpened as i32 {
anyhow::bail!("response result non-zero or incorrect type");
}
Ok(handle)
}
fn close_reader(&mut self) {
let req = ServerRequest {
r#type: req_type::DiscClose as i32,
sector_offset: Some(0),
sector_count: Some(0),
};
let _ = self.send_req(req);
debug!("reader dropped and closed");
}
fn send_req(&mut self, req: ServerRequest) -> Result<ServerResponse> {
let mut encoded_request = Vec::new();
req.encode(&mut encoded_request)
.context("couldn't encode request")?;
self.stream
.write_all(&encoded_request)
.context("couldn't write stream")?;
let zero: u8 = 0;
self.stream
.write_all(&[zero])
.context("couldn't write stream terminator")?;
self.stream.flush()?;
let mut buffer = Vec::new();
let mut temp_buf = [0u8; 8192];
let max_size = 1024 * 1024 + 1024;
loop {
let bytes_read = self
.stream
.read(&mut temp_buf)
.context("couldn't read from stream")?;
if bytes_read == 0 {
anyhow::bail!("Connection closed before receiving complete message");
}
buffer.extend_from_slice(&temp_buf[..bytes_read]);
if buffer.last() == Some(&0) {
let msg_bytes = &buffer[..buffer.len() - 1];
match ServerResponse::decode(msg_bytes) {
Ok(response) => {
trace!(
"Successfully decoded message ({} bytes + terminator)",
msg_bytes.len()
);
trace!(
"Decoded response: type={}, result={}",
response.r#type, response.result
);
return Ok(response);
}
Err(_) => {
trace!("incomplete response, reading more");
}
}
}
if buffer.len() > max_size {
anyhow::bail!(
"Message size exceeded maximum ({}MB)",
max_size / (1024 * 1024)
);
}
}
}
pub fn get_total_sectors(&mut self) -> Result<u32> {
let req = ServerRequest {
r#type: req_type::DiscSize as i32,
sector_offset: Some(0),
sector_count: Some(0),
};
let response = self.send_req(req)?;
if response.r#type != resp_type::DiscSize as i32 {
anyhow::bail!("Expected DISC_SIZE response, got type {}", response.r#type);
}
let total_sectors = response.result as u32;
info!(
"Server reported {} total sectors ({} MB)",
total_sectors,
(total_sectors as u64 * 2048) / (1024 * 1024)
);
Ok(total_sectors)
}
pub fn read_data(&mut self, pos: u32, block_count: u32) -> Result<Vec<u8>> {
let req = ServerRequest {
r#type: req_type::DiscRead as i32,
sector_offset: Some(pos),
sector_count: Some(block_count),
};
let response = self.send_req(req)?;
if response.r#type != resp_type::DiscRead as i32 {
anyhow::bail!("Expected DISC_READ response, got type {}", response.r#type);
}
if let Some(data) = response.data {
let sectors_read = response.result as u32;
if sectors_read != block_count {
trace!(
"Server returned {} sectors (requested {})",
sectors_read, block_count
);
}
trace!(
"Read {} sectors ({} bytes) from sector {}",
sectors_read,
data.len(),
pos
);
let expected_size = (sectors_read as usize) * 2048;
if data.len() != expected_size {
anyhow::bail!(
"Server returned {} bytes for {} sectors, expected {} bytes",
data.len(),
sectors_read,
expected_size
);
}
Ok(data)
} else {
anyhow::bail!(
"Server returned DISC_READ response without data (result={})",
response.result
);
}
}
pub fn dump_iso<P: AsRef<Path>, F>(
&mut self,
output_path: P,
lsn_size: usize,
mut progress_callback: Option<F>,
) -> Result<u32>
where
F: FnMut(u32, u32),
{
const MAX_BLOCK_SIZE: u32 = 512;
let total_sectors = self
.get_total_sectors()
.context("Failed to get total sectors")?;
info!(
"Dumping ISO: {} sectors ({} MB)",
total_sectors,
(total_sectors as u64 * lsn_size as u64) / (1024 * 1024)
);
let mut output_file = BufWriter::new(
File::create(output_path.as_ref()).context("Failed to create output file")?,
);
let mut current_sector = 0u32;
while current_sector < total_sectors {
let block_size = std::cmp::min(MAX_BLOCK_SIZE, total_sectors - current_sector);
let data = self
.read_data(current_sector, block_size)
.with_context(|| {
format!(
"Failed to read sectors {} to {}",
current_sector,
current_sector + block_size
)
})?;
let sectors_read = (data.len() / lsn_size) as u32;
if sectors_read == 0 {
if current_sector < total_sectors {
anyhow::bail!(
"Server returned 0 sectors at position {}, but {} sectors remain",
current_sector,
total_sectors - current_sector
);
}
break;
}
output_file
.write_all(&data)
.context("Failed to write to output file")?;
current_sector += sectors_read;
if let Some(ref mut callback) = progress_callback {
callback(current_sector, total_sectors);
}
if current_sector.is_multiple_of(10240) || current_sector == total_sectors {
info!(
"Finished: {}/{} sectors ({:.1}%)",
current_sector,
total_sectors,
(current_sector as f64 / total_sectors as f64) * 100.0
);
}
}
output_file.flush().context("Failed to flush output file")?;
info!("ISO dump complete: {} sectors written", total_sectors);
Ok(total_sectors)
}
}