# SHK Parser
A Rust library for parsing Stronghold Kingdoms attack formation files (`.cas` files).
## Features
- ๐ **Fast & Safe**: Written in Rust with zero-copy parsing where possible
- ๐ฏ **Type-Safe**: Strong typing for all unit types and abilities
- ๐ **Well-Documented**: Comprehensive API documentation and examples
- ๐งช **Well-Tested**: Extensive test coverage with real game files
- ๐ง **Easy to Use**: Simple, clean API with helpful error messages
- ๐ **Serialization**: Optional JSON support via serde
- ๐ป **CLI Tool**: Command-line interface with multiple output formats
- โก **Better Errors**: Detailed error messages with thiserror
## Optional Features
Enable additional functionality with cargo features:
```toml
[dependencies]
shk_parser = { version = "0.1.0", features = [
"serde",
"tsify",
"cli"
] }
```
- **`serde`**: Enables JSON serialization/deserialization
- **`tsify`**: Enables TypeScript type generation for WASM bindings (requires `serde`)
- **`cli`**: Enables the command-line interface
## Quick Start
Add this to your `Cargo.toml`:
```toml
[dependencies]
shk_parser = "0.1.0"
```
Parse a formation file:
```rust
use shk_parser::parse_formation_file;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let units = parse_formation_file("my_formation.cas")?;
println!("Formation contains {} units:", units.len());
for (i, unit) in units.iter().enumerate() {
println!(" {}: {}", i + 1, unit);
}
Ok(())
}
```
## Supported Units
The parser supports all Stronghold Kingdoms unit types:
### Basic Units
- **Archer**: Basic ranged units
- **Pikeman**: Basic melee units
### Advanced Units
- **Catapult**: Siege weapons with target coordinates
- **Captain**: Special command units with various abilities
### Captain Abilities
- **Delay**: Wait for a specified time before acting
- **Rallying Cry**: Rally nearby troops to boost morale
- **Arrow Volley**: Coordinate arrow attack at target location
- **Battle Cry**: Boost unit morale and effectiveness
- **Catapults Volley**: Coordinate catapult bombardment at target
## API Reference
### Core Functions
```rust
// Parse from raw bytes
fn parse_formation(data: &[u8]) -> Result<Vec<UnitRecord>, ParseError>
// Parse directly from file path
fn parse_formation_file<P: AsRef<Path>>(path: P) -> Result<Vec<UnitRecord>, ParseError>
```
### Types
```rust
pub struct UnitRecord {
pub position: Position,
pub unit_type: UnitType,
}
pub struct Position {
pub x: u8,
pub y: u8,
}
pub enum UnitType {
Archer,
Pikeman,
Catapult { target: Position },
Captain { ability: CaptainAbility, wait_time: u8 },
Unknown(u8),
}
pub enum CaptainAbility {
Delay,
RallyingCry,
ArrowVolley { target: Position },
BattleCry,
CatapultsVolley { target: Position },
Unknown(u8),
}
```
## Command Line Usage
With the `cli` feature enabled, you can use the binary tool:
```bash
# Parse all test files (default behavior)
cargo run --features cli --bin parse_formations
# Parse specific files
cargo run --features cli --bin parse_formations -- file1.cas file2.cas
# Verbose output with detailed parsing info
cargo run --features cli --bin parse_formations -- --verbose file.cas
# JSON output (requires serde feature)
cargo run --features cli,serde --bin parse_formations -- --format json file.cas
```
### CLI Options
```
A parser for Stronghold Kingdoms formation files (.cas)
Usage: parse_formations [OPTIONS] [FILES]...
Arguments:
[FILES]... Formation file(s) to parse
Options:
-f, --format <FORMAT> Output format [default: human]
-v, --verbose Show detailed unit information
-h, --help Print help
```
## File Format
`.cas` files use a simple binary format:
1. **Header** (4 bytes): Number of units as little-endian u32
2. **Unit Records** (variable length): One record per unit
### Record Formats
| Archer/Pikeman | 3 bytes | `x, y, unit_type` |
| Catapult | 5 bytes | `x, y, unit_type, target_x, target_y` |
| Captain (simple) | 4 bytes | `x, y, unit_type, wait_time` |
| Captain (with target) | 6 bytes | `x, y, unit_type, wait_time, target_x, target_y` |
## Examples
### Parse and Display All Units
```rust
use shk_parser::parse_formation_file;
let units = parse_formation_file("formation.cas")?;
for unit in &units {
match &unit.unit_type {
UnitType::Archer => println!("Archer at ({}, {})", unit.position.x, unit.position.y),
UnitType::Captain { ability, wait_time } => {
println!("Captain using {:?} for {}s at ({}, {})",
ability, wait_time, unit.position.x, unit.position.y);
}
_ => println!("{}", unit),
}
}
```
### Filter by Unit Type
```rust
use shk_parser::{parse_formation_file, UnitType};
let units = parse_formation_file("formation.cas")?;
// Find all captains
let captains: Vec<_> = units.iter()
.filter(|unit| matches!(unit.unit_type, UnitType::Captain { .. }))
.collect();
println!("Found {} captains", captains.len());
```
### Access Captain Abilities
```rust
use shk_parser::{parse_formation_file, UnitType, CaptainAbility};
let units = parse_formation_file("formation.cas")?;
for unit in &units {
if let UnitType::Captain { ability, wait_time } = &unit.unit_type {
match ability {
CaptainAbility::ArrowVolley { target } => {
println!("Arrow volley targeting ({}, {}) after {}s",
target.x, target.y, wait_time);
}
CaptainAbility::Delay => {
println!("Waiting for {}s", wait_time);
}
_ => println!("Captain ability: {:?}", ability),
}
}
}
```
## Error Handling
The library provides detailed error information:
```rust
use shk_parser::{parse_formation_file, ParseError};
match parse_formation_file("formation.cas") {
Ok(units) => println!("Parsed {} units", units.len()),
Err(ParseError::FileNotFound(path)) => eprintln!("File not found: {}", path),
Err(ParseError::InvalidFormat(msg)) => eprintln!("Invalid file format: {}", msg),
Err(ParseError::IoError(err)) => eprintln!("IO error: {}", err),
}
```
## Testing
Run the test suite:
```bash
cargo test
```
The library includes tests with real `.cas` files to ensure compatibility.
## License
Licensed under either of
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.