Skip to content
72 changes: 72 additions & 0 deletions src/package/install/bepinex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::{fs, path::Path};

use walkdir::WalkDir;

use crate::error::Error;
use crate::package::install::tracked::TrackedFs;
use crate::package::install::PackageInstaller;
use crate::ts::package_reference::PackageReference;

pub struct BpxInstaller<T: TrackedFs> {
fs: T,
}

impl<T: TrackedFs> BpxInstaller<T> {
pub fn new(fs: T) -> Self {
BpxInstaller { fs }
}
}

impl<T: TrackedFs> PackageInstaller<T> for BpxInstaller<T> {
async fn install_package(
&self,
_package: &PackageReference,
_package_deps: &[PackageReference],
package_dir: &Path,
state_dir: &Path,
_staging_dir: &Path,
game_dir: &Path,
_is_modloader: bool,
) -> Result<(), Error> {
// Figure out the root bepinex directory. This should, in theory, always be the folder
// that contains the winhttp.dll binary.
let bepinex_root = WalkDir::new(package_dir)
.into_iter()
.filter_map(|x| x.ok())
.filter(|x| x.path().is_file())
.find(|x| x.path().file_name().unwrap() == "winhttp.dll")
.expect("Failed to find winhttp.dll within BepInEx directory.");
let bepinex_root = bepinex_root.path().parent().unwrap();

let bep_dir = bepinex_root.join("BepInEx");
let bep_dst = state_dir.join("BepInEx");

// self.fs.dir_copy(&bep_dir, &bep_dst).await.unwrap();

// Install top-level doorstop files.
let files = fs::read_dir(bepinex_root)
.unwrap()
.filter_map(|x| x.ok())
.filter(|x| x.path().is_file());

for file in files {
let dest = game_dir.join(file.path().file_name().unwrap());
// self.fs.file_copy(&file.path(), &dest, None).await?;
}

Ok(())
}

async fn uninstall_package(
&self,
_package: &PackageReference,
_package_deps: &[PackageReference],
_package_dir: &Path,
_state_dir: &Path,
_staging_dir: &Path,
_game_dir: &Path,
_is_modloader: bool,
) -> Result<(), Error> {
todo!()
}
}
55 changes: 55 additions & 0 deletions src/package/install/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,58 @@
use super::Package;
use crate::error::Error;
use crate::error::IoError;
use crate::package::install::bepinex::BpxInstaller;
use crate::package::install::tracked::ConcreteFs;
use crate::package::install::tracked::TrackedFs;
use crate::project::state::StateEntry;
use crate::ts::package_reference::PackageReference;
use crate::ts::v1::models::ecosystem::R2MLLoader;
use crate::ui::reporter::{Progress, ProgressBarTrait, VoidProgress};

pub mod api;
mod legacy_compat;
pub mod manifest;
pub mod bepinex;
mod tracked;

pub trait PackageInstaller<T: TrackedFs> {
/// Install a package into this profile.
///
/// `state_dir` is the directory that is "linked" to at runtime by the modloader.
/// `staging_dir` is the directory that contains files that are directly installed into the game directory.
async fn install_package(
&self,
package: &PackageReference,
package_deps: &[PackageReference],
package_dir: &Path,
state_dir: &Path,
staging_dir: &Path,
game_dir: &Path,
is_modloader: bool,
) -> Result<(), Error>;

/// Uninstall a package from this profile.
async fn uninstall_package(
&self,
package: &PackageReference,
package_deps: &[PackageReference],
package_dir: &Path,
state_dir: &Path,
staging_dir: &Path,
game_dir: &Path,
is_modloader: bool,
) -> Result<(), Error>;

}


/// Get the proper installer for the provided modloader variant.
pub fn get_installer<T: TrackedFs>(ml_variant: &R2MLLoader, fs: T) -> Option<impl PackageInstaller<T>> {
match ml_variant {
R2MLLoader::BepInEx => Some(BpxInstaller::new(fs)),
_ => None,
}
}

