-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit edd69ea
Showing
7 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
on: | ||
push: | ||
branches: | ||
- main | ||
- master | ||
pull_request: | ||
workflow_dispatch: | ||
|
||
jobs: | ||
linux: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-python@v4 | ||
with: | ||
python-version: "3.10" | ||
- name: Build wheels | ||
uses: PyO3/maturin-action@v1 | ||
with: | ||
target: ${{ matrix.target }} | ||
args: --release --out dist --find-interpreter | ||
manylinux: auto | ||
- name: Upload wheels | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: wheels | ||
path: dist | ||
|
||
windows: | ||
runs-on: windows-latest | ||
strategy: | ||
matrix: | ||
target: [x64, x86] | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-python@v4 | ||
with: | ||
python-version: "3.10" | ||
architecture: ${{ matrix.target }} | ||
- name: Build wheels | ||
uses: PyO3/maturin-action@v1 | ||
with: | ||
target: ${{ matrix.target }} | ||
args: --release --out dist --find-interpreter | ||
- name: Upload wheels | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: wheels | ||
path: dist | ||
|
||
macos: | ||
runs-on: macos-latest | ||
strategy: | ||
matrix: | ||
target: [x86_64, aarch64] | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-python@v4 | ||
with: | ||
python-version: "3.10" | ||
- name: Build wheels | ||
uses: PyO3/maturin-action@v1 | ||
with: | ||
target: ${{ matrix.target }} | ||
args: --release --out dist --find-interpreter | ||
- name: Upload wheels | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: wheels | ||
path: dist |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "farc" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
name = "farc" | ||
crate-type = ["cdylib", "lib"] | ||
|
||
[dependencies] | ||
binary_parser = { git = "https://github.com/BroGamer4256/binary_parser" } | ||
libflate = "2.0" | ||
thiserror = "1.0.56" | ||
pyo3 = { version = "0.20", features = ["extension-module"] } |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[build-system] | ||
requires = ["maturin>=0.14,<0.15"] | ||
build-backend = "maturin" | ||
|
||
[project] | ||
name = "farc" | ||
requires-python = ">=3.7" | ||
classifiers = [ | ||
"Programming Language :: Rust", | ||
"Programming Language :: Python :: Implementation :: CPython", | ||
"Programming Language :: Python :: Implementation :: PyPy", | ||
] |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
hard_tabs = true |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
use binary_parser::*; | ||
use libflate::gzip; | ||
use std::{ | ||
collections::BTreeMap, | ||
fs::File, | ||
io::{self, Cursor, Read, SeekFrom, Write}, | ||
path::Path, | ||
}; | ||
use thiserror::Error; | ||
|
||
pub mod py; | ||
|
||
pub struct Farc { | ||
pub entries: BTreeMap<String, BinaryParser>, | ||
} | ||
|
||
#[derive(Error, Debug)] | ||
pub enum FarcError { | ||
#[error("{0}")] | ||
BinaryParserError(#[from] BinaryParserError), | ||
#[error("File unsupported")] | ||
Unsupported, | ||
#[error("{0}")] | ||
IoError(#[from] io::Error), | ||
#[error("Pending Writes, please call finish_writes on any entries that have been modified")] | ||
PendingWrites, | ||
} | ||
|
||
pub type Result<T> = std::result::Result<T, FarcError>; | ||
|
||
impl Farc { | ||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { | ||
let mut reader = BinaryParser::from_file(path)?; | ||
Self::from_parser(&mut reader) | ||
} | ||
|
||
pub fn from_parser(reader: &mut BinaryParser) -> Result<Self> { | ||
reader.set_big_endian(true); | ||
let signature = reader.read_string(4)?; | ||
let header_size = reader.read_u32()? + 8; | ||
let entries = match signature.as_str() { | ||
"FARC" => unimplemented!(), | ||
"FArC" => { | ||
let mut entries = BTreeMap::new(); | ||
_ = reader.read_u32()?; | ||
|
||
while reader.position() < header_size as u64 { | ||
let name = reader.read_null_string()?; | ||
_ = reader.read_u32()?; | ||
let compressed_len = reader.read_u32()?; | ||
let uncompressed_len = reader.read_u32()?; | ||
|
||
reader.seek(SeekFrom::Current(-12))?; | ||
let data = reader | ||
.read_pointer(move |reader| reader.read_buf(compressed_len as usize))?; | ||
reader.seek(SeekFrom::Current(8))?; | ||
|
||
let data = if compressed_len != uncompressed_len { | ||
let mut cursor = Cursor::new(data); | ||
let mut decoder = gzip::Decoder::new(&mut cursor)?; | ||
let mut data = Vec::new(); | ||
decoder.read_to_end(&mut data)?; | ||
BinaryParser::from_buf(data) | ||
} else { | ||
BinaryParser::from_buf(data) | ||
}; | ||
|
||
entries.insert(name, data); | ||
} | ||
|
||
entries | ||
} | ||
"FArc" => { | ||
let mut entries = BTreeMap::new(); | ||
_ = reader.read_u32()?; | ||
|
||
while reader.position() < header_size as u64 { | ||
let name = reader.read_null_string()?; | ||
_ = reader.read_u32()?; | ||
let length = reader.read_u32()?; | ||
|
||
reader.seek(SeekFrom::Current(-8))?; | ||
let data = | ||
reader.read_pointer(move |reader| reader.read_parser(length as usize))?; | ||
reader.seek(SeekFrom::Current(4))?; | ||
|
||
entries.insert(name, data); | ||
} | ||
|
||
entries | ||
} | ||
_ => return Err(FarcError::Unsupported), | ||
}; | ||
reader.set_big_endian(false); | ||
Ok(Self { entries }) | ||
} | ||
|
||
pub fn write_file<P: AsRef<Path>>(&self, path: P, compress: bool) -> Result<()> { | ||
if self | ||
.entries | ||
.iter() | ||
.find(|(_, data)| data.pending_writes()) | ||
.is_some() | ||
{ | ||
return Err(FarcError::PendingWrites); | ||
} | ||
let mut file = File::create(path)?; | ||
let parser = self.write_parser(compress)?; | ||
file.write(&parser.to_buf_const().unwrap())?; | ||
Ok(()) | ||
} | ||
|
||
pub fn write_parser(&self, compress: bool) -> Result<BinaryParser> { | ||
if self | ||
.entries | ||
.iter() | ||
.find(|(_, data)| data.pending_writes()) | ||
.is_some() | ||
{ | ||
return Err(FarcError::PendingWrites); | ||
} | ||
|
||
let mut writer = BinaryParser::new(); | ||
writer.set_big_endian(true); | ||
if compress { | ||
writer.write_string("FArC")?; | ||
} else { | ||
writer.write_string("FArc")?; | ||
} | ||
let size = if compress { | ||
self.entries | ||
.iter() | ||
.map(|(name, _)| name.len() + 1 + 12) | ||
.fold(0, |acc, elem| acc + elem) | ||
} else { | ||
self.entries | ||
.iter() | ||
.map(|(name, _)| name.len() + 1 + 8) | ||
.fold(0, |acc, elem| acc + elem) | ||
}; | ||
|
||
writer.write_u32(size as u32)?; | ||
writer.write_u32(0)?; | ||
for (name, data) in &self.entries { | ||
if compress { | ||
let buf = data.to_buf_const().unwrap(); | ||
let mut encoder = gzip::Encoder::new(vec![])?; | ||
encoder.write(buf)?; | ||
let data = encoder.finish().into_result()?; | ||
let compressed_len = data.len() as u32; | ||
let uncomprssed_len = buf.len() as u32; | ||
|
||
writer.write_null_string(name)?; | ||
writer.write_pointer(move |writer| writer.write_buf(&data))?; | ||
writer.write_u32(compressed_len)?; | ||
writer.write_u32(uncomprssed_len)?; | ||
} else { | ||
let data = data.to_buf_const().unwrap().clone(); | ||
let len = data.len() as u32; | ||
|
||
writer.write_null_string(name)?; | ||
writer.write_pointer(move |writer| writer.write_buf(&data))?; | ||
writer.write_u32(len)?; | ||
}; | ||
} | ||
writer.finish_writes()?; | ||
|
||
Ok(writer) | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use crate::*; | ||
use pyo3::{exceptions::*, prelude::*}; | ||
use std::collections::BTreeMap; | ||
|
||
impl From<FarcError> for PyErr { | ||
fn from(value: FarcError) -> Self { | ||
match value { | ||
FarcError::BinaryParserError(binary_err) => { | ||
PyErr::new::<PyIOError, _>(binary_err.to_string()) | ||
} | ||
FarcError::Unsupported => PyErr::new::<PyException, _>("Unsupported file"), | ||
FarcError::IoError(io_err) => PyErr::new::<PyException, _>(io_err.to_string()), | ||
FarcError::PendingWrites => PyErr::new::<PyException, _>( | ||
"Cannot save file please call finish_writes on any entries that have been modified", | ||
), | ||
} | ||
} | ||
} | ||
|
||
#[pyfunction] | ||
fn read(path: String) -> PyResult<BTreeMap<String, Vec<u8>>> { | ||
Ok(Farc::from_file(path).map(|farc| { | ||
farc.entries | ||
.into_iter() | ||
.map(|(name, entry)| (name, entry.to_buf_const().unwrap().clone())) | ||
.collect() | ||
})?) | ||
} | ||
|
||
#[pyfunction] | ||
#[pyo3(signature = (entries, path, compress=true))] | ||
fn save(entries: BTreeMap<String, Vec<u8>>, path: String, compress: bool) -> PyResult<()> { | ||
let farc = Farc { | ||
entries: entries | ||
.into_iter() | ||
.map(|(name, entry)| (name, BinaryParser::from_buf(entry))) | ||
.collect(), | ||
}; | ||
farc.write_file(&path, compress)?; | ||
Ok(()) | ||
} | ||
|
||
#[pymodule] | ||
fn farc(_: Python<'_>, m: &PyModule) -> PyResult<()> { | ||
m.add_function(wrap_pyfunction!(read, m)?)?; | ||
m.add_function(wrap_pyfunction!(save, m)?)?; | ||
|
||
Ok(()) | ||
} |