1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-19 14:26:51 +00:00

Add preliminary support for restoring character backups from Auracite

Not much is importable yet - pretty much just the character's name and
appearance.
This commit is contained in:
Joshua Goins 2025-03-30 18:51:11 -04:00
parent e4870762eb
commit 243d94c586
8 changed files with 277 additions and 15 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ config.json
*.db
config.yaml
src/opcodes.rs
backups/

151
Cargo.lock generated
View file

@ -17,6 +17,15 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "array-init"
version = "2.1.0"
@ -141,18 +150,49 @@ dependencies = [
"serde",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "bzip2"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
dependencies = [
"bzip2-sys",
]
[[package]]
name = "bzip2-sys"
version = "0.1.13+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "cc"
version = "1.2.17"
@ -168,12 +208,53 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "derive_arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "either"
version = "1.15.0"
@ -214,6 +295,16 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "flate2"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -431,6 +522,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-subscriber",
"zip",
]
[[package]]
@ -481,6 +573,18 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "lockfree-object-pool"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "lua-src"
version = "547.0.0"
@ -500,6 +604,16 @@ dependencies = [
"which",
]
[[package]]
name = "lzma-rs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
dependencies = [
"byteorder",
"crc",
]
[[package]]
name = "matchit"
version = "0.8.4"
@ -875,6 +989,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "slab"
version = "0.4.9"
@ -1184,8 +1304,39 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "zip"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27c03817464f64e23f6f37574b4fdc8cf65925b5bfd2b0f2aedf959791941f88"
dependencies = [
"arbitrary",
"bzip2",
"crc32fast",
"crossbeam-utils",
"flate2",
"indexmap",
"lzma-rs",
"memchr",
"zopfli",
]
[[package]]
name = "zlib-rs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"
[[package]]
name = "zopfli"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
dependencies = [
"bumpalo",
"crc32fast",
"lockfree-object-pool",
"log",
"once_cell",
"simd-adler32",
]

View file

@ -87,3 +87,6 @@ bitflags = { version = "1.3", default-features = false }
# For server-side scripting
mlua = { version = "0.10", features = ["lua51", "vendored", "send", "async"], default-features = false }
# For character backup decompression
zip = { version = "2.2", features = ["deflate", "lzma", "bzip2"], default-features = false }

View file

@ -72,6 +72,12 @@ In this example, lobby number 4 will replace the **Aether data center**, but the
Some other launchers (like XIVLauncher) will allow you to specify these extra arguments, but they will still authenticate to the retail servers. You can still connect to Kawari with this way, but **make sure to specify your own session ID, or your retail account's session ID will be sent to the lobby server**!
## Importing characters from retail
It's possible to import existing characters from the retail server using [Auracite](https://auracite.xiv.zone). Place the backup ZIP under `backups/` and the World server will import it into the database the next time it's started.
This feature is still a work-in-progress, and not all data is imported yet.
## Chat commands
### Debug commands

View file