pub struct Installer {
pub exec_path: PathBuf,
Expand Down Expand Up @@ -119,6 +166,14 @@
) -> Result<Vec<TrackedFile>, Error> {
// Determine if the package is a modloader or not.
let is_modloader = package.identifier.name.to_lowercase().contains("bepinex");
BpxInstaller::new(ConcreteFs::new(StateEntry::default()));

let fs = ConcreteFs::new(StateEntry::default());
let test = get_installer(&R2MLLoader::BepInEx, fs);

// bepinex::install_package(package.identifier.clone(), &package.dependencies, package_dir, state_dir, staging_dir, is_modloader).await;

panic!();

let request = Request::PackageInstall {
is_modloader,
Expand Down
106 changes: 106 additions & 0 deletions src/package/install/tracked.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::path::Path;
use tokio::fs;
use walkdir::WalkDir;

use crate::package::install::api::{FileAction, TrackedFile};
use crate::project::state::{StagedFile, StateEntry};

use crate::error::Error;

pub trait TrackedFs {
/// Create a new instance dedicated to tracking filesystem edits during the
/// installation of the provided package.
///
/// This essentially creates or opens the cooresponding entry within the
/// tracked_files.json file and writes any tracked fs modifications to it.
fn new(state: StateEntry) -> Self;

/// Extract the new StateEntry from this instance.
fn extract_state(self) -> StateEntry;

/// Copy a file from a source to a destination, overwriting it if the file
/// already exists.
///
/// This will append (or overwrite) a FileAction::Create entry.
async fn file_copy(&mut self, src: &Path, dst: &Path, stage_dst: Option<&Path>) -> Result<(), Error>;

/// Delete some target file.
///
/// If `tracked` is set this this will append a FileAction::Delete entry,
/// overwriting one if it already exists for this file.
async fn file_delete(&mut self, target: &Path, tracked: bool);

/// Recursively copy a source directory to a destination, overwriting it if
/// it already exists.
///
/// This will append (or overwrite) a FileAction::Create entry for each file
/// copied while recursing.
async fn dir_copy(&mut self, src: &Path, dst: &Path) -> Result<(), Error>;

/// Recursively delete some target directory.
///
/// If `tracked` if set then this will append a FileAction::Delete entry
/// for each file deleted while recursing, otherwise matching entries are
/// deleted.
async fn dir_delete(&mut self, target: &Path, tracked: bool);
}

#[derive(Debug)]
pub struct ConcreteFs {
state: StateEntry,
}

impl TrackedFs for ConcreteFs {
fn new(state: StateEntry) -> Self {
ConcreteFs {
state
}
}

fn extract_state(self) -> StateEntry {
self.state
}

async fn file_copy(&mut self, src: &Path, dst: &Path, stage_dst: Option<&Path>) -> Result<(), Error> {
fs::copy(src, dst).await?;
let tracked = TrackedFile { action: FileAction::Create, path: dst.to_path_buf(), context: None };

if let Some(stage_dst) = stage_dst {
let mut staged = StagedFile::new(tracked)?;
staged.dest.push(stage_dst.to_path_buf());
self.state.add_staged(staged, false);
} else {
self.state.add_linked(tracked, false);
}

Ok(())
}

async fn file_delete(&mut self, target: &Path, tracked: bool) {
todo!()
}

async fn dir_copy(&mut self, src: &Path, dst: &Path) -> Result<(), Error> {
let files = WalkDir::new(&src)
.into_iter()
.filter_map(|e| e.ok())
.filter(|x| x.path().is_file());

for file in files {
let dest = dst.join(file.path().strip_prefix(&src).unwrap());
let dest_parent = dest.parent().unwrap();

if !dest_parent.is_dir() {
fs::create_dir_all(dest_parent).await?;
}

self.file_copy(file.path(), &dest, None).await?;
}

Ok(())
}

async fn dir_delete(&mut self, target: &Path, tracked: bool) {
todo!()
}
}
4 changes: 2 additions & 2 deletions src/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ pub mod error;
pub mod lock;
pub mod manifest;
pub mod overrides;
mod publish;
mod state;
pub mod publish;
pub mod state;

pub enum ProjectKind {
Dev(ProjectOverrides),
Expand Down
16 changes: 15 additions & 1 deletion src/project/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,26 @@
}
}

#[derive(Serialize, Deserialize, Default)]
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct StateEntry {
pub staged: Vec<StagedFile>,
pub linked: Vec<TrackedFile>,
}

impl StateEntry {
/// Add a new staged file. If overwrite is set then already existing
/// entries with the same path will be replaced.
pub fn add_staged(&mut self, file: StagedFile, overwrite: bool) {
todo!()
}

/// Add a new linked file. If overwrite is set then already existing
/// entries with the same path will be replaced.
pub fn add_linked(&mut self, file: TrackedFile, overwrite: bool) {
todo!()
}
}

#[derive(Serialize, Deserialize, Default)]
pub struct StateFile {
pub state: HashMap<PackageReference, StateEntry>,
Expand Down
18 changes: 17 additions & 1 deletion src/ts/v1/models/ecosystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,23 @@ pub struct GameDefR2MM {
pub struct R2MMModLoaderPackage {
pub package_id: String,
pub root_folder: String,
pub loader: String,
pub loader: R2MLLoader,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum R2MLLoader {
BepInEx,
GDWeave,
GodotML,
Lovely,
MelonLoader,
Northstar,
#[serde(rename = "recursive-melonloader")]
RecursiveMelonLoader,
#[serde(rename = "return-of-modding")]
ReturnOfModding,
Shimloader,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down
Loading