From c0ff3b98f2490b0e3c2630a249ae52d365b539e1 Mon Sep 17 00:00:00 2001 From: Putta Khunchalee Date: Mon, 25 Nov 2024 23:38:20 +0700 Subject: [PATCH] Updates README to clarify our goal (#1142) --- Cargo.lock | 134 ----------- Cargo.toml | 1 - README.md | 13 +- src/pfs/COPYING | 165 -------------- src/pfs/Cargo.toml | 13 -- src/pfs/src/directory/dirent.rs | 100 --------- src/pfs/src/directory/mod.rs | 221 ------------------ src/pfs/src/file.rs | 281 ----------------------- src/pfs/src/header.rs | 167 -------------- src/pfs/src/image.rs | 255 --------------------- src/pfs/src/inode.rs | 383 -------------------------------- src/pfs/src/lib.rs | 181 --------------- src/pfs/src/pfsc.rs | 275 ----------------------- 13 files changed, 10 insertions(+), 2179 deletions(-) delete mode 100644 src/pfs/COPYING delete mode 100644 src/pfs/Cargo.toml delete mode 100644 src/pfs/src/directory/dirent.rs delete mode 100644 src/pfs/src/directory/mod.rs delete mode 100644 src/pfs/src/file.rs delete mode 100644 src/pfs/src/header.rs delete mode 100644 src/pfs/src/image.rs delete mode 100644 src/pfs/src/inode.rs delete mode 100644 src/pfs/src/lib.rs delete mode 100644 src/pfs/src/pfsc.rs diff --git a/Cargo.lock b/Cargo.lock index 2e0822984..5578c4d8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,17 +31,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - [[package]] name = "ahash" version = "0.8.11" @@ -461,15 +450,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "block2" version = "0.5.1" @@ -697,16 +677,6 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -984,15 +954,6 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" -[[package]] -name = "cpufeatures" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -1039,16 +1000,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "ctor-lite" version = "0.1.0" @@ -1091,17 +1042,6 @@ dependencies = [ "syn", ] -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - [[package]] name = "dispatch" version = "0.2.0" @@ -1568,16 +1508,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "gethostname" version = "0.4.3" @@ -1787,15 +1717,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "home" version = "0.5.9" @@ -2251,15 +2172,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - [[package]] name = "input" version = "0.9.1" @@ -3095,19 +3007,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pfs" -version = "0.1.0" -dependencies = [ - "aes", - "byteorder", - "flate2", - "hmac", - "sha2", - "thiserror", - "xts-mode", -] - [[package]] name = "pico-args" version = "0.5.0" @@ -3592,17 +3491,6 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "shlex" version = "1.3.0" @@ -3878,12 +3766,6 @@ dependencies = [ "syn", ] -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "svgtypes" version = "0.15.2" @@ -4142,12 +4024,6 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "udev" version = "0.9.1" @@ -5075,16 +4951,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" -[[package]] -name = "xts-mode" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "yoke" version = "0.7.4" diff --git a/Cargo.toml b/Cargo.toml index f6a4f48d7..0fab6d505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = [ "src/fs", "src/llt", "src/obconf", - "src/pfs", "src/tls", ] diff --git a/README.md b/README.md index 46c69e384..419d1e5f7 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ Obliteration is a free and open-source PlayStation 4 kernel rewritten in Rust. O This project started as a hard-fork from [Kyty](https://github.com/InoriRus/Kyty). Then we decided to rewrite the whole project from scratch by using Kyty and [Uplift](https://github.com/idc/uplift) as a reference to help us getting started with the project. +Our ultimate goal is to become a permissive free and open-source operating system optimized for gaming that can run on a variety of hardware. The reason we want to built this because: + +- Windows is bloated and Microsoft keep pushing too many things into it. +- Linux is a nightmare for beginners. Its license also making it not an ideal choice for a proprietary hardware. +- macOS has a limited set of hardware and its price too expensive. You can get a PC with high-end graphic card at the same price. +- FreeBSD and the others was not designed for gaming. Their goal are either a server or a general desktop. + +So we want to take this opportunity to go beyond a PlayStation 4 emulator since we already building an operating system kernel. + The project logo and icon was designed by [VocalFan](https://github.com/VocalFan). ## Get a daily build @@ -24,8 +33,6 @@ We use icons from https://materialdesignicons.com for UI. ## License -- `src/param`, `src/pfs` and `src/pkg` are licensed under LGPL-3.0. -- All other source code are licensed under either MIT License or Apache License, Version 2.0; or both. -- All release binaries are under GPL-3.0. +All source code are licensed under either MIT License or Apache License, Version 2.0; or both. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Obliteration by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/src/pfs/COPYING b/src/pfs/COPYING deleted file mode 100644 index 02bbb60bc..000000000 --- a/src/pfs/COPYING +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. \ No newline at end of file diff --git a/src/pfs/Cargo.toml b/src/pfs/Cargo.toml deleted file mode 100644 index d78a6825e..000000000 --- a/src/pfs/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "pfs" -version = "0.1.0" -edition = "2021" - -[dependencies] -aes = "0.8" -byteorder = "1.4.3" -flate2 = "1.0" -hmac = "0.12" -sha2 = "0.10" -thiserror = "1.0" -xts-mode = "0.5" diff --git a/src/pfs/src/directory/dirent.rs b/src/pfs/src/directory/dirent.rs deleted file mode 100644 index 84c505dc2..000000000 --- a/src/pfs/src/directory/dirent.rs +++ /dev/null @@ -1,100 +0,0 @@ -use byteorder::{ByteOrder, LE}; -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::io::Read; - -pub(super) struct Dirent { - ino: usize, - ty: u32, - entsize: usize, - name: Vec, -} - -impl Dirent { - pub const FILE: u32 = 2; - pub const DIRECTORY: u32 = 3; - pub const SELF: u32 = 4; - pub const PARENT: u32 = 5; - - pub fn read(from: &mut F) -> Result { - // Read static sized fields. - let mut data: [u8; 16] = [0u8; 16]; - - from.read_exact(&mut data)?; - - let entsize = LE::read_u32(&data[0x0c..]) as usize; - - if entsize == 0 { - return Err(ReadError::EndOfEntry); - } - - let ino = LE::read_u32(&data[0x00..]) as usize; - let ty = LE::read_u32(&data[0x04..]); - let namelen = LE::read_u32(&data[0x08..]) as usize; - - // Read name. - let mut name = vec![0; namelen]; - - from.read_exact(&mut name)?; - - Ok(Self { - ino, - ty, - entsize, - name, - }) - } - - pub fn inode(&self) -> usize { - self.ino - } - - pub fn ty(&self) -> u32 { - self.ty - } - - pub fn take_name(&mut self) -> Vec { - std::mem::take(&mut self.name) - } - - /// This method **MUST** be called before [`take_name`] otherwise the returned value will be incorrect. - pub fn padding_size(&self) -> usize { - self.entsize - 16 - self.name.len() - } -} - -#[derive(Debug)] -pub enum ReadError { - IoFailed(std::io::Error), - TooSmall, - EndOfEntry, -} - -impl From for ReadError { - fn from(v: std::io::Error) -> Self { - if v.kind() == std::io::ErrorKind::UnexpectedEof { - ReadError::TooSmall - } else { - ReadError::IoFailed(v) - } - } -} - -impl Error for ReadError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::IoFailed(e) => Some(e), - _ => None, - } - } -} - -impl Display for ReadError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match self { - Self::IoFailed(_) => f.write_str("I/O failed"), - Self::TooSmall => f.write_str("data too small"), - Self::EndOfEntry => f.write_str("end of entry"), - } - } -} diff --git a/src/pfs/src/directory/mod.rs b/src/pfs/src/directory/mod.rs deleted file mode 100644 index 1229382f6..000000000 --- a/src/pfs/src/directory/mod.rs +++ /dev/null @@ -1,221 +0,0 @@ -use self::dirent::Dirent; -use crate::file::File; -use crate::inode::Inode; -use crate::Pfs; -use std::collections::HashMap; -use std::io::SeekFrom; -use std::ops::DerefMut; -use std::rc::Rc; -use thiserror::Error; - -pub mod dirent; - -/// Represents a directory in the PFS. -#[derive(Clone)] -pub struct Directory<'a> { - pfs: Rc>, - inode: usize, -} - -impl<'a> Directory<'a> { - pub(super) fn new(pfs: Rc>, inode: usize) -> Self { - Self { pfs, inode } - } - - pub fn mode(&self) -> u16 { - self.inode().mode() - } - - pub fn flags(&self) -> u32 { - self.inode().flags().value() - } - - pub fn atime(&self) -> u64 { - self.inode().atime() - } - - pub fn mtime(&self) -> u64 { - self.inode().mtime() - } - - pub fn ctime(&self) -> u64 { - self.inode().ctime() - } - - pub fn birthtime(&self) -> u64 { - self.inode().birthtime() - } - - pub fn mtimensec(&self) -> u32 { - self.inode().mtimensec() - } - - pub fn atimensec(&self) -> u32 { - self.inode().atimensec() - } - - pub fn ctimensec(&self) -> u32 { - self.inode().ctimensec() - } - - pub fn birthnsec(&self) -> u32 { - self.inode().birthnsec() - } - - pub fn uid(&self) -> u32 { - self.inode().uid() - } - - pub fn gid(&self) -> u32 { - self.inode().gid() - } - - pub fn open(&self) -> Result, OpenError> { - // Load occupied blocks. - let mut image = self.pfs.image.lock().unwrap(); - let image = image.deref_mut(); - let inode = &self.pfs.inodes[self.inode]; - let blocks = match inode.load_blocks(image.as_mut()) { - Ok(v) => v, - Err(e) => return Err(OpenError::LoadBlocksFailed(e)), - }; - - // Read all dirents. - let mut items: HashMap, Item<'a>> = HashMap::new(); - let block_size = image.header().block_size(); - let mut block_data = vec![0; block_size as usize]; - - for block_num in blocks { - // Seek to block. - let offset = (block_num as u64) * (block_size as u64); - - match image.seek(SeekFrom::Start(offset)) { - Ok(v) => { - if v != offset { - return Err(OpenError::BlockNotExists(block_num)); - } - } - Err(e) => return Err(OpenError::SeekToBlockFailed(block_num, e)), - } - - // Read block data. - if let Err(e) = image.read_exact(&mut block_data) { - return Err(OpenError::ReadBlockFailed(block_num, e)); - } - - // Read dirents in the block. - let mut next = block_data.as_slice(); - - for num in 0.. { - // Read dirent. - let mut dirent = match Dirent::read(&mut next) { - Ok(v) => v, - Err(e) => match e { - dirent::ReadError::IoFailed(e) => { - panic!("Failed to read dirent due to I/O error: {}", e); - } - dirent::ReadError::TooSmall | dirent::ReadError::EndOfEntry => break, - }, - }; - - // Skip remaining padding. - next = match next.get(dirent.padding_size()..) { - Some(v) => v, - None => { - return Err(OpenError::InvalidDirent { - block: block_num, - dirent: num, - }); - } - }; - - // Check if inode valid. - let inode = dirent.inode(); - - if inode >= self.pfs.inodes.len() { - return Err(OpenError::InvalidInode(inode)); - } - - // Construct object. - let item = match dirent.ty() { - Dirent::FILE => Item::File(File::new(self.pfs.clone(), inode)), - Dirent::DIRECTORY => Item::Directory(Directory::new(self.pfs.clone(), inode)), - Dirent::SELF | Dirent::PARENT => continue, - _ => { - return Err(OpenError::UnknownDirent { - block: block_num, - dirent: num, - }); - } - }; - - items.insert(dirent.take_name(), item); - } - } - - Ok(Items { items }) - } - - fn inode(&self) -> &Inode { - &self.pfs.inodes[self.inode] - } -} - -/// Represents a collection of items in the directory. -pub struct Items<'a> { - items: HashMap, Item<'a>>, -} - -impl<'a> Items<'a> { - pub fn len(&self) -> usize { - self.items.len() - } - - pub fn get(&self, name: &[u8]) -> Option<&Item<'a>> { - self.items.get(name) - } - - pub fn take(&mut self, name: &[u8]) -> Option> { - self.items.remove(name) - } -} - -impl<'a> IntoIterator for Items<'a> { - type Item = (Vec, Item<'a>); - type IntoIter = std::collections::hash_map::IntoIter, Item<'a>>; - - fn into_iter(self) -> Self::IntoIter { - self.items.into_iter() - } -} - -/// Represents an item in the directory. -pub enum Item<'a> { - Directory(Directory<'a>), - File(File<'a>), -} - -/// Errors of [`open()`][Directory::open()]. -#[derive(Debug, Error)] -pub enum OpenError { - #[error("inode #{0} is not valid")] - InvalidInode(usize), - - #[error("cannot load occupied blocks")] - LoadBlocksFailed(#[source] crate::inode::LoadBlocksError), - - #[error("cannot seek to block #{0}")] - SeekToBlockFailed(u32, #[source] std::io::Error), - - #[error("block #{0} does not exist")] - BlockNotExists(u32), - - #[error("cannot read block #{0}")] - ReadBlockFailed(u32, #[source] std::io::Error), - - #[error("Dirent #{dirent:} in block #{block:} has invalid size")] - InvalidDirent { block: u32, dirent: usize }, - - #[error("Dirent #{dirent:} in block #{block:} has unknown type")] - UnknownDirent { block: u32, dirent: usize }, -} diff --git a/src/pfs/src/file.rs b/src/pfs/src/file.rs deleted file mode 100644 index b461de07c..000000000 --- a/src/pfs/src/file.rs +++ /dev/null @@ -1,281 +0,0 @@ -use crate::inode::Inode; -use crate::Pfs; -use std::cmp::min; -use std::io::{Error, ErrorKind, Read, Seek, SeekFrom}; -use std::ops::DerefMut; -use std::rc::Rc; - -/// Represents a file in the PFS. -pub struct File<'a> { - pfs: Rc>, - inode: usize, - occupied_blocks: Vec, - current_offset: u64, - current_block: Vec, -} - -impl<'a> File<'a> { - pub(crate) fn new(pfs: Rc>, inode: usize) -> Self { - Self { - pfs, - inode, - occupied_blocks: Vec::new(), - current_offset: 0, - current_block: Vec::new(), - } - } - - pub fn mode(&self) -> u16 { - self.inode().mode() - } - - pub fn flags(&self) -> u32 { - self.inode().flags().value() - } - - pub fn len(&self) -> u64 { - self.inode().size() - } - - pub fn decompressed_len(&self) -> u64 { - self.inode().decompressed_size() - } - - pub fn atime(&self) -> u64 { - self.inode().atime() - } - - pub fn mtime(&self) -> u64 { - self.inode().mtime() - } - - pub fn ctime(&self) -> u64 { - self.inode().ctime() - } - - pub fn birthtime(&self) -> u64 { - self.inode().birthtime() - } - - pub fn mtimensec(&self) -> u32 { - self.inode().mtimensec() - } - - pub fn atimensec(&self) -> u32 { - self.inode().atimensec() - } - - pub fn ctimensec(&self) -> u32 { - self.inode().ctimensec() - } - - pub fn birthnsec(&self) -> u32 { - self.inode().birthnsec() - } - - pub fn uid(&self) -> u32 { - self.inode().uid() - } - - pub fn gid(&self) -> u32 { - self.inode().gid() - } - - pub fn is_compressed(&self) -> bool { - self.inode().flags().is_compressed() - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - fn inode(&self) -> &Inode { - &self.pfs.inodes[self.inode] - } -} - -impl<'a> Clone for File<'a> { - fn clone(&self) -> Self { - Self { - pfs: self.pfs.clone(), - inode: self.inode, - occupied_blocks: Vec::new(), - current_offset: self.current_offset, - current_block: Vec::new(), - } - } -} - -impl<'a> Seek for File<'a> { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result { - // Get inode. - let inode = match self.pfs.inodes.get(self.inode) { - Some(v) => v, - None => { - return Err(Error::new( - ErrorKind::Other, - format!("inode #{} does not exist", self.inode), - )) - } - }; - - // Calculate new offset. - let offset = match pos { - SeekFrom::Start(v) => min(inode.size(), v), - SeekFrom::End(v) => { - if v >= 0 { - inode.size() - } else { - let v = v.unsigned_abs(); - - if v > inode.size() { - return Err(Error::from(ErrorKind::InvalidInput)); - } - - inode.size() - v - } - } - SeekFrom::Current(v) => { - if v >= 0 { - min(inode.size(), self.current_offset + (v as u64)) - } else { - let v = v.unsigned_abs(); - - if v > self.current_offset { - return Err(Error::from(ErrorKind::InvalidInput)); - } - - self.current_offset - v - } - } - }; - - // Update offset. - if offset != self.current_offset { - self.current_offset = offset; - self.current_block.clear(); - } - - Ok(self.current_offset) - } - - fn rewind(&mut self) -> std::io::Result<()> { - self.current_offset = 0; - self.current_block.clear(); - Ok(()) - } - - fn stream_position(&mut self) -> std::io::Result { - Ok(self.current_offset) - } -} - -impl<'a> Read for File<'a> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - // Get inode. - let inode = match self.pfs.inodes.get(self.inode) { - Some(v) => v, - None => { - return Err(Error::new( - ErrorKind::Other, - format!("inode #{} does not exist", self.inode), - )) - } - }; - - // Check if we need to do the actual read. - if buf.is_empty() || self.current_offset == inode.size() { - return Ok(0); - } - - // Load occupied blocks. - let mut image = self.pfs.image.lock().unwrap(); - let image = image.deref_mut(); - - if self.occupied_blocks.is_empty() { - self.occupied_blocks = match inode.load_blocks(image.as_mut()) { - Ok(v) => v, - Err(e) => return Err(Error::new(ErrorKind::Other, e)), - }; - } - - // Copy data. - let block_size = image.header().block_size(); - let mut copied = 0usize; - - loop { - // Load block for current offset. - if self.current_block.is_empty() { - // Get block number. - let block_index = self.current_offset / (block_size as u64); - let block_num = match self.occupied_blocks.get(block_index as usize) { - Some(&v) => v, - None => { - break Err(Error::new( - ErrorKind::Other, - format!("block #{} is not available", block_index), - )); - } - }; - - // Check if this is a last block. - let total = block_index * (block_size as u64) + (block_size as u64); - let read_amount = if total > inode.size() { - // Both total and len never be zero. - (block_size as u64) - (total - inode.size()) - } else { - block_size as u64 - }; - - #[allow(clippy::uninit_vec)] - { - // calling `set_len()` immediately after reserving a buffer creates uninitialized values - // Allocate buffer. - self.current_block.reserve(read_amount as usize); - unsafe { self.current_block.set_len(read_amount as usize) }; - } - - // Seek to block. - let offset = (block_num as u64) * (block_size as u64); - - match image.seek(SeekFrom::Start(offset)) { - Ok(v) => { - if v != offset { - return Err(Error::new( - ErrorKind::Other, - format!("block #{} does not exist", block_num), - )); - } - } - Err(e) => return Err(e), - } - - // Load block data. - image.read_exact(&mut self.current_block)?; - } - - // Get a window into current block from current offset. - let offset = self.current_offset % (block_size as u64); - let src = &self.current_block[(offset as usize)..]; - - // Copy the window to output buffer. - let dst = unsafe { buf.as_mut_ptr().add(copied) }; - let amount = min(src.len(), buf.len() - copied) as u32; - - unsafe { dst.copy_from_nonoverlapping(src.as_ptr(), amount as usize) }; - copied += amount as usize; - - // Advance current offset. - self.current_offset += amount as u64; - - if self.current_offset % (block_size as u64) == 0 { - self.current_block.clear(); - } - - // Check if completed. - if copied == buf.len() || self.current_offset == inode.size() { - break Ok(copied); - } - } - } -} diff --git a/src/pfs/src/header.rs b/src/pfs/src/header.rs deleted file mode 100644 index 8afae918a..000000000 --- a/src/pfs/src/header.rs +++ /dev/null @@ -1,167 +0,0 @@ -use byteorder::{ByteOrder, LE}; -use std::fmt::{Display, Formatter}; -use std::io::Read; -use thiserror::Error; - -/// Contains PFS header. -/// -/// See https://www.psdevwiki.com/ps4/PFS#Header.2FSuperblock for some basic information. -pub(crate) struct Header { - mode: Mode, - blocksz: u32, - ndinode: u64, - ndinodeblock: u32, - superroot_ino: u64, - key_seed: [u8; 16], -} - -impl Header { - pub(super) fn read(image: &mut I) -> Result { - // Read the whole header into the buffer. - let mut hdr = [0u8; 0x380]; - - if let Err(e) = image.read_exact(&mut hdr) { - return Err(ReadError::IoFailed(e)); - } - - // Check version. - let version = LE::read_u64(&hdr[0x00..]); - - if version != 1 { - return Err(ReadError::InvalidVersion); - } - - // Check format. - let format = LE::read_u64(&hdr[0x08..]); - - if format != 20130315 { - return Err(ReadError::InvalidFormat); - } - - // Read fields. - let mode = Mode(LE::read_u16(&hdr[0x1c..])); - let blocksz = LE::read_u32(&hdr[0x20..]); - let ndinode = LE::read_u64(&hdr[0x30..]); - let ndinodeblock = LE::read_u64(&hdr[0x40..]); - let superroot_ino = LE::read_u64(&hdr[0x48..]); - let key_seed = &hdr[0x370..(0x370 + 16)]; - - // Usually block will be references by u32. Not sure why ndinodeblock is 64-bits. Design - // flaws? - if ndinodeblock > (u32::MAX as u64) { - return Err(ReadError::TooManyInodeBlocks); - } - - Ok(Self { - mode, - blocksz, - ndinode, - ndinodeblock: ndinodeblock as u32, - superroot_ino, - key_seed: key_seed.try_into().unwrap(), - }) - } - - pub fn mode(&self) -> Mode { - self.mode - } - - pub fn block_size(&self) -> u32 { - self.blocksz - } - - /// Gets a number of total inodes. - pub fn inode_count(&self) -> usize { - self.ndinode as _ - } - - /// Gets a number of blocks containing inode (not a number of inode). - pub fn inode_block_count(&self) -> u32 { - self.ndinodeblock - } - - pub fn super_root_inode(&self) -> usize { - self.superroot_ino as _ - } - - pub fn key_seed(&self) -> &[u8; 16] { - &self.key_seed - } -} - -/// Contains PFS flags. -#[derive(Clone, Copy)] -#[repr(transparent)] -pub(crate) struct Mode(u16); - -impl Mode { - pub fn is_signed(&self) -> bool { - self.0 & 0x1 != 0 - } - - pub fn is_64bits(&self) -> bool { - self.0 & 0x2 != 0 - } - - pub fn is_encrypted(&self) -> bool { - self.0 & 0x4 != 0 - } -} - -impl Display for Mode { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{:x}", self.0)?; - - let mut op = false; - let mut first = true; - let mut flag = |name: &str| -> std::fmt::Result { - if !op { - f.write_str(" (")?; - op = true; - } - - if !first { - f.write_str(", ")?; - } - - f.write_str(name)?; - first = false; - - Ok(()) - }; - - if self.is_signed() { - flag("signed")?; - } - - if self.is_64bits() { - flag("64-bits")?; - } - - if self.is_encrypted() { - flag("encrypted")?; - } - - if op { - f.write_str(")")?; - } - - Ok(()) - } -} - -/// Errors for [read()][Header::read()]. -#[derive(Debug, Error)] -pub enum ReadError { - #[error("cannot read image")] - IoFailed(#[source] std::io::Error), - - #[error("invalid version")] - InvalidVersion, - - #[error("invalid format")] - InvalidFormat, - - #[error("too many blocks for inodes")] - TooManyInodeBlocks, -} diff --git a/src/pfs/src/image.rs b/src/pfs/src/image.rs deleted file mode 100644 index b84673c4c..000000000 --- a/src/pfs/src/image.rs +++ /dev/null @@ -1,255 +0,0 @@ -use crate::header::Header; -use crate::Image; -use aes::Aes128; -use hmac::{Hmac, Mac}; -use sha2::Sha256; -use std::cmp::min; -use std::io::{IoSliceMut, Read, Seek, SeekFrom}; -use xts_mode::{get_tweak_default, Xts128}; - -pub(super) const XTS_BLOCK_SIZE: usize = 0x1000; - -/// Gets data key and tweak key. -pub(super) fn get_xts_keys(ekpfs: &[u8], seed: &[u8; 16]) -> ([u8; 16], [u8; 16]) { - // Derive key. - let mut hmac = Hmac::::new_from_slice(ekpfs).unwrap(); - let mut input: Vec = Vec::with_capacity(seed.len() + 4); - - input.extend([0x01, 0x00, 0x00, 0x00]); - input.extend(seed); - - hmac.update(input.as_slice()); - - // Split key. - let secret = hmac.finalize().into_bytes(); - let mut data_key: [u8; 16] = Default::default(); - let mut tweak_key: [u8; 16] = Default::default(); - - tweak_key.copy_from_slice(&secret[..16]); - data_key.copy_from_slice(&secret[16..]); - - (data_key, tweak_key) -} - -/// Encapsulate an unencrypted PFS image. -pub(super) struct Unencrypted { - image: I, - header: Header, -} - -impl Unencrypted { - pub fn new(image: I, header: Header) -> Self { - Self { image, header } - } -} - -impl Image for Unencrypted { - fn header(&self) -> &Header { - &self.header - } -} - -impl Seek for Unencrypted { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result { - self.image.seek(pos) - } - - fn rewind(&mut self) -> std::io::Result<()> { - self.image.rewind() - } - - fn stream_position(&mut self) -> std::io::Result { - self.image.stream_position() - } -} - -impl Read for Unencrypted { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - self.image.read(buf) - } - - fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> std::io::Result { - self.image.read_vectored(bufs) - } - - fn read_to_end(&mut self, buf: &mut Vec) -> std::io::Result { - self.image.read_to_end(buf) - } - - fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { - self.image.read_to_string(buf) - } - - fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { - self.image.read_exact(buf) - } -} - -/// Encapsulate an encrypted PFS image. -pub(super) struct Encrypted { - image: I, - len: u64, - header: Header, - decryptor: Xts128, - encrypted_start: usize, // Block index, not offset. - offset: u64, - current_block: Vec, -} - -impl Encrypted { - pub fn new( - image: I, - image_len: u64, - header: Header, - decryptor: Xts128, - encrypted_start: usize, - current_offset: u64, - ) -> Self { - Self { - image, - len: image_len, - header, - decryptor, - encrypted_start, - offset: current_offset, - current_block: Vec::new(), - } - } -} - -impl Image for Encrypted { - fn header(&self) -> &Header { - &self.header - } -} - -impl Seek for Encrypted { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result { - use std::io::{Error, ErrorKind}; - - // Calculate the offset. - let offset = match pos { - SeekFrom::Start(v) => min(v, self.len), - SeekFrom::End(v) => { - if v >= 0 { - self.len - } else { - match self.len.checked_sub(v.unsigned_abs()) { - Some(v) => v, - None => return Err(Error::from(ErrorKind::InvalidInput)), - } - } - } - SeekFrom::Current(v) => { - if v >= 0 { - min(self.offset + (v as u64), self.len) - } else { - match self.offset.checked_sub(v.unsigned_abs()) { - Some(v) => v, - None => return Err(Error::from(ErrorKind::InvalidInput)), - } - } - } - }; - - // Update the offset if it is difference. - if offset != self.offset { - self.offset = offset; - self.current_block.clear(); - } - - Ok(offset) - } - - fn rewind(&mut self) -> std::io::Result<()> { - if self.offset != 0 { - self.offset = 0; - self.current_block.clear(); - } - - Ok(()) - } - - fn stream_position(&mut self) -> std::io::Result { - Ok(self.offset) - } -} - -impl Read for Encrypted { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - use std::io::{Error, ErrorKind}; - - // Check if we need to do the actual read. - if buf.is_empty() || self.offset == self.len { - return Ok(0); - } - - // Fill output buffer until it is full or EOF. - let mut copied = 0; - - loop { - // Check if we have remaining data available for current block. - if self.current_block.is_empty() { - // Get offset for current block. - let block = (self.offset as usize) / XTS_BLOCK_SIZE; - let offset = (block * XTS_BLOCK_SIZE) as u64; - - // Seek image file to the target offset. - match self.image.seek(SeekFrom::Start(offset)) { - Ok(v) => { - if v != offset { - return Err(Error::new( - ErrorKind::Other, - format!("unable to seek to offset {}", offset), - )); - } - } - Err(e) => return Err(e), - } - - // Read the current block. - self.current_block.reserve(XTS_BLOCK_SIZE); - - unsafe { - let buf = std::slice::from_raw_parts_mut( - self.current_block.as_mut_ptr(), - XTS_BLOCK_SIZE, - ); - - self.image.read_exact(buf)?; - self.current_block.set_len(XTS_BLOCK_SIZE); - } - - // Decrypt block. - if block >= self.encrypted_start { - let tweak = get_tweak_default(block as _); - - self.decryptor - .decrypt_sector(&mut self.current_block, tweak); - } - - // Discard any data before current offset. - let offset = (self.offset as usize) % XTS_BLOCK_SIZE; - - self.current_block.drain(..offset); - } - - // Copy data to output buffer. - let amount = min(buf.len() - copied, self.current_block.len()); - let src = self.current_block.drain(..amount); - let dst = &mut buf[copied..(copied + amount)]; - - dst.copy_from_slice(src.as_slice()); - copied += amount; - - // Advance the offset. - drop(src); - self.offset += amount as u64; - - // Check if output buffer is filled or EOF. - if copied == buf.len() || self.offset == self.len { - break Ok(copied); - } - } - } -} diff --git a/src/pfs/src/inode.rs b/src/pfs/src/inode.rs deleted file mode 100644 index 587d4ed46..000000000 --- a/src/pfs/src/inode.rs +++ /dev/null @@ -1,383 +0,0 @@ -use crate::Image; -use byteorder::{ByteOrder, LE}; -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::io::{Read, SeekFrom}; -use thiserror::Error; - -/// Contains information for an inode. -pub(crate) struct Inode { - index: usize, - mode: u16, - flags: InodeFlags, - size: u64, - decompressed_size: u64, - atime: u64, - mtime: u64, - ctime: u64, - birthtime: u64, - mtimensec: u32, - atimensec: u32, - ctimensec: u32, - birthnsec: u32, - uid: u32, - gid: u32, - blocks: u32, - direct_blocks: [u32; 12], - direct_sigs: [Option<[u8; 32]>; 12], - indirect_blocks: [u32; 5], - indirect_signs: [Option<[u8; 32]>; 5], - indirect_reader: fn(&mut &[u8]) -> Option, -} - -impl Inode { - pub(super) fn from_raw32_unsigned(index: usize, raw: &mut R) -> Result - where - R: Read, - { - // Read common fields. - let raw: [u8; 168] = Self::read_raw(raw)?; - let mut inode = Self::read_common_fields(index, &raw, Self::read_indirect32_unsigned); - - // Read block pointers. - let mut offset = 0x64; - for i in 0..12 { - inode.direct_blocks[i] = LE::read_u32(&raw[offset..offset + 4]); - offset += 4; - } - - for i in 0..5 { - inode.indirect_blocks[i] = LE::read_u32(&raw[offset..offset + 4]); - offset += 4; - } - - Ok(inode) - } - - pub(super) fn from_raw32_signed(index: usize, raw: &mut R) -> Result - where - R: Read, - { - // Read common fields. - let raw: [u8; 712] = Self::read_raw(raw)?; - let mut inode = Self::read_common_fields(index, &raw, Self::read_indirect32_signed); - - // Read block pointers. - let mut offset = 0x64; - for i in 0..12 { - inode.direct_sigs[i] = Some(raw[offset..offset + 32].try_into().unwrap()); - inode.direct_blocks[i] = LE::read_u32(&raw[offset + 32..offset + 36]); - offset += 36; - } - - for i in 0..5 { - inode.indirect_signs[i] = Some(raw[offset..offset + 32].try_into().unwrap()); - inode.indirect_blocks[i] = LE::read_u32(&raw[offset + 32..offset + 36]); - offset += 36; - } - - Ok(inode) - } - - pub fn mode(&self) -> u16 { - self.mode - } - - pub fn flags(&self) -> InodeFlags { - self.flags - } - - pub fn size(&self) -> u64 { - self.size - } - - pub fn decompressed_size(&self) -> u64 { - self.decompressed_size - } - - pub fn atime(&self) -> u64 { - self.atime - } - - pub fn mtime(&self) -> u64 { - self.mtime - } - - pub fn ctime(&self) -> u64 { - self.ctime - } - - pub fn birthtime(&self) -> u64 { - self.birthtime - } - - pub fn mtimensec(&self) -> u32 { - self.mtimensec - } - - pub fn atimensec(&self) -> u32 { - self.atimensec - } - - pub fn ctimensec(&self) -> u32 { - self.ctimensec - } - - pub fn birthnsec(&self) -> u32 { - self.birthnsec - } - - pub fn uid(&self) -> u32 { - self.uid - } - - pub fn gid(&self) -> u32 { - self.gid - } - - pub fn load_blocks(&self, image: &mut dyn Image) -> Result, LoadBlocksError> { - // Check if inode use contiguous blocks. - let mut blocks: Vec = Vec::with_capacity(self.blocks as usize); - - if blocks.len() == self.blocks as usize { - // inode with zero block should not be possible but just in case for malformed image. - return Ok(blocks); - } - - if self.direct_blocks[1] == 0xffffffff { - let start = self.direct_blocks[0]; - - for block in start..(start + self.blocks) { - blocks.push(block); - } - - return Ok(blocks); - } - - // Load direct pointers. - for i in 0..12 { - blocks.push(self.direct_blocks[i]); - - if blocks.len() == self.blocks as usize { - return Ok(blocks); - } - } - - // FIXME: Refactor algorithm to read indirect blocks. - // Load indirect 0. - let block_num = self.indirect_blocks[0]; - let block_size = image.header().block_size(); - let offset = (block_num * block_size) as u64; - - match image.seek(SeekFrom::Start(offset)) { - Ok(v) => { - if v != offset { - return Err(LoadBlocksError::BlockNotExists(block_num)); - } - } - Err(e) => return Err(LoadBlocksError::SeekFailed(block_num, e)), - } - - let mut block0 = vec![0; block_size as usize]; - - if let Err(e) = image.read_exact(&mut block0) { - return Err(LoadBlocksError::ReadBlockFailed(block_num, e)); - } - - let mut data = block0.as_slice(); - - while let Some(i) = (self.indirect_reader)(&mut data) { - blocks.push(i); - - if blocks.len() == self.blocks as usize { - return Ok(blocks); - } - } - - // Load indirect 1. - let block_num = self.indirect_blocks[1]; - let offset = (block_num * block_size) as u64; - - match image.seek(SeekFrom::Start(offset)) { - Ok(v) => { - if v != offset { - return Err(LoadBlocksError::BlockNotExists(block_num)); - } - } - Err(e) => return Err(LoadBlocksError::SeekFailed(block_num, e)), - } - - if let Err(e) = image.read_exact(&mut block0) { - return Err(LoadBlocksError::ReadBlockFailed(block_num, e)); - } - - let mut block1 = vec![0; block_size as usize]; - let mut data0 = block0.as_slice(); - - while let Some(i) = (self.indirect_reader)(&mut data0) { - let offset = (i * block_size) as u64; - - match image.seek(SeekFrom::Start(offset)) { - Ok(v) => { - if v != offset { - return Err(LoadBlocksError::BlockNotExists(i)); - } - } - Err(e) => return Err(LoadBlocksError::SeekFailed(i, e)), - } - - if let Err(e) = image.read_exact(&mut block1) { - return Err(LoadBlocksError::ReadBlockFailed(i, e)); - } - - let mut data1 = block1.as_slice(); - - while let Some(j) = (self.indirect_reader)(&mut data1) { - blocks.push(j); - - if blocks.len() == self.blocks as usize { - return Ok(blocks); - } - } - } - - panic!( - "Data of inode #{} was spanned to indirect block #2, which we are not supported yet", - self.index - ); - } - - fn read_raw(raw: &mut R) -> Result<[u8; L], FromRawError> { - let mut buf: [u8; L] = [0u8; L]; - - if let Err(e) = raw.read_exact(&mut buf) { - return Err(if e.kind() == std::io::ErrorKind::UnexpectedEof { - FromRawError::TooSmall - } else { - FromRawError::IoFailed(e) - }); - } - - Ok(buf) - } - - fn read_common_fields( - index: usize, - raw: &[u8], - indirect_reader: fn(&mut &[u8]) -> Option, - ) -> Self { - let mode = LE::read_u16(&raw[0x00..]); - let flags = InodeFlags(LE::read_u32(&raw[0x04..])); - let size = LE::read_u64(&raw[0x08..]); - let decompressed_size = LE::read_u64(&raw[0x10..]); - let atime = LE::read_u64(&raw[0x18..]); - let mtime = LE::read_u64(&raw[0x20..]); - let ctime = LE::read_u64(&raw[0x28..]); - let birthtime = LE::read_u64(&raw[0x30..]); - let mtimensec = LE::read_u32(&raw[0x38..]); - let atimensec = LE::read_u32(&raw[0x3c..]); - let ctimensec = LE::read_u32(&raw[0x40..]); - let birthnsec = LE::read_u32(&raw[0x44..]); - let uid = LE::read_u32(&raw[0x48..]); - let gid = LE::read_u32(&raw[0x4c..]); - let blocks = LE::read_u32(&raw[0x60..]); - - Self { - index, - mode, - flags, - size, - decompressed_size, - atime, - mtime, - ctime, - birthtime, - mtimensec, - atimensec, - ctimensec, - birthnsec, - uid, - gid, - blocks, - direct_blocks: [0; 12], - direct_sigs: [None; 12], - indirect_blocks: [0; 5], - indirect_signs: [None; 5], - indirect_reader, - } - } - - fn read_indirect32_unsigned(raw: &mut &[u8]) -> Option { - let value = match raw.get(..4) { - Some(v) => LE::read_u32(&v[0x00..]), - None => return None, - }; - - *raw = &raw[4..]; - - Some(value) - } - - fn read_indirect32_signed(raw: &mut &[u8]) -> Option { - let value = match raw.get(..36) { - Some(v) => LE::read_u32(&v[0x20..]), - None => return None, - }; - - *raw = &raw[36..]; - - Some(value) - } -} - -/// Flags of the inode. -#[derive(Clone, Copy)] -#[repr(transparent)] -pub(crate) struct InodeFlags(u32); - -impl InodeFlags { - pub fn is_compressed(self) -> bool { - self.0 & 0x00000001 != 0 - } - - pub fn value(self) -> u32 { - self.0 - } -} - -#[derive(Debug)] -pub enum FromRawError { - IoFailed(std::io::Error), - TooSmall, -} - -impl Error for FromRawError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::IoFailed(e) => Some(e), - _ => None, - } - } -} - -impl Display for FromRawError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match self { - Self::IoFailed(_) => f.write_str("I/O failed"), - Self::TooSmall => f.write_str("data too small"), - } - } -} - -/// Errors for [`load_blocks()`][Inode::load_blocks()]. -#[derive(Debug, Error)] -pub enum LoadBlocksError { - #[error("cannot seek to block #{0}")] - SeekFailed(u32, #[source] std::io::Error), - - #[error("block #{0} does not exist")] - BlockNotExists(u32), - - #[error("cannot read block #{0}")] - ReadBlockFailed(u32, #[source] std::io::Error), -} diff --git a/src/pfs/src/lib.rs b/src/pfs/src/lib.rs deleted file mode 100644 index b52293cbf..000000000 --- a/src/pfs/src/lib.rs +++ /dev/null @@ -1,181 +0,0 @@ -use self::directory::Directory; -use self::header::Header; -use self::inode::Inode; -use aes::cipher::KeyInit; -use aes::Aes128; -use std::io::{Read, Seek, SeekFrom}; -use std::rc::Rc; -use std::sync::Mutex; -use thiserror::Error; -use xts_mode::Xts128; - -pub mod directory; -pub mod file; -pub mod header; -pub mod image; -pub mod inode; -pub mod pfsc; - -pub fn open<'a, I>(mut image: I, ekpfs: Option<&[u8]>) -> Result>, OpenError> -where - I: Read + Seek + 'a, -{ - // Read header. - let header = match Header::read(&mut image) { - Ok(v) => v, - Err(e) => return Err(OpenError::ReadHeaderFailed(e)), - }; - - // Check if image is supported. - let mode = header.mode(); - - if mode.is_64bits() { - panic!("64-bits inode is not supported yet."); - } - - // Construct reader. - let block_size = header.block_size(); - let mut image: Box = if mode.is_encrypted() { - // The super block (block that contain header) never get encrypted. - if (block_size as usize) < image::XTS_BLOCK_SIZE { - return Err(OpenError::InvalidBlockSize); - } - - // Get image size. - let image_len = match image.seek(SeekFrom::End(0)) { - Ok(v) => v, - Err(e) => return Err(OpenError::GetImageLengthFailed(e)), - }; - - // Setup decryptor. - let ekpfs = match ekpfs { - Some(v) => v, - None => panic!("The image is encrypted but no EKPFS is provided"), - }; - - let key_seed = header.key_seed(); - let (data_key, tweak_key) = image::get_xts_keys(ekpfs, key_seed); - let cipher_1 = Aes128::new((&data_key).into()); - let cipher_2 = Aes128::new((&tweak_key).into()); - - Box::new(image::Encrypted::new( - image, - image_len, - header, - Xts128::::new(cipher_1, cipher_2), - (block_size as usize) / image::XTS_BLOCK_SIZE, - image_len, - )) - } else { - Box::new(image::Unencrypted::new(image, header)) - }; - - // Read inode blocks. - let mut block_data = vec![0; block_size as usize]; - let mut inodes: Vec = Vec::with_capacity(image.header().inode_count()); - - 'load_block: for block_num in 0..image.header().inode_block_count() { - // Get the offset for target block. The first inode block always start at second block. - let offset = (block_size as u64) + (block_num as u64) * (block_size as u64); - - // Seek to target block. - match image.seek(SeekFrom::Start(offset)) { - Ok(v) => { - if v != offset { - return Err(OpenError::InvalidBlock(block_num)); - } - } - Err(e) => return Err(OpenError::SeekToBlockFailed(block_num, e)), - } - - // Read the whole block. - if let Err(e) = image.read_exact(&mut block_data) { - return Err(OpenError::ReadBlockFailed(block_num, e)); - } - - // Read inodes in the block. - let mut src = block_data.as_slice(); - - let reader = if mode.is_signed() { - Inode::from_raw32_signed - } else { - Inode::from_raw32_unsigned - }; - - while inodes.len() < image.header().inode_count() { - let inode = match reader(inodes.len(), &mut src) { - Ok(v) => v, - Err(e) => match e { - inode::FromRawError::TooSmall => continue 'load_block, - inode::FromRawError::IoFailed(e) => { - panic!("Failed to read inode from a buffer: {}", e); - } - }, - }; - - inodes.push(inode); - } - - break; - } - - // Check if super-root valid - let super_root = image.header().super_root_inode(); - - if super_root >= inodes.len() { - return Err(OpenError::InvalidSuperRoot); - } - - Ok(Rc::new(Pfs { - image: Mutex::new(image), - inodes, - root: super_root, - })) -} - -/// Represents a loaded PFS. -pub struct Pfs<'a> { - image: Mutex>, - inodes: Vec, - root: usize, -} - -impl<'a> Pfs<'a> { - pub fn inodes(&self) -> usize { - self.inodes.len() - } - - pub fn root(self: &Rc) -> Directory<'a> { - Directory::new(self.clone(), self.root) - } -} - -/// Encapsulate a PFS image. -pub(crate) trait Image: Read + Seek { - fn header(&self) -> &Header; -} - -/// Errors for [`open()`]. -#[derive(Debug, Error)] -pub enum OpenError { - #[error("cannot read header")] - ReadHeaderFailed(#[source] header::ReadError), - - #[error("invalid block size")] - InvalidBlockSize, - - #[error("cannot get the length of image")] - GetImageLengthFailed(#[source] std::io::Error), - - #[error("cannot seek to block #{0}")] - SeekToBlockFailed(u32, #[source] std::io::Error), - - #[error("block #{0} is not valid")] - InvalidBlock(u32), - - #[error("cannot read block #{0}")] - ReadBlockFailed(u32, #[source] std::io::Error), - - #[error("invalid super-root")] - InvalidSuperRoot, -} diff --git a/src/pfs/src/pfsc.rs b/src/pfs/src/pfsc.rs deleted file mode 100644 index 85a579328..000000000 --- a/src/pfs/src/pfsc.rs +++ /dev/null @@ -1,275 +0,0 @@ -use byteorder::{ByteOrder, LE}; -use flate2::FlushDecompress; -use std::cmp::min; -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::io::{ErrorKind, Read, Seek, SeekFrom}; - -// FIXME: Refactor the whole implementation of this since a lot of logic does not make sense. -pub struct Reader { - file: F, - block_size: u32, - original_block_size: u64, - compressed_blocks: Vec, // Original block number to compressed block offset. - original_size: u64, - current_offset: u64, - current_block: Vec, -} - -impl Reader { - pub fn open(mut file: F) -> Result { - // Seek to beginning. - if let Err(e) = file.rewind() { - return Err(OpenError::SeekFailed(0, e)); - } - - // Check header. - let mut hdr: [u8; 48] = [0u8; 48]; - - if let Err(e) = file.read_exact(&mut hdr) { - return Err(if e.kind() == ErrorKind::UnexpectedEof { - OpenError::TooSmall - } else { - OpenError::IoFailed(e) - }); - } - - let magic = &hdr[0..4]; - - if &magic != b"PFSC" { - return Err(OpenError::InvalidMagic); - } - - // Read header. - let block_size = LE::read_u32(&hdr[0x0c..]); // BlockSz - let original_block_size = LE::read_u64(&hdr[0x10..]); // BlockSz2 - let block_offsets = LE::read_u64(&hdr[0x18..]); // BlockOffsets - let original_size = LE::read_u64(&hdr[0x28..]); // DataLength - - // Read block offsets. - if let Err(e) = file.seek(SeekFrom::Start(block_offsets)) { - return Err(OpenError::SeekFailed(block_offsets, e)); - } - - let original_block_count = original_size / original_block_size + 1; - let mut compressed_blocks: Vec = vec![0; original_block_count as usize]; - let buf = unsafe { - let (pre, mid, pos) = compressed_blocks.align_to_mut::(); - - assert!(pre.is_empty()); - assert!(pos.is_empty()); - - mid - }; - - if let Err(e) = file.read_exact(buf) { - return Err(OpenError::ReadBlockMappingFailed(e)); - } - - Ok(Self { - file, - block_size, - original_block_size, - compressed_blocks, - original_size, - current_offset: 0, - current_block: Vec::new(), - }) - } - - pub fn len(&self) -> u64 { - self.original_size - } - - pub fn is_empty(&self) -> bool { - self.original_size == 0 - } - - fn read_compressed_block(&mut self, num: u64) -> std::io::Result<()> { - // Get end offset. - let end = match self.compressed_blocks.get(num as usize + 1) { - Some(v) => v, - None => return Err(std::io::Error::from(ErrorKind::InvalidInput)), - }; - - // Get start offset and compressed size. - let offset = self.compressed_blocks[num as usize]; - let size = end - offset; - - #[allow(clippy::uninit_vec)] - { - // calling `set_len()` immediately after reserving a buffer creates uninitialized values - // Allocate buffer. - self.current_block.reserve(self.block_size as usize); - unsafe { self.current_block.set_len(self.block_size as usize) }; - } - - let buf = self.current_block.as_mut_slice(); - - // Check if block compressed. - match size.cmp(&self.original_block_size) { - std::cmp::Ordering::Less => { - // Read compressed. - let mut compressed = vec![0; size as usize]; - - self.file.seek(SeekFrom::Start(offset))?; - self.file.read_exact(&mut compressed)?; - - // Decompress. - let mut deflate = flate2::Decompress::new(true); - let status = match deflate.decompress(&compressed, buf, FlushDecompress::Finish) { - Ok(v) => v, - Err(e) => return Err(std::io::Error::new(ErrorKind::Other, e)), - }; - - if status != flate2::Status::StreamEnd || deflate.total_out() as usize != buf.len() - { - return Err(std::io::Error::new( - ErrorKind::Other, - format!("invalid data on block #{}", num), - )); - } - } - std::cmp::Ordering::Equal => { - self.file.seek(SeekFrom::Start(offset))?; - self.file.read_exact(buf)?; - } - std::cmp::Ordering::Greater => buf.fill(0), - } - - Ok(()) - } -} - -impl Seek for Reader { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result { - use std::io::Error; - - // Calculate the new offset. - let offset = match pos { - SeekFrom::Start(v) => min(v, self.original_size), - SeekFrom::End(v) => { - if v >= 0 { - self.original_size - } else { - match self.original_size.checked_sub(v.unsigned_abs()) { - Some(v) => v, - None => return Err(Error::from(ErrorKind::InvalidInput)), - } - } - } - SeekFrom::Current(v) => { - if v >= 0 { - min(self.current_offset + (v as u64), self.original_size) - } else { - match self.current_offset.checked_sub(v.unsigned_abs()) { - Some(v) => v, - None => return Err(Error::from(ErrorKind::InvalidInput)), - } - } - } - }; - - // Check if we need to update the offset. - if offset != self.current_offset { - self.current_offset = offset; - self.current_block.clear(); - } - - Ok(offset) - } - - fn rewind(&mut self) -> std::io::Result<()> { - self.current_offset = 0; - self.current_block.clear(); - Ok(()) - } - - fn stream_position(&mut self) -> std::io::Result { - Ok(self.current_offset) - } -} - -impl Read for Reader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - if buf.is_empty() || self.current_offset == self.original_size { - return Ok(0); - } - - // Copy data. - let mut copied = 0usize; - - loop { - // Load block for current offset. - if self.current_block.is_empty() { - // Load block data. - let block_index = self.current_offset / (self.block_size as u64); - - self.read_compressed_block(block_index)?; - - // Check if this is a last block. - let total = block_index * (self.block_size as u64) + (self.block_size as u64); - - if total > self.original_size { - let need = (self.block_size as u64) - (total - self.original_size); - self.current_block.truncate(need as usize); - }; - } - - // Get a window into current block from current offset. - let offset = self.current_offset % (self.block_size as u64); - let src = &self.current_block[(offset as usize)..]; - - // Copy the window to output buffer. - let dst = unsafe { buf.as_mut_ptr().add(copied) }; - let amount = min(src.len(), buf.len() - copied); - - unsafe { dst.copy_from_nonoverlapping(src.as_ptr(), amount) }; - copied += amount; - - // Advance current offset. - self.current_offset += amount as u64; - - if self.current_offset % (self.block_size as u64) == 0 { - self.current_block.clear(); - } - - // Check if completed. - if copied == buf.len() || self.current_offset == self.original_size { - break Ok(copied); - } - } - } -} - -#[derive(Debug)] -pub enum OpenError { - SeekFailed(u64, std::io::Error), - IoFailed(std::io::Error), - TooSmall, - InvalidMagic, - ReadBlockMappingFailed(std::io::Error), -} - -impl Error for OpenError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::SeekFailed(_, e) => Some(e), - Self::IoFailed(e) => Some(e), - Self::ReadBlockMappingFailed(e) => Some(e), - _ => None, - } - } -} - -impl Display for OpenError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match self { - Self::SeekFailed(p, _) => write!(f, "cannot seek to offset {}", p), - Self::IoFailed(_) => f.write_str("I/O failed"), - Self::TooSmall => f.write_str("data too small"), - Self::InvalidMagic => f.write_str("invalid magic"), - Self::ReadBlockMappingFailed(_) => f.write_str("cannot read block mapping"), - } - } -}