1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-07-09 07:27:46 +00:00

Update Icarus, switch to the new Physis Resource system

While there isn't a functional difference in Kawari yet, this is
paving the way to allow loading unpacked game files.
This commit is contained in:
Joshua Goins 2025-07-08 21:53:24 -04:00
parent 9b03e2d7c2
commit 7b6605b018
5 changed files with 33 additions and 35 deletions

16
Cargo.lock generated
View file

@ -213,9 +213,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.28" version = "1.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c" checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -542,9 +542,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.14" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@ -567,7 +567,7 @@ dependencies = [
[[package]] [[package]]
name = "icarus" name = "icarus"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/redstrate/Icarus?branch=ver%2F2025.06.10.0000.0000#c6eb3b05ebe54b86c25d09dbc0bfc7b30f5cdadf" source = "git+https://github.com/redstrate/Icarus?branch=ver%2F2025.06.28.0000.0000#bcd980957786ac63f89c63e190b06358685ce762"
dependencies = [ dependencies = [
"physis", "physis",
] ]
@ -1060,7 +1060,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]] [[package]]
name = "physis" name = "physis"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/redstrate/physis#c602e6b28f06c178707bef5a9ea78fdc70c8f4a3" source = "git+https://github.com/redstrate/physis#c2eb47cca0300b9ecaae69473f03539a9e1e4662"
dependencies = [ dependencies = [
"binrw", "binrw",
"bitflags", "bitflags",
@ -1456,9 +1456,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.46.0" version = "1.46.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4" checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",

View file

@ -72,7 +72,7 @@ physis = { git = "https://github.com/redstrate/physis", default-features = false
bitflags = { version = "2.9", default-features = false } bitflags = { version = "2.9", default-features = false }
# excel sheet data # excel sheet data
icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.06.10.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race", "Aetheryte", "EquipSlotCategory", "Action", "WeatherRate", "PlaceName", "GilShopItem"], default-features = false } icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.06.28.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race", "Aetheryte", "EquipSlotCategory", "Action", "WeatherRate", "PlaceName", "GilShopItem"], default-features = false }
[target.'cfg(not(target_family = "wasm"))'.dependencies] [target.'cfg(not(target_family = "wasm"))'.dependencies]
# Used for the web servers # Used for the web servers

View file

@ -11,6 +11,7 @@ use icarus::{Tribe::TribeSheet, Warp::WarpSheet};
use physis::common::{Language, Platform}; use physis::common::{Language, Platform};
use physis::exd::{EXD, ExcelRowKind}; use physis::exd::{EXD, ExcelRowKind};
use physis::exh::EXH; use physis::exh::EXH;
use physis::resource::{SqPackResource, read_excel_sheet, read_excel_sheet_header};
use crate::{common::Attributes, config::get_config}; use crate::{common::Attributes, config::get_config};
@ -18,7 +19,7 @@ use super::timestamp_secs;
/// Convenient methods built on top of Physis to access data relevant to the server /// Convenient methods built on top of Physis to access data relevant to the server
pub struct GameData { pub struct GameData {
pub game_data: physis::gamedata::GameData, pub resource: SqPackResource,
pub item_exh: EXH, pub item_exh: EXH,
pub item_pages: Vec<EXD>, pub item_pages: Vec<EXD>,
pub classjob_exp_indexes: Vec<i8>, pub classjob_exp_indexes: Vec<i8>,
@ -56,17 +57,14 @@ impl GameData {
pub fn new() -> Self { pub fn new() -> Self {
let config = get_config(); let config = get_config();
let mut game_data = let mut game_data = SqPackResource::from_existing(Platform::Win32, &config.game_location);
physis::gamedata::GameData::from_existing(Platform::Win32, &config.game_location);
let mut item_pages = Vec::new(); let mut item_pages = Vec::new();
let item_exh = game_data.read_excel_sheet_header("Item").unwrap(); let item_exh = read_excel_sheet_header(&mut game_data, "Item").unwrap();
for (i, _) in item_exh.pages.iter().enumerate() { for (i, _) in item_exh.pages.iter().enumerate() {
item_pages.push( item_pages.push(
game_data read_excel_sheet(&mut game_data, "Item", &item_exh, Language::English, i).unwrap(),
.read_excel_sheet("Item", &item_exh, Language::English, i)
.unwrap(),
); );
} }
@ -81,7 +79,7 @@ impl GameData {
} }
Self { Self {
game_data, resource: game_data,
item_exh, item_exh,
item_pages, item_pages,
classjob_exp_indexes, classjob_exp_indexes,
@ -90,7 +88,7 @@ impl GameData {
/// Gets the world name from an id into the World Excel sheet. /// Gets the world name from an id into the World Excel sheet.
pub fn get_world_name(&mut self, world_id: u16) -> Option<String> { pub fn get_world_name(&mut self, world_id: u16) -> Option<String> {
let sheet = WorldSheet::read_from(&mut self.game_data, Language::None)?; let sheet = WorldSheet::read_from(&mut self.resource, Language::None)?;
let row = sheet.get_row(world_id as u32)?; let row = sheet.get_row(world_id as u32)?;
row.Name().into_string().cloned() row.Name().into_string().cloned()
@ -98,7 +96,7 @@ impl GameData {
/// Gets the starting city-state from a given class/job id. /// Gets the starting city-state from a given class/job id.
pub fn get_citystate(&mut self, classjob_id: u16) -> Option<u8> { pub fn get_citystate(&mut self, classjob_id: u16) -> Option<u8> {
let sheet = ClassJobSheet::read_from(&mut self.game_data, Language::English)?; let sheet = ClassJobSheet::read_from(&mut self.resource, Language::English)?;
let row = sheet.get_row(classjob_id as u32)?; let row = sheet.get_row(classjob_id as u32)?;
row.StartingTown().into_u8().copied() row.StartingTown().into_u8().copied()
@ -108,7 +106,7 @@ impl GameData {
// The Tribe Excel sheet only has deltas (e.g. 2 or -2) which are applied to a base 20 number... from somewhere // The Tribe Excel sheet only has deltas (e.g. 2 or -2) which are applied to a base 20 number... from somewhere
let base_stat = 20; let base_stat = 20;
let sheet = TribeSheet::read_from(&mut self.game_data, Language::English)?; let sheet = TribeSheet::read_from(&mut self.resource, Language::English)?;
let row = sheet.get_row(tribe_id as u32)?; let row = sheet.get_row(tribe_id as u32)?;
Some(Attributes { Some(Attributes {
@ -207,7 +205,7 @@ impl GameData {
/// Returns the pop range object id that's associated with the warp id /// Returns the pop range object id that's associated with the warp id
pub fn get_warp(&mut self, warp_id: u32) -> Option<(u32, u16)> { pub fn get_warp(&mut self, warp_id: u32) -> Option<(u32, u16)> {
let sheet = WarpSheet::read_from(&mut self.game_data, Language::English)?; let sheet = WarpSheet::read_from(&mut self.resource, Language::English)?;
let row = sheet.get_row(warp_id)?; let row = sheet.get_row(warp_id)?;
let pop_range_id = row.PopRange().into_u32()?; let pop_range_id = row.PopRange().into_u32()?;
@ -217,7 +215,7 @@ impl GameData {
} }
pub fn get_aetheryte(&mut self, aetheryte_id: u32) -> Option<(u32, u16)> { pub fn get_aetheryte(&mut self, aetheryte_id: u32) -> Option<(u32, u16)> {
let sheet = AetheryteSheet::read_from(&mut self.game_data, Language::English)?; let sheet = AetheryteSheet::read_from(&mut self.resource, Language::English)?;
let row = sheet.get_row(aetheryte_id)?; let row = sheet.get_row(aetheryte_id)?;
// TODO: just look in the level sheet? // TODO: just look in the level sheet?
@ -229,7 +227,7 @@ impl GameData {
// Retrieves a zone's internal name, place name or parent region name. // Retrieves a zone's internal name, place name or parent region name.
pub fn get_territory_name(&mut self, zone_id: u32, which: TerritoryNameKind) -> Option<String> { pub fn get_territory_name(&mut self, zone_id: u32, which: TerritoryNameKind) -> Option<String> {
let sheet = TerritoryTypeSheet::read_from(&mut self.game_data, Language::None)?; let sheet = TerritoryTypeSheet::read_from(&mut self.resource, Language::None)?;
let row = sheet.get_row(zone_id)?; let row = sheet.get_row(zone_id)?;
let offset = match which { let offset = match which {
@ -240,7 +238,7 @@ impl GameData {
TerritoryNameKind::Place => row.PlaceName().into_u16()?, TerritoryNameKind::Place => row.PlaceName().into_u16()?,
}; };
let sheet = PlaceNameSheet::read_from(&mut self.game_data, Language::English)?; let sheet = PlaceNameSheet::read_from(&mut self.resource, Language::English)?;
let row = sheet.get_row(*offset as u32)?; let row = sheet.get_row(*offset as u32)?;
let value = row.Name().into_string()?; let value = row.Name().into_string()?;
@ -250,7 +248,7 @@ impl GameData {
/// Turn an equip slot category id into a slot for the equipped inventory /// Turn an equip slot category id into a slot for the equipped inventory
pub fn get_equipslot_category(&mut self, equipslot_id: u8) -> Option<u16> { pub fn get_equipslot_category(&mut self, equipslot_id: u8) -> Option<u16> {
let sheet = EquipSlotCategorySheet::read_from(&mut self.game_data, Language::None)?; let sheet = EquipSlotCategorySheet::read_from(&mut self.resource, Language::None)?;
let row = sheet.get_row(equipslot_id as u32)?; let row = sheet.get_row(equipslot_id as u32)?;
let main_hand = row.MainHand().into_i8()?; let main_hand = row.MainHand().into_i8()?;
@ -322,7 +320,7 @@ impl GameData {
} }
pub fn get_casttime(&mut self, action_id: u32) -> Option<u16> { pub fn get_casttime(&mut self, action_id: u32) -> Option<u16> {
let sheet = ActionSheet::read_from(&mut self.game_data, Language::English)?; let sheet = ActionSheet::read_from(&mut self.resource, Language::English)?;
let row = sheet.get_row(action_id)?; let row = sheet.get_row(action_id)?;
row.Cast100ms().into_u16().copied() row.Cast100ms().into_u16().copied()
@ -331,7 +329,7 @@ impl GameData {
/// Calculates the current weather at the current time /// Calculates the current weather at the current time
// TODO: instead allow targetting a specific time to calculate forcecasts // TODO: instead allow targetting a specific time to calculate forcecasts
pub fn get_weather_rate(&mut self, weather_rate_id: u32) -> Option<i32> { pub fn get_weather_rate(&mut self, weather_rate_id: u32) -> Option<i32> {
let sheet = WeatherRateSheet::read_from(&mut self.game_data, Language::None)?; let sheet = WeatherRateSheet::read_from(&mut self.resource, Language::None)?;
let row = sheet.get_row(weather_rate_id)?; let row = sheet.get_row(weather_rate_id)?;
let target = Self::calculate_target(); let target = Self::calculate_target();
@ -379,7 +377,7 @@ impl GameData {
/// Gets the current weather for the given zone id /// Gets the current weather for the given zone id
pub fn get_weather(&mut self, zone_id: u32) -> Option<i32> { pub fn get_weather(&mut self, zone_id: u32) -> Option<i32> {
let sheet = TerritoryTypeSheet::read_from(&mut self.game_data, Language::None)?; let sheet = TerritoryTypeSheet::read_from(&mut self.resource, Language::None)?;
let row = sheet.get_row(zone_id)?; let row = sheet.get_row(zone_id)?;
let weather_rate_id = row.WeatherRate().into_u8()?; let weather_rate_id = row.WeatherRate().into_u8()?;
@ -394,7 +392,7 @@ impl GameData {
/// Gets the item and its cost from the specified shop. /// Gets the item and its cost from the specified shop.
pub fn get_gilshop_item(&mut self, gilshop_id: u32, index: u16) -> Option<ItemInfo> { pub fn get_gilshop_item(&mut self, gilshop_id: u32, index: u16) -> Option<ItemInfo> {
let sheet = GilShopItemSheet::read_from(&mut self.game_data, Language::None)?; let sheet = GilShopItemSheet::read_from(&mut self.resource, Language::None)?;
let row = sheet.get_subrow(gilshop_id, index)?; let row = sheet.get_subrow(gilshop_id, index)?;
let item_id = row.Item().into_i32()?; let item_id = row.Item().into_i32()?;

View file

@ -133,7 +133,7 @@ impl<'a> Iterator for InventoryIterator<'a> {
impl Inventory { impl Inventory {
/// Equip the starting items for a given classjob /// Equip the starting items for a given classjob
pub fn equip_classjob_items(&mut self, classjob_id: u16, game_data: &mut GameData) { pub fn equip_classjob_items(&mut self, classjob_id: u16, game_data: &mut GameData) {
let sheet = ClassJobSheet::read_from(&mut game_data.game_data, Language::English).unwrap(); let sheet = ClassJobSheet::read_from(&mut game_data.resource, Language::English).unwrap();
let row = sheet.get_row(classjob_id as u32).unwrap(); let row = sheet.get_row(classjob_id as u32).unwrap();
self.equipped.main_hand = self.equipped.main_hand =
@ -149,7 +149,7 @@ impl Inventory {
/// Equip the starting items for a given race /// Equip the starting items for a given race
pub fn equip_racial_items(&mut self, race_id: u8, gender: u8, game_data: &mut GameData) { pub fn equip_racial_items(&mut self, race_id: u8, gender: u8, game_data: &mut GameData) {
let sheet = RaceSheet::read_from(&mut game_data.game_data, Language::English).unwrap(); let sheet = RaceSheet::read_from(&mut game_data.resource, Language::English).unwrap();
let row = sheet.get_row(race_id as u32).unwrap(); let row = sheet.get_row(race_id as u32).unwrap();
if gender == 0 { if gender == 0 {

View file

@ -5,6 +5,7 @@ use physis::{
ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject, ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject,
}, },
lvb::Lvb, lvb::Lvb,
resource::Resource,
}; };
use crate::common::{GameData, TerritoryNameKind}; use crate::common::{GameData, TerritoryNameKind};
@ -27,8 +28,7 @@ impl Zone {
..Default::default() ..Default::default()
}; };
let sheet = let sheet = TerritoryTypeSheet::read_from(&mut game_data.resource, Language::None).unwrap();
TerritoryTypeSheet::read_from(&mut game_data.game_data, Language::None).unwrap();
let Some(row) = sheet.get_row(id as u32) else { let Some(row) = sheet.get_row(id as u32) else {
tracing::warn!("Invalid zone id {id}, allowing anyway..."); tracing::warn!("Invalid zone id {id}, allowing anyway...");
return zone; return zone;
@ -40,11 +40,11 @@ impl Zone {
let bg_path = row.Bg().into_string().unwrap(); let bg_path = row.Bg().into_string().unwrap();
let path = format!("bg/{}.lvb", &bg_path); let path = format!("bg/{}.lvb", &bg_path);
let lgb_file = game_data.game_data.extract(&path).unwrap(); let lgb_file = game_data.resource.read(&path).unwrap();
let lgb = Lvb::from_existing(&lgb_file).unwrap(); let lgb = Lvb::from_existing(&lgb_file).unwrap();
let mut load_lgb = |path: &str| -> Option<LayerGroup> { let mut load_lgb = |path: &str| -> Option<LayerGroup> {
let lgb_file = game_data.game_data.extract(path)?; let lgb_file = game_data.resource.read(path)?;
tracing::info!("Loading {path}"); tracing::info!("Loading {path}");
let lgb = LayerGroup::from_existing(&lgb_file); let lgb = LayerGroup::from_existing(&lgb_file);
if lgb.is_none() { if lgb.is_none() {