Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
BroGamer4256 committed Jan 20, 2024
0 parents commit edd69ea
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 0 deletions.
73 changes: 73 additions & 0 deletions .github/workflows/CI.yml
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
14 changes: 14 additions & 0 deletions Cargo.toml
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"] }
12 changes: 12 additions & 0 deletions pyproject.toml
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",
]
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hard_tabs = true
170 changes: 170 additions & 0 deletions src/lib.rs
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)
}
}
49 changes: 49 additions & 0 deletions src/py.rs
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(())
}

0 comments on commit edd69ea

Please sign in to comment.