@ -32,6 +32,39 @@ pub struct CustomizeData {
pub face_paint_color: u8,
}
impl From<physis::chardat::CustomizeData> for CustomizeData {
fn from(value: physis::chardat::CustomizeData) -> Self {
Self {
race: value.race as u8 + 1,
gender: value.gender as u8,
age: value.age,
height: value.height,
subrace: value.subrace as u8 + 1,
face: value.face,
hair: value.hair,
enable_highlights: value.enable_highlights as u8,
skin_tone: value.skin_tone,
right_eye_color: value.right_eye_color,
hair_tone: value.hair_tone,
highlights: value.highlights,
facial_features: value.facial_features,
facial_feature_color: value.facial_feature_color,
eyebrows: value.eyebrows,
left_eye_color: value.left_eye_color,
eyes: value.eyes,
nose: value.nose,
jaw: value.jaw,
mouth: value.mouth,
lips_tone_fur_pattern: value.lips_tone_fur_pattern,
race_feature_size: value.race_feature_size,
race_feature_type: value.race_feature_type,
bust: value.bust,
face_paint: value.face_paint,
face_paint_color: value.face_paint_color,
}
}
}
impl CustomizeData {
pub fn to_json(&self) -> Value {
json!([

View file

@ -32,12 +32,12 @@ impl CharaMake {
pub fn to_json(&self) -> String {
let content = json!([
self.customize.to_json(),
self.unk1,
self.guardian,
self.birth_month,
self.birth_day,
self.classjob_id,
self.unk2,
self.unk1.to_string(),
self.guardian.to_string(),
self.birth_month.to_string(),
self.birth_day.to_string(),
self.classjob_id.to_string(),
self.unk2.to_string(),
]);
let obj = json!({

View file

@ -591,13 +591,16 @@ pub async fn send_custom_world_packet(segment: CustomIpcSegment) -> Option<Custo
// read response
let mut buf = [0; 10024]; // TODO: this large buffer is just working around these packets not being compressed, but they really should be!
let n = stream.read(&mut buf).await.expect("Failed to read data!");
if n != 0 {
println!("Got {n} bytes of response!");
println!("Got {n} bytes of response!");
let (segments, _) = parse_packet::<CustomIpcSegment>(&buf[..n], &mut packet_state).await;
let (segments, _) = parse_packet::<CustomIpcSegment>(&buf[..n], &mut packet_state).await;
match &segments[0].segment_type {
SegmentType::CustomIpc { data } => Some(data.clone()),
_ => None,
return match &segments[0].segment_type {
SegmentType::CustomIpc { data } => Some(data.clone()),
_ => None,
};
}
None
}

View file

@ -1,9 +1,10 @@
use std::sync::Mutex;
use std::{io::Read, sync::Mutex};
use rusqlite::Connection;
use serde::Deserialize;
use crate::{
common::Position,
common::{CustomizeData, Position},
lobby::{
CharaMake, ClientSelectData, RemakeMode,
ipc::{CharacterDetails, CharacterFlag},
@ -46,9 +47,73 @@ impl WorldDatabase {
connection.execute(query, ()).unwrap();
}
Self {
let this = Self {
connection: Mutex::new(connection),
};
// Import any backups
// NOTE: This won't make sense when service accounts are a real thing, so the functionality will probably be moved
{
if let Ok(paths) = std::fs::read_dir("./backups") {
for path in paths {
let path = path.unwrap().path();
if path.extension().unwrap() == "zip" {
this.import_character(path.to_str().unwrap());
}
}
}
}
this
}
fn import_character(&self, path: &str) {
tracing::info!("Importing character backup {path}...");
let file = std::fs::File::open(path).unwrap();
let mut archive = zip::ZipArchive::new(file).unwrap();
#[derive(Deserialize)]
struct CharacterJson {
name: String,
}
let character: CharacterJson;
{
let mut character_file = archive.by_name("character.json").unwrap();
let mut json_string = String::new();
character_file.read_to_string(&mut json_string).unwrap();
character = serde_json::from_str(&json_string).unwrap();
}
if !self.check_is_name_free(&character.name) {
tracing::warn!("* Skipping since this character already exists.");
return;
}
let charsave_file = archive.by_name("FFXIV_CHARA_01.dat").unwrap();
let charsave_bytes: Vec<u8> = charsave_file.bytes().map(|x| x.unwrap()).collect();
let charsave = physis::chardat::CharacterData::from_existing(&charsave_bytes).unwrap();
let customize = CustomizeData::from(charsave.customize);
let chara_make = CharaMake {
customize,
unk1: 73,
guardian: 1, // TODO: extract these as well
birth_month: 1,
birth_day: 1,
classjob_id: 5,
unk2: 1,
};
// TODO: extract city-state
self.create_player_data(&character.name, &chara_make.to_json(), 2, 132);
tracing::info!("{} added to the world!", character.name);
}
pub fn find_player_data(&self, actor_id: u32) -> PlayerData {