Skip to content

Commit

Permalink
switch to writing completely in memory
Browse files Browse the repository at this point in the history
  • Loading branch information
bananaturtlesandwich committed Aug 28, 2023
1 parent 3fd110c commit 73c1ecb
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 180 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ edition = "2021"

[dependencies]
eframe = { version = "0.22", default-features = false, features = ["glow", "persistence"] }
unreal_asset = { git = "https://github.com/astrotechies/unrealmodding", package = "unreal_asset", rev = "dfcc3e2"}
unreal_asset = { git = "https://github.com/astrotechies/unrealmodding", package = "unreal_asset", rev = "186842c"}
repak = { git = "https://github.com/bananaturtlesandwich/repak", branch = "features-redo", default-features = false }
walkdir = "2.3"
strum = { version = "0.25", features = ["derive"] }
rand = "0.8"
egui-modal = "0.2"
Expand Down
45 changes: 25 additions & 20 deletions src/io.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
use std::{fs::File, io::Cursor, path::Path};
use unreal_asset::{engine_version::EngineVersion::VER_UE4_25, error::Error, Asset};
use unreal_asset::{engine_version::EngineVersion::VER_UE4_25, Asset};

pub fn open(file: impl AsRef<Path>) -> Result<Asset<File>, Error> {
Asset::new(
File::open(&file)?,
File::open(file.as_ref().with_extension("uexp")).ok(),
pub fn open(asset: Vec<u8>, bulk: Vec<u8>) -> Result<super::Asset<Vec<u8>>, crate::writing::Error> {
Ok(Asset::new(
std::io::Cursor::new(asset),
Some(std::io::Cursor::new(bulk)),
VER_UE4_25,
None,
)
)?)
}

pub fn open_from_bytes<'chain>(
pub fn open_slice<'chain>(
asset: &'chain [u8],
bulk: &'chain [u8],
) -> Result<Asset<Cursor<&'chain [u8]>>, Error> {
Asset::new(
Cursor::new(asset),
Some(Cursor::new(bulk)),
) -> Result<super::Asset<&'chain [u8]>, crate::writing::Error> {
Ok(Asset::new(
std::io::Cursor::new(asset),
Some(std::io::Cursor::new(bulk)),
VER_UE4_25,
None,
)
)?)
}

pub fn save<C: std::io::Read + std::io::Seek>(
asset: &mut Asset<C>,
path: impl AsRef<Path>,
) -> Result<(), Error> {
asset.write_data(
&mut File::create(&path)?,
Some(&mut File::create(path.as_ref().with_extension("uexp"))?),
)
map: &mut Asset<C>,
mod_pak: &super::Mod,
path: &str,
) -> Result<(), crate::writing::Error> {
let mut asset = std::io::Cursor::new(vec![]);
let mut bulk = std::io::Cursor::new(vec![]);
map.write_data(&mut asset, Some(&mut bulk))?;
mod_pak.lock()?.write_file(path, &mut asset)?;
mod_pak.lock()?.write_file(
&path.replace(".uasset", ".uexp").replace(".umap", ".uexp"),
&mut bulk,
)?;
Ok(())
}
7 changes: 4 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ mod logic;
mod map;
mod writing;

type Mod = std::sync::Arc<std::sync::Mutex<repak::PakWriter<std::fs::File>>>;
type Asset<T> = unreal_asset::Asset<std::io::Cursor<T>>;

pub struct Rando {
notifs: egui_modal::Modal,
pak: std::path::PathBuf,
Expand Down Expand Up @@ -260,13 +263,11 @@ impl eframe::App for Rando {
)
.clicked()
{
std::fs::remove_dir_all(self.pak.join("rando_p")).unwrap_or_default();
notify!(
self,
logic::randomise(self),
"seed has been generated, written and installed"
);
std::fs::remove_dir_all(self.pak.join("rando_p")).unwrap_or_default();
)
}
});
self.notifs.show_dialog();
Expand Down
11 changes: 7 additions & 4 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,23 @@ fn get_actor_exports<C: std::io::Seek + std::io::Read>(
}

