A Rust implementation of unp4k - a tool for extracting and modifying Star Citizen .p4k files.
Note
The functionality to modify/create P4K is experimental; it is only used for testing local tools and cannot be verified by the game.
This project is organized as a Cargo workspace:
unp4k_rs/
├── src/ # Core library (unp4k)
│ ├── lib.rs
│ ├── p4k.rs # P4K archive reading
│ ├── p4k_writer.rs # P4K archive writing
│ ├── cryxml.rs # CryXML format conversion
│ ├── dataforge/ # DataForge/DCB format parser
│ └── ...
└── crates/
├── unp4k-cli/ # Command-line tool (unp4k-cli)
│ └── src/main.rs
└── dataforge-mcp/ # MCP server for DataForge (optional)
└── src/
├── lib.rs
├── server.rs # HTTP server with Streamable HTTP transport
└── tools.rs # MCP tool definitions
Download the latest release from GitHub releases.
# Install CLI tool
cargo install --git https://github.com/StarCitizenToolBox/unp4k_rs.git unp4k-cligit clone https://github.com/StarCitizenToolBox/unp4k_rs.git
cd unp4k_rs
cargo install --path crates/unp4k-cliunp4k --help- 📦 Open and extract Star Citizen
.p4karchives - ✏️ Create, modify and patch
.p4karchives - 🔐 AES-128-CBC encryption/decryption support
- 🗜️ Support for STORE, DEFLATE, and ZSTD compression
- 📝 CryXML binary format to standard XML conversion
- 📊 DataForge/DCB binary format to XML conversion
- 📦 Socpak (
.socpak) file extraction support - 🔍 Full-text search across DCB records
- 💻 Cross-platform (Windows, macOS, Linux)
# Extract all files
unp4k Data.p4k
# Extract files matching a pattern
unp4k Data.p4k "*.xml"
unp4k Data.p4k "Data/Libs/*"# List all files
unp4k list Data.p4k
# List files matching a pattern
unp4k list Data.p4k "*.dcb"# Extract to current directory
unp4k extract Data.p4k
# Extract with pattern
unp4k extract Data.p4k "*.xml" -o ./output
# Extract and convert CryXML to standard XML
unp4k extract Data.p4k "*.xml" --convert-xml
# Extract with automatic socpak virtual directory mapping
unp4k extract Data.p4k --socpak-to-dir
# This enables transparent socpak handling:
# - .socpak files are mapped as .unsocpak/ virtual directories in memory
# - You can access files inside .socpak archives through virtual paths
# - Original .socpak files remain accessible
# Example: Data/Shaders/file.socpak becomes:
# - Data/Shaders/file.socpak (original file)
# - Data/Shaders/file.unsocpak/ (virtual directory with extracted contents)When opening a P4K with socpak_to_dir option enabled:
# .socpak files are automatically processed at open time
# Virtual directory structure is created in memoryHow it works:
.socpakfiles are standard ZIP archives used by Star Citizen- With
socpak_to_dir: true,.socpakfiles are transparently mapped as virtual directories:- Original:
path/to/file.socpak - Virtual:
path/to/file.unsocpak/(contains extracted files) - Both are accessible through the normal P4K API
- Original:
- Lazy Loading: Files inside
.socpakare NOT extracted at P4K open time- Virtual entries are created based on ZIP file listings
- Actual extraction happens only when you access a file
- Reduces memory usage and improves P4K open speed
- No actual file system writes occur at open time
- When extracting to disk, virtual directory structure is preserved
Example:
# Original P4K structure:
# Data/Shaders/effects.socpak
# With --socpak-to-dir:
# Data/Shaders/effects.socpak (original file)
# Data/Shaders/effects.unsocpak/shader1.dxbc (virtual, extracted on first access)
# Data/Shaders/effects.unsocpak/shader2.dxbc (virtual, extracted on first access)You can extract .socpak files directly without opening a P4K:
# Extract a single .socpak file
unp4k unsocpak file.socpak
# Extract to a specific directory
unp4k unsocpak file.socpak -o ./output
# Extract all .socpak files in a directory
unp4k unsocpak ./directory
# Recursively search and extract all .socpak files
unp4k unsocpak ./directory -r
# Overwrite existing files
unp4k unsocpak file.socpak -wunp4k info Data.p4k# Create a new P4K from a directory
unp4k pack output.p4k ./my_files
# With custom compression
unp4k pack output.p4k ./my_files -c zstd
unp4k pack output.p4k ./my_files -c deflate
unp4k pack output.p4k ./my_files -c store
# Without encryption
unp4k pack output.p4k ./my_files -e false
# With base path prefix
unp4k pack output.p4k ./my_files -b Data/MyMod# Patch a P4K with files from a directory
# Files in patch directory will replace matching files in the P4K
unp4k patch Data.p4k ./patches
# With base path prefix
unp4k patch Data.p4k ./patches -b Data/Localization# Add a file to the archive
unp4k add Data.p4k myfile.xml
# Add with custom archive path
unp4k add Data.p4k myfile.xml -a Data/Config/myfile.xml# Replace a file in the archive (keeps original compression settings)
unp4k replace Data.p4k myfile.xml Data/Config/myfile.xml# Delete files matching patterns
unp4k delete Data.p4k "*.tmp" "*.bak"# Show DCB file info
unp4k dcb Game.dcb --info
# Convert to separate XML files (like upstream unp4k)
unp4k dcb Game.dcb
# Convert to a single merged XML file
unp4k dcb Game.dcb --merge
# Specify output directory
unp4k dcb Game.dcb -o ./outputThe MCP (Model Context Protocol) server allows AI assistants to query and search DataForge/DCB data:
# Start MCP server on default port (3721)
unp4k mcp Game.dcb
# Start on custom port
unp4k mcp Game.dcb -p 8080Available MCP Tools:
| Tool | Description |
|---|---|
get_stats |
Get DataForge statistics and metadata |
list_paths |
List record paths with keyword/regex filtering and pagination |
get_content |
Get XML content of a specific record |
batch_get_content |
Get XML content for multiple records (max 10) |
search_in_paths |
Two-level filtering: path keyword + content keyword |
full_text_search |
Search across all record paths and XML content |
suggest_paths |
Path auto-completion based on prefix |
list_directories |
Explore record hierarchy at different depths |
Note: DataForge files can contain 50000+ records (GB-level data). Always use
get_statsfirst to understand data size, and usecount_only=truewith pagination for large queries.
Add to your Cargo.toml:
[dependencies]
unp4k = { git = "https://github.com/StarCitizenToolBox/unp4k_rs.git" }use unp4k::{P4kFile, CryXmlReader};
fn main() -> anyhow::Result<()> {
// Open the archive
let mut p4k = P4kFile::open("Data.p4k")?;
// List entries
for entry in p4k.entries() {
println!("{}: {} bytes", entry.name, entry.uncompressed_size);
}
// Extract a file
let data = p4k.extract("Data/Libs/Config/defaultProfile.xml")?;
// Convert CryXML to standard XML
if CryXmlReader::is_cryxml(&data) {
let xml = CryXmlReader::parse(&data)?;
println!("{}", xml);
}
Ok(())
}use unp4k::{P4kFile, P4kOpenOptions, EntrySource};
fn main() -> anyhow::Result<()> {
// Open P4K with socpak virtual directory mapping enabled
let options = P4kOpenOptions {
socpak_to_dir: true,
};
let mut p4k = P4kFile::open_with_options("Data.p4k", options)?;
// List all entries (includes both .socpak files and virtual .unsocpak/ directories)
for entry in p4k.entries() {
match &entry.source {
EntrySource::P4k => {
println!("P4K: {}", entry.name);
}
EntrySource::Socpak { parent_socpak, inner_path } => {
println!("Virtual: {} (from {})", entry.name, parent_socpak);
}
}
}
// Access files inside .socpak archives transparently
// If "Data/Shaders/effects.socpak" contains "shader.dxbc", you can access it as:
let shader_data = p4k.extract("Data/Shaders/effects.unsocpak/shader.dxbc")?;
// Or access the original .socpak file:
let socpak_data = p4k.extract("Data/Shaders/effects.socpak")?;
Ok(())
}use unp4k::{P4kWriter, P4kWriteEntry, P4kWriteOptions, P4kModifier, CompressionMethod};
fn create_archive() -> anyhow::Result<()> {
// Create a new P4K
let mut writer = P4kWriter::create("my_archive.p4k")?;
// Add entries
let entry = P4kWriteEntry::new("Data/test.xml", b"<root/>".to_vec());
writer.add_entry(entry)?;
// Or add from file
writer.add_file("local_file.txt", "Data/remote_file.txt")?;
writer.finish()?;
Ok(())
}
fn modify_archive() -> anyhow::Result<()> {
// Open existing P4K for modification
let mut modifier = P4kModifier::open("Data.p4k")?;
// Add/replace a file
modifier.add(P4kWriteEntry::new("Data/new_file.xml", b"<data/>".to_vec()));
// Delete a file
modifier.delete("Data/old_file.xml");
// Save to new file
modifier.save("Data_modified.p4k")?;
Ok(())
}use unp4k::dataforge::{DataForge, search_records};
fn main() -> anyhow::Result<()> {
let data = std::fs::read("Game.dcb")?;
let df = DataForge::parse(&data)?;
// List record paths
for path in df.record_paths().take(10) {
println!("{}", path);
}
// Convert a record to XML
let xml = df.record_to_xml("path/to/record", true)?;
println!("{}", xml);
// Full-text search across all records
let results = search_records(&df, "vehicle");
for result in results {
println!("Found in: {} ({} matches)", result.path, result.matches.len());
}
Ok(())
}The .p4k files are encrypted ZIP archives with custom features:
- Encryption: AES-128-CBC with a known public key (same as CryEngine)
- Compression Methods:
STORE(0) - No compressionDEFLATE(8) - Standard ZIP compressionZSTD(100) - Zstandard compression (custom extension)
- CryXML: Binary XML format used for many game configuration files
- Original unp4k by dolkensp
- Star Citizen by Cloud Imperium Games
GNU GENERAL PUBLIC LICENSE Version 3