mirror of
https://github.com/redstrate/Kawari.git
synced 2025-07-09 15:37:45 +00:00
Allow loading unpacked game files, add unpacking mode
This enables you to subsitute game files with your own more easily, along with running a server with only a limited amount of game data.
This commit is contained in:
parent
0291221dc7
commit
d16c2c6583
5 changed files with 98 additions and 12 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1060,7 +1060,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
|||
[[package]]
|
||||
name = "physis"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/redstrate/physis#c2eb47cca0300b9ecaae69473f03539a9e1e4662"
|
||||
source = "git+https://github.com/redstrate/physis#c0d3df99c36e1c3aedc8fd203192df556eddcc29"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitflags",
|
||||
|
|
3
USAGE.md
3
USAGE.md
|
@ -33,7 +33,8 @@ For the World server to function, Kawari needs to be built with `--features oodl
|
|||
Afterwards, create a `config.yaml` in the current directory. Currently the minimal config you need to run most services looks like this:
|
||||
|
||||
```yaml
|
||||
game_location: /path/to/gamedir/
|
||||
filesystem:
|
||||
game_path: "C:\Program Files (x86)\SquareEnix\FINAL FANTASY XIV - A Realm Reborn\game"
|
||||
```
|
||||
|
||||
More configuration options can be found in `config.rs`, such as changing the ports services run on. If you plan on just running it locally for yourself, you don't need to set anything else.
|
||||
|
|
|
@ -38,7 +38,7 @@ fn do_game_version_check(client_version_str: &str) -> bool {
|
|||
}
|
||||
|
||||
let game_exe_path = [
|
||||
config.game_location,
|
||||
config.filesystem.game_path,
|
||||
MAIN_SEPARATOR_STR.to_string(),
|
||||
exe_name.to_string(),
|
||||
]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use icarus::Action::ActionSheet;
|
||||
use icarus::Aetheryte::AetheryteSheet;
|
||||
use icarus::ClassJob::ClassJobSheet;
|
||||
|
@ -11,7 +13,10 @@ use icarus::{Tribe::TribeSheet, Warp::WarpSheet};
|
|||
use physis::common::{Language, Platform};
|
||||
use physis::exd::{EXD, ExcelRowKind};
|
||||
use physis::exh::EXH;
|
||||
use physis::resource::{SqPackResource, read_excel_sheet, read_excel_sheet_header};
|
||||
use physis::resource::{
|
||||
Resource, ResourceResolver, SqPackResource, UnpackedResource, read_excel_sheet,
|
||||
read_excel_sheet_header,
|
||||
};
|
||||
|
||||
use crate::{common::Attributes, config::get_config};
|
||||
|
||||
|
@ -19,7 +24,7 @@ use super::timestamp_secs;
|
|||
|
||||
/// Convenient methods built on top of Physis to access data relevant to the server
|
||||
pub struct GameData {
|
||||
pub resource: SqPackResource,
|
||||
pub resource: ResourceResolver,
|
||||
pub item_exh: EXH,
|
||||
pub item_pages: Vec<EXD>,
|
||||
pub classjob_exp_indexes: Vec<i8>,
|
||||
|
@ -57,20 +62,39 @@ impl GameData {
|
|||
pub fn new() -> Self {
|
||||
let config = get_config();
|
||||
|
||||
let mut game_data = SqPackResource::from_existing(Platform::Win32, &config.game_location);
|
||||
// setup resolvers
|
||||
let sqpack_resource = SqPackResourceSpy::from(
|
||||
SqPackResource::from_existing(Platform::Win32, &config.filesystem.game_path),
|
||||
&config.filesystem.unpack_path,
|
||||
);
|
||||
let mut resource_resolver = ResourceResolver::new();
|
||||
for path in config.filesystem.additional_search_paths {
|
||||
let unpacked_resource = UnpackedResource::from_existing(&path);
|
||||
resource_resolver.add_source(Box::new(unpacked_resource));
|
||||
}
|
||||
resource_resolver.add_source(Box::new(sqpack_resource));
|
||||
|
||||
let mut item_pages = Vec::new();
|
||||
|
||||
let item_exh = read_excel_sheet_header(&mut game_data, "Item").unwrap();
|
||||
let item_exh = read_excel_sheet_header(&mut resource_resolver, "Item")
|
||||
.expect("Failed to read Item EXH, does the file exist?");
|
||||
for (i, _) in item_exh.pages.iter().enumerate() {
|
||||
item_pages.push(
|
||||
read_excel_sheet(&mut game_data, "Item", &item_exh, Language::English, i).unwrap(),
|
||||
read_excel_sheet(
|
||||
&mut resource_resolver,
|
||||
"Item",
|
||||
&item_exh,
|
||||
Language::English,
|
||||
i,
|
||||
)
|
||||
.expect("Failed to read Item EXD, does the file exist?"),
|
||||
);
|
||||
}
|
||||
|
||||
let mut classjob_exp_indexes = Vec::new();
|
||||
|
||||
let sheet = ClassJobSheet::read_from(&mut game_data, Language::English).unwrap();
|
||||
let sheet = ClassJobSheet::read_from(&mut resource_resolver, Language::English)
|
||||
.expect("Failed to read ClassJobSheet, does the Excel files exist?");
|
||||
// TODO: ids are hardcoded until we have API in Icarus to do this
|
||||
for i in 0..43 {
|
||||
let row = sheet.get_row(i).unwrap();
|
||||
|
@ -79,7 +103,7 @@ impl GameData {
|
|||
}
|
||||
|
||||
Self {
|
||||
resource: game_data,
|
||||
resource: resource_resolver,
|
||||
item_exh,
|
||||
item_pages,
|
||||
classjob_exp_indexes,
|
||||
|
@ -406,3 +430,46 @@ pub enum TerritoryNameKind {
|
|||
Region,
|
||||
Place,
|
||||
}
|
||||
|
||||
/// Wrapper around SqPackResource to let us spy when it reads files
|
||||
struct SqPackResourceSpy {
|
||||
sqpack_resource: SqPackResource,
|
||||
output_directory: String,
|
||||
}
|
||||
|
||||
impl SqPackResourceSpy {
|
||||
pub fn from(sqpack_resource: SqPackResource, output_directory: &str) -> Self {
|
||||
Self {
|
||||
sqpack_resource,
|
||||
output_directory: output_directory.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resource for SqPackResourceSpy {
|
||||
fn read(&mut self, path: &str) -> Option<physis::ByteBuffer> {
|
||||
if let Some(buffer) = self.sqpack_resource.read(path) {
|
||||
let mut new_path = PathBuf::from(&self.output_directory);
|
||||
new_path.push(path.to_lowercase());
|
||||
|
||||
if !std::fs::exists(&new_path).unwrap_or_default() {
|
||||
// create directory if it doesn't exist'
|
||||
let parent_directory = new_path.parent().unwrap();
|
||||
if !std::fs::exists(parent_directory).unwrap_or_default() {
|
||||
std::fs::create_dir_all(parent_directory)
|
||||
.expect("Couldn't create directory for extraction?!");
|
||||
}
|
||||
|
||||
std::fs::write(new_path, &buffer).expect("Couldn't extract file!!");
|
||||
}
|
||||
|
||||
return Some(buffer);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn exists(&mut self, path: &str) -> bool {
|
||||
self.sqpack_resource.exists(path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -359,6 +359,24 @@ impl SaveDataBankConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// Configuration for the game filesystem.
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct FilesystemConfig {
|
||||
/// Path to the game directory. For example, "C:\Program Files (x86)\SquareEnix\FINAL FANTASY XIV - A Realm Reborn\game".
|
||||
#[serde(default)]
|
||||
pub game_path: String,
|
||||
|
||||
/// Additional search paths for *unpacked game files*.
|
||||
/// These are ordered from highest-to-lowest, these are always preferred over retail game files.
|
||||
#[serde(default)]
|
||||
pub additional_search_paths: Vec<String>,
|
||||
|
||||
/// Unpack used files to the specified directory.
|
||||
/// If the directory is not specified, Kawari won't save file contents.
|
||||
#[serde(default)]
|
||||
pub unpack_path: String,
|
||||
}
|
||||
|
||||
/// Global and all-encompassing config.
|
||||
/// Settings that affect all servers belong here.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -367,7 +385,7 @@ pub struct Config {
|
|||
pub supported_platforms: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub game_location: String,
|
||||
pub filesystem: FilesystemConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub admin: AdminConfig,
|
||||
|
@ -409,7 +427,7 @@ impl Default for Config {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
supported_platforms: default_supported_platforms(),
|
||||
game_location: String::new(),
|
||||
filesystem: FilesystemConfig::default(),
|
||||
admin: AdminConfig::default(),
|
||||
frontier: FrontierConfig::default(),
|
||||
lobby: LobbyConfig::default(),
|
||||
|
|
Loading…
Add table
Reference in a new issue