/// creates and assigns a unique name
fn give_unique_name<C: std::io::Seek + std::io::Read>(orig: &mut FName, asset: &mut Asset<C>) {
fn give_unique_name<C: std::io::Seek + std::io::Read>(
orig: &mut FName,
asset: &mut Asset<C>,
) -> Result<(), crate::writing::Error> {
// for the cases where the number is unnecessary

if orig
.get_content(|name| asset.search_name_reference(name))
.is_none()
{
*orig = orig.get_content(|name| asset.add_fname(name));
return;
return Ok(());
}
let mut name = orig.get_owned_content();
let mut counter: u16 = match name.rfind(|ch: char| ch.to_digit(10).is_none()) {
Some(index) if index != name.len() - 1 => {
name.drain(index + 1..).collect::<String>().parse().unwrap()
name.drain(index + 1..).collect::<String>().parse()?
}
_ => 1,
};
Expand All @@ -76,7 +79,7 @@ fn give_unique_name<C: std::io::Seek + std::io::Read>(orig: &mut FName, asset: &
{
counter += 1;
}
*orig = asset.add_fname(&format!("{name}{counter}"))
Ok(*orig = asset.add_fname(&format!("{name}{counter}")))
}

/// on all possible export references
Expand Down
5 changes: 3 additions & 2 deletions src/map/transplant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ pub fn transplant<C: std::io::Seek + std::io::Read, D: std::io::Seek + std::io::
index: usize,
recipient: &mut Asset<C>,
donor: &Asset<D>,
) {
) -> Result<(), crate::writing::Error> {
let mut children = super::get_actor_exports(index, donor, recipient.asset_data.exports.len());

// make sure the actor has a unique object name
super::give_unique_name(
&mut children[0].get_base_export_mut().object_name,
recipient,
);
)?;

let actor_ref = PackageIndex::new(recipient.asset_data.exports.len() as i32 + 1);
// add the actor to persistent level
Expand Down Expand Up @@ -132,4 +132,5 @@ pub fn transplant<C: std::io::Seek + std::io::Read, D: std::io::Seek + std::io::
i += 1;
}
recipient.imports.append(&mut imports);
Ok(())
}
168 changes: 83 additions & 85 deletions src/writing.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::logic::*;
use super::Mod;
use crate::{io::*, map::*};
use unreal_asset::{
cast,
Expand All @@ -21,13 +22,49 @@ pub enum Error {
Repak(#[from] repak::Error),
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("parse: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("locked poisoned name vec")]
VecPoison,
#[error("locked poisoned writer")]
WriterPoison,
#[error("extracted poisoned writer")]
InnerMutex(#[from] std::sync::PoisonError<repak::PakWriter<std::fs::File>>),
#[error("some threads are still using writer")]
InnerArc,
#[error("failed to strip prefix when writing file to pak")]
Strip(#[from] std::path::StripPrefixError),
#[error("thread failed to complete")]
Thread,
#[error("data was not as expected - you may have an older version of the game")]
Assumption,
}

pub const MOD: &str = "rando_p/Blue Fire/Content/BlueFire";
macro_rules! stub {
($type: ty, $variant: ident) => {
impl From<$type> for Error {
fn from(_: $type) -> Self {
Self::$variant
}
}
};
}

stub!(
std::sync::Arc<std::sync::Mutex<repak::PakWriter<std::fs::File>>>,
InnerArc
);
stub!(
std::sync::PoisonError<std::sync::MutexGuard<'_, repak::PakWriter<std::fs::File>>>,
WriterPoison
);
stub!(
std::sync::PoisonError<std::sync::MutexGuard<'_, Vec<String>>>,
VecPoison
);
stub!(Box<dyn std::any::Any + Send + 'static>, Thread);

pub const MOD: &str = "Blue Fire/Content/BlueFire/";

const SAVEGAME: &str = "Player/Logic/FrameWork/BlueFireSaveGame.uasset";

Expand All @@ -37,25 +74,20 @@ fn extract(
app: &crate::Rando,
pak: &repak::PakReader,
path: &str,
) -> Result<(unreal_asset::Asset<std::fs::File>, std::path::PathBuf), Error> {
let loc = app.pak.join(MOD).join(path);
if path != "Maps/World/A02_ArcaneTunnels/A02_EastArcane.umap" {
std::fs::create_dir_all(loc.parent().expect("is a file"))?;
pak.read_file(
) -> Result<super::Asset<Vec<u8>>, Error> {
open(
pak.get(
&format!("Blue Fire/Content/BlueFire/{path}"),
&mut app.pak()?,
&mut std::fs::File::create(&loc)?,
)?;
pak.read_file(
)?,
pak.get(
&format!(
"Blue Fire/Content/BlueFire/{}",
path.replace(".uasset", ".uexp").replace(".umap", ".uexp")
),
&mut app.pak()?,
&mut std::fs::File::create(loc.with_extension("uexp"))?,
)?;
}
Ok((open(&loc)?, loc))
)?,
)
}

fn byte_property(
Expand Down Expand Up @@ -110,83 +142,51 @@ fn set_byte(
pub fn write(data: Data, app: &crate::Rando) -> Result<(), Error> {
let mut sync = app.pak()?;
let pak = repak::PakReader::new(&mut sync, repak::Version::V9)?;
// correct the shenanigans in spirit hunter
let loc = app
.pak
.join(MOD)
.join("Maps/World/A02_ArcaneTunnels/A02_EastArcane.umap");
std::fs::create_dir_all(loc.parent().expect("is a file"))?;
pak.read_file(
"Blue Fire/Content/BlueFire/Maps/World/A02_ArcaneTunnels/A02_EastArcane.umap",
&mut sync,
&mut std::fs::File::create(&loc)?,
)?;
pak.read_file(
"Blue Fire/Content/BlueFire/Maps/World/A02_ArcaneTunnels/A02_EastArcane.uexp",
&mut sync,
&mut std::fs::File::create(loc.with_extension("uexp"))?,
)?;
let mut spirit_hunter = open(&loc)?;
spirit_hunter.asset_data.exports[440]
.get_base_export_mut()
.object_name = spirit_hunter.add_fname("Pickup_A02_SRF2");
save(&mut spirit_hunter, &loc)?;
let mod_pak = std::sync::Arc::new(std::sync::Mutex::new(repak::PakWriter::new(
std::fs::File::create(app.pak.join("rando_p.pak"))?,
repak::Version::V9,
"../../../".to_string(),
None,
)));
std::thread::scope(|thread| -> Result<(), Error> {
for thread in [
thread.spawn(|| overworld::write(data.overworld, app, &pak)),
thread.spawn(|| cutscenes::write(data.cutscenes, app, &pak)),
thread.spawn(|| savegames::write(data.savegames, data.shop_emotes, app, &pak)),
thread.spawn(|| specific::write(data.cases, app, &pak)),
thread.spawn(|| overworld::write(data.overworld, app, &pak, &mod_pak)),
thread.spawn(|| cutscenes::write(data.cutscenes, app, &pak, &mod_pak)),
thread
.spawn(|| savegames::write(data.savegames, data.shop_emotes, app, &pak, &mod_pak)),
thread.spawn(|| specific::write(data.cases, app, &pak, &mod_pak)),
] {
thread.join().unwrap()?
thread.join()??
}
Ok(())
})?;
let mut mod_pak = Mod::try_unwrap(mod_pak)?.into_inner()?;
// change the logo so people know it worked
let logo = app.pak.join(MOD).join("HUD/Menu/Blue-Fire-Logo.uasset");
std::fs::create_dir_all(logo.parent().expect("is a file"))?;
std::fs::write(&logo, include_bytes!("blueprints/logo.uasset"))?;
std::fs::write(
logo.with_extension("uexp"),
include_bytes!("blueprints/logo.uexp"),
let logo = MOD.to_string() + "HUD/Menu/Blue-Fire-Logo.uasset";
mod_pak.write_file(
&logo,
&mut std::io::Cursor::new(include_bytes!("blueprints/logo.uasset")),
)?;
let mut pak = repak::PakWriter::new(
std::fs::File::create(app.pak.join("rando_p.pak"))?,
repak::Version::V9,
"../../../".to_string(),
None,
);
for file in walkdir::WalkDir::new(app.pak.join("rando_p"))
.into_iter()
.filter_map(|entry| entry.ok().filter(|file| file.path().is_file()))
{
pak.write_file(
&file
.path()
.strip_prefix(&app.pak.join("rando_p"))?
.to_str()
.unwrap_or_default()
.replace("\\", "/"),
&mut std::fs::File::open(file.path())?,
)?
}
pak.write_index()?;
mod_pak.write_file(
&logo.replace(".uasset", ".uexp"),
&mut std::io::Cursor::new(include_bytes!("blueprints/logo.uexp")),
)?;
mod_pak.write_index()?;
Ok(())
}

fn create_hook<C: std::io::Read + std::io::Seek>(
app: &crate::Rando,
pak: &repak::PakReader,
get_hook: impl Fn(&std::path::PathBuf) -> Result<unreal_asset::Asset<C>, Error>,
mod_pak: &Mod,
hook: &mut unreal_asset::Asset<C>,
drop: &Drop,
cutscene: &str,
index: usize,
) -> Result<(), Error> {
let mut loc = app.pak.join(MOD).join("Libraries");
std::fs::create_dir_all(&loc)?;
let mut loc = MOD.to_string() + "Libraries";
let new_name = format!("{}_Hook", cutscene.split('/').last().unwrap_or_default());
loc = loc.join(&new_name).with_extension("uasset");
let mut hook = get_hook(&loc)?;
loc = format!("{loc}/{new_name}.uasset");
// edit the item given by the kismet bytecode in the hook
let Export::FunctionExport(function_export::FunctionExport {
struct_export:
Expand Down Expand Up @@ -237,20 +237,18 @@ fn create_hook<C: std::io::Read + std::io::Seek>(
let name = map.get_name_reference_mut(i as i32);
*name = name.replace("hook", &new_name);
}
save(&mut hook, loc)?;
let loc = app.pak.join(MOD).join(cutscene).with_extension("uasset");
std::fs::create_dir_all(loc.parent().expect("is a file"))?;
pak.read_file(
&format!("Blue Fire/Content/BlueFire/{cutscene}.uasset"),
&mut app.pak()?,
&mut std::fs::File::create(&loc)?,
)?;
pak.read_file(
&format!("Blue Fire/Content/BlueFire/{cutscene}.uexp"),
&mut app.pak()?,
&mut std::fs::File::create(loc.with_extension("uexp"))?,
save(hook, mod_pak, &loc)?;
let loc = format!("{MOD}{cutscene}.uasset");
let mut cutscene = open(
pak.get(
&format!("Blue Fire/Content/BlueFire/{cutscene}.uasset"),
&mut app.pak()?,
)?,
pak.get(
&format!("Blue Fire/Content/BlueFire/{cutscene}.uexp"),
&mut app.pak()?,
)?,
)?;
let mut cutscene = open(&loc)?;
let universal_refs: Vec<usize> = cutscene
.get_name_map()
.get_ref()
Expand All @@ -265,7 +263,7 @@ fn create_hook<C: std::io::Read + std::io::Seek>(
let name = map.get_name_reference_mut(i as i32);
*name = name.replace("UniversalFunctions", &new_name);
}
save(&mut cutscene, &loc)?;
save(&mut cutscene, mod_pak, &loc)?;
Ok(())
}

Expand Down
Loading

0 comments on commit 73c1ecb

Please sign in to comment.