Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:mrkcee
asahi-bless
asahi-nvram-0.3.0.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File asahi-nvram-0.3.0.obscpio of Package asahi-bless
07070100000000000081A400000000000000000000000165CB904F00000008000000000000000000000000000000000000001D00000000asahi-nvram-0.3.0/.gitignore/target 07070100000001000081A400000000000000000000000165CB904F00003979000000000000000000000000000000000000001D00000000asahi-nvram-0.3.0/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler32" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom", "once_cell", "version_check", ] [[package]] name = "anstream" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3a318f1f38d2418400f8209655bfd825785afd25aa30bb7ba6cc792e4596748" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "apple-nvram" version = "0.2.1" dependencies = [ "adler32", "crc32fast", "nix", ] [[package]] name = "asahi-bless" version = "0.3.0" dependencies = [ "apple-nvram", "clap 4.4.11", "gpt", "uuid", ] [[package]] name = "asahi-btsync" version = "0.2.0" dependencies = [ "apple-nvram", "clap 3.2.25", "rust-ini", ] [[package]] name = "asahi-nvram" version = "0.2.1" dependencies = [ "apple-nvram", "clap 3.2.25", ] [[package]] name = "asahi-wifisync" version = "0.2.0" dependencies = [ "apple-nvram", "clap 3.2.25", "rust-ini", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap", "once_cell", "strsim", "termcolor", "textwrap", ] [[package]] name = "clap" version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", "clap_lex 0.6.0", "strsim", ] [[package]] name = "clap_derive" version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "crc" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "dlv-list" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gpt" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" dependencies = [ "bitflags 2.3.3", "crc", "log", "uuid", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset", "pin-utils", "static_assertions", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "ordered-multimap" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" dependencies = [ "dlv-list", "hashbrown", ] [[package]] name = "os_str_bytes" version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rust-ini" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" dependencies = [ "cfg-if", "ordered-multimap", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" dependencies = [ "getrandom", ] [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 07070100000002000081A400000000000000000000000165CB904F000000BB000000000000000000000000000000000000001D00000000asahi-nvram-0.3.0/Cargo.toml[workspace] members = [ "apple-nvram", "asahi-nvram", "asahi-btsync", "asahi-bless", "asahi-wifisync" ] resolver = "2" [profile.release] lto = true panic = "abort" 07070100000003000081A400000000000000000000000165CB904F0000043C000000000000000000000000000000000000001A00000000asahi-nvram-0.3.0/LICENSEMIT License Copyright (c) 2022 The Asahi Linux Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.07070100000004000081A400000000000000000000000165CB904F0000006D000000000000000000000000000000000000001C00000000asahi-nvram-0.3.0/README.md# asahi-nvram Nvram reader/writer for arm macs. May or may not result in you having to perform a dfu restore.07070100000005000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000001E00000000asahi-nvram-0.3.0/apple-nvram07070100000006000081A400000000000000000000000165CB904F000001CB000000000000000000000000000000000000002900000000asahi-nvram-0.3.0/apple-nvram/Cargo.toml[package] name = "apple-nvram" version = "0.2.1" edition = "2021" license = "MIT" description = "A library to parse and write apple-formatted nvram entries" homepage = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" repository = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] adler32 = "1" crc32fast = "1.3.2" nix = "0.26" 070701000000070000A1FF0000000000000000000000016635A1100000000A000000000000000000000000000000000000002600000000asahi-nvram-0.3.0/apple-nvram/LICENSE../LICENSE07070100000008000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000002200000000asahi-nvram-0.3.0/apple-nvram/src07070100000009000081A400000000000000000000000165CB904F00000988000000000000000000000000000000000000002900000000asahi-nvram-0.3.0/apple-nvram/src/lib.rs// SPDX-License-Identifier: MIT use std::{ borrow::Cow, fmt::{Debug, Display, Formatter}, }; pub mod mtd; pub mod v1v2; pub mod v3; fn chrp_checksum_add(lhs: u8, rhs: u8) -> u8 { let (out, carry) = lhs.overflowing_add(rhs); if carry { out + 1 } else { out } } fn slice_rstrip<'a, T: PartialEq<T>>(mut ts: &'a [T], t: &T) -> &'a [T] { while let Some(last) = ts.last() { if last == t { ts = ts.split_last().unwrap().1; } else { break; } } ts } fn slice_find<T: PartialEq<T>>(ts: &[T], t: &T) -> Option<usize> { let mut ret = None; for (i, v) in ts.iter().enumerate() { if v == t { ret = Some(i); break; } } ret } #[derive(Debug)] pub enum Error { ParseError, SectionTooBig, ApplyError(std::io::Error), } type Result<T> = std::result::Result<T, Error>; #[derive(Clone, Copy, PartialEq)] pub enum VarType { Common, System, } impl Display for VarType { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { match self { &VarType::Common => write!(f, "common"), &VarType::System => write!(f, "system"), } } } pub fn nvram_parse<'a>(nvr: &'a [u8]) -> Result<Box<dyn Nvram<'a> + 'a>> { match (v3::Nvram::parse(nvr), v1v2::Nvram::parse(nvr)) { (Ok(nvram_v3), Err(_)) => Ok(Box::new(nvram_v3)), (Err(_), Ok(nvram_v1v2)) => Ok(Box::new(nvram_v1v2)), _ => Err(Error::ParseError), } } pub trait NvramWriter { fn erase_if_needed(&mut self, offset: u32, size: usize); fn write_all(&mut self, offset: u32, buf: &[u8]) -> std::io::Result<()>; } pub trait Nvram<'a> { fn prepare_for_write(&mut self); fn active_part_mut(&mut self) -> &mut dyn Partition<'a>; fn partitions(&self) -> Box<dyn Iterator<Item = &dyn Partition<'a>> + '_>; fn serialize(&self) -> Result<Vec<u8>>; fn apply(&mut self, w: &mut dyn NvramWriter) -> Result<()>; } pub trait Partition<'a>: Display { fn variables(&self) -> Box<dyn Iterator<Item = &dyn Variable<'a>> + '_>; fn get_variable(&self, key: &'a [u8], typ: VarType) -> Option<&dyn Variable<'a>>; fn insert_variable(&mut self, key: &'a [u8], value: Cow<'a, [u8]>, typ: VarType); fn remove_variable(&mut self, key: &'a [u8], typ: VarType); } pub trait Variable<'a>: Display { fn value(&self) -> Cow<'a, [u8]>; } 0707010000000A000081A400000000000000000000000165CB904F0000052E000000000000000000000000000000000000002900000000asahi-nvram-0.3.0/apple-nvram/src/mtd.rsuse std::{ fs::File, io::{Seek, SeekFrom, Write}, os::unix::io::AsRawFd, }; use crate::NvramWriter; pub struct MtdWriter { file: File, } impl MtdWriter { pub fn new(file: File) -> MtdWriter { MtdWriter { file } } } impl NvramWriter for MtdWriter { fn erase_if_needed(&mut self, offset: u32, size: usize) { erase_if_needed(&self.file, offset, size); } fn write_all(&mut self, offset: u32, buf: &[u8]) -> std::io::Result<()> { self.file.seek(SeekFrom::Start(offset as u64))?; self.file.write_all(buf)?; Ok(()) } } #[repr(C)] pub struct EraseInfoUser { start: u32, length: u32, } #[repr(C)] #[derive(Default)] pub struct MtdInfoUser { ty: u8, flags: u32, size: u32, erasesize: u32, writesize: u32, oobsize: u32, padding: u64, } nix::ioctl_write_ptr!(mtd_mem_erase, b'M', 2, EraseInfoUser); nix::ioctl_read!(mtd_mem_get_info, b'M', 1, MtdInfoUser); fn erase_if_needed(file: &File, offset: u32, size: usize) { if unsafe { mtd_mem_get_info(file.as_raw_fd(), &mut MtdInfoUser::default()) }.is_err() { return; } let erase_info = EraseInfoUser { start: offset, length: size as u32, }; unsafe { mtd_mem_erase(file.as_raw_fd(), &erase_info).unwrap(); } } 0707010000000B000081A400000000000000000000000165CB904F00002F6F000000000000000000000000000000000000002A00000000asahi-nvram-0.3.0/apple-nvram/src/v1v2.rsuse std::{ borrow::Cow, collections::HashMap, fmt::{Debug, Display, Formatter}, }; use crate::{chrp_checksum_add, slice_find, slice_rstrip, Error, Result, VarType}; pub struct UnescapeVal<I> { inner: I, esc_out: u8, remaining: u8, } impl<I> UnescapeVal<I> where I: Iterator<Item = u8>, { pub fn new(inner: I) -> Self { Self { inner, esc_out: 0, remaining: 0, } } } impl<I> Iterator for UnescapeVal<I> where I: Iterator<Item = u8>, { type Item = u8; fn next(&mut self) -> Option<u8> { if self.remaining != 0 { self.remaining -= 1; return Some(self.esc_out); } if let Some(n) = self.inner.next() { if n != 0xFF { return Some(n); } let count = self.inner.next()?; self.esc_out = if count & 0x80 == 0 { 0 } else { 0xFF }; self.remaining = (count & 0x7F) - 1; Some(self.esc_out) } else { None } } } #[derive(Clone)] pub struct CHRPHeader<'a> { pub name: &'a [u8], pub size: u16, pub signature: u8, } impl Debug for CHRPHeader<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("CHRPHeader") .field("name", &String::from_utf8_lossy(self.name).into_owned()) .field("size", &self.size) .field("signature", &self.signature) .finish() } } impl CHRPHeader<'_> { pub fn parse(nvr: &[u8]) -> Result<CHRPHeader<'_>> { let signature = nvr[0]; let cksum = nvr[1]; let size = u16::from_le_bytes(nvr[2..4].try_into().unwrap()); let name = slice_rstrip(&nvr[4..16], &0); let cand = CHRPHeader { name, size, signature, }; if cand.checksum() != cksum { return Err(Error::ParseError); } Ok(cand) } fn checksum(&self) -> u8 { let mut cksum = 0; for &u in self.name { cksum = chrp_checksum_add(cksum, u); } cksum = chrp_checksum_add(cksum, self.signature); cksum = chrp_checksum_add(cksum, (self.size & 0xFF) as u8); chrp_checksum_add(cksum, (self.size >> 8) as u8) } pub fn serialize(&self, v: &mut Vec<u8>) { v.push(self.signature); v.push(self.checksum()); v.extend_from_slice(&self.size.to_le_bytes()); v.extend_from_slice(self.name); for _ in 0..(12 - self.name.len()) { v.push(0); } } } #[derive(Clone)] pub struct Variable<'a> { pub key: &'a [u8], pub value: Cow<'a, [u8]>, pub typ: VarType, } impl<'a> Variable<'a> { pub fn new(key: &'a [u8], value: &'a [u8], typ: VarType) -> Variable<'a> { Variable { key, value: Cow::Borrowed(value), typ, } } } impl<'a> crate::Variable<'a> for Variable<'a> { fn value(&self) -> Cow<'a, [u8]> { Cow::Owned(UnescapeVal::new(self.value.iter().copied()).collect()) } } impl Display for Variable<'_> { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { let key = String::from_utf8_lossy(self.key); let mut value = String::new(); for c in UnescapeVal::new(self.value.iter().copied()) { if (c as char).is_ascii() && !(c as char).is_ascii_control() { value.push(c as char); } else { value.push_str(&format!("%{c:02x}")); } } let value: String = value.chars().take(128).collect(); write!(f, "{}:{}={}", self.typ, key, value) } } #[derive(Clone)] pub struct Section<'a> { pub header: CHRPHeader<'a>, pub values: HashMap<&'a [u8], Variable<'a>>, } impl Section<'_> { pub fn parse(mut nvr: &[u8]) -> Result<Section<'_>> { let header = CHRPHeader::parse(&nvr[..16])?; nvr = &nvr[16..]; let mut values = HashMap::new(); loop { let zero = slice_find(nvr, &0); if zero.is_none() { break; } let zero = zero.unwrap(); let cand = &nvr[..zero]; let eq = slice_find(cand, &b'='); if eq.is_none() { break; } let eq = eq.unwrap(); let key = &cand[..eq]; let typ = if header.name == b"common" { VarType::Common } else { VarType::System }; values.insert(key, Variable::new(key, &cand[(eq + 1)..], typ)); nvr = &nvr[(zero + 1)..] } Ok(Section { header, values }) } fn size_bytes(&self) -> usize { self.header.size as usize * 16 } pub fn serialize(&self, v: &mut Vec<u8>) -> Result<()> { let start_size = v.len(); self.header.serialize(v); for val in self.values.values() { v.extend_from_slice(val.key); v.push(b'='); v.extend_from_slice(&val.value); v.push(0); } let my_size = v.len() - start_size; if my_size > self.size_bytes() { return Err(Error::SectionTooBig); } for _ in 0..(self.size_bytes() - my_size) { v.push(0); } Ok(()) } } struct SectionDebug<'a, 'b>(&'a Section<'b>); impl Debug for SectionDebug<'_, '_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut m = f.debug_map(); for v in self.0.values.values() { m.entry( &String::from_utf8_lossy(v.key).into_owned(), &String::from_utf8_lossy( &UnescapeVal::new(v.value.iter().copied()).collect::<Vec<_>>(), ) .into_owned(), ); } m.finish() } } impl Debug for Section<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("Section") .field("header", &self.header) .field("values", &SectionDebug(self)) .finish() } } #[derive(Debug, Clone)] pub struct Partition<'a> { pub header: CHRPHeader<'a>, pub generation: u32, pub common: Section<'a>, pub system: Section<'a>, } impl<'a> Partition<'a> { pub fn parse(nvr: &[u8]) -> Result<Partition<'_>> { let header = CHRPHeader::parse(&nvr[..16])?; if header.name != b"nvram" { return Err(Error::ParseError); } let adler = u32::from_le_bytes(nvr[16..20].try_into().unwrap()); let generation = u32::from_le_bytes(nvr[20..24].try_into().unwrap()); let sec1 = Section::parse(&nvr[32..])?; let sec2 = Section::parse(&nvr[(32 + sec1.size_bytes())..])?; let calc_adler = adler32::adler32(&nvr[20..(32 + sec1.size_bytes() + sec2.size_bytes())]).unwrap(); if adler != calc_adler { return Err(Error::ParseError); } let mut com = None; let mut sys = None; if sec1.header.name == b"common" { com = Some(sec1); } else if sec1.header.name == b"system" { sys = Some(sec1); } if sec2.header.name == b"common" { com = Some(sec2); } else if sec2.header.name == b"system" { sys = Some(sec2); } if com.is_none() || sys.is_none() { return Err(Error::ParseError); } Ok(Partition { header, generation, common: com.unwrap(), system: sys.unwrap(), }) } fn size_bytes(&self) -> usize { 32 + self.common.size_bytes() + self.system.size_bytes() } pub fn serialize(&self, v: &mut Vec<u8>) -> Result<()> { self.header.serialize(v); v.extend_from_slice(&[0; 4]); let adler_start = v.len(); v.extend_from_slice(&self.generation.to_le_bytes()); v.extend_from_slice(&[0; 8]); self.common.serialize(v)?; self.system.serialize(v)?; let adler_end = v.len(); let adler = adler32::adler32(&v[adler_start..adler_end]).unwrap(); v[(adler_start - 4)..adler_start].copy_from_slice(&adler.to_le_bytes()); Ok(()) } pub fn variables(&self) -> impl Iterator<Item = &Variable<'a>> { self.common .values .values() .chain(self.system.values.values()) } } impl<'a> crate::Partition<'a> for Partition<'a> { fn get_variable(&self, key: &[u8], typ: VarType) -> Option<&dyn crate::Variable<'a>> { match typ { VarType::Common => self .common .values .get(key) .map(|v| v as &dyn crate::Variable), VarType::System => self .system .values .get(key) .map(|v| v as &dyn crate::Variable), } } fn insert_variable(&mut self, key: &'a [u8], value: Cow<'a, [u8]>, typ: VarType) { match typ { VarType::Common => &mut self.common, VarType::System => &mut self.system, } .values .insert(key, Variable { key, value, typ }); } fn remove_variable(&mut self, key: &'a [u8], typ: VarType) { match typ { VarType::Common => &mut self.common, VarType::System => &mut self.system, } .values .remove(key); } fn variables(&self) -> Box<dyn Iterator<Item = &dyn crate::Variable<'a>> + '_> { Box::new(self.variables().map(|e| e as &dyn crate::Variable<'a>)) } } impl Display for Partition<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "size: {}, generation: {}, count: {}", self.header.size, self.generation, self.common.values.len() + self.system.values.len(), ) } } #[derive(Debug)] pub struct Nvram<'a> { pub partitions: [Partition<'a>; 2], pub active: usize, } impl<'a> Nvram<'a> { pub fn parse(nvr: &[u8]) -> Result<Nvram<'_>> { let p1; let p2; match (Partition::parse(nvr), Partition::parse(&nvr[0x10000..])) { (Err(err), Err(_)) => return Err(err), (Ok(p1r), Err(_)) => { p1 = p1r; p2 = p1.clone(); } (Err(_), Ok(p2r)) => { p2 = p2r; p1 = p2.clone(); } (Ok(p1r), Ok(p2r)) => { p1 = p1r; p2 = p2r; } } let active = if p1.generation > p2.generation { 0 } else { 1 }; let partitions = [p1, p2]; Ok(Nvram { partitions, active }) } pub fn partitions(&self) -> impl Iterator<Item = &Partition<'a>> { self.partitions.iter() } } impl<'a> crate::Nvram<'a> for Nvram<'a> { fn serialize(&self) -> Result<Vec<u8>> { let mut v = Vec::with_capacity(self.partitions[0].size_bytes() * 2); self.partitions[0].serialize(&mut v)?; self.partitions[1].serialize(&mut v)?; Ok(v) } fn prepare_for_write(&mut self) { let inactive = 1 - self.active; self.partitions[inactive] = self.partitions[self.active].clone(); self.partitions[inactive].generation += 1; self.active = inactive; } // fn active_part(&self) -> &Partition<'a> { // &self.partitions[self.active] // } fn active_part_mut(&mut self) -> &mut dyn crate::Partition<'a> { &mut self.partitions[self.active] as &mut dyn crate::Partition<'a> } fn partitions(&self) -> Box<dyn Iterator<Item = &dyn crate::Partition<'a>> + '_> { Box::new(self.partitions().map(|e| e as &dyn crate::Partition<'a>)) } fn apply(&mut self, w: &mut dyn crate::NvramWriter) -> Result<()> { let data = self.serialize()?; w.erase_if_needed(0, data.len()); w.write_all(0, &data).map_err(|e| Error::ApplyError(e))?; Ok(()) } } 0707010000000C000081A400000000000000000000000165CB904F00006CE2000000000000000000000000000000000000002800000000asahi-nvram-0.3.0/apple-nvram/src/v3.rsuse std::{ borrow::Cow, fmt::{Display, Formatter}, ops::ControlFlow, }; use crate::{Error, VarType}; // https://github.com/apple-oss-distributions/xnu/blob/main/iokit/Kernel/IONVRAMV3Handler.cpp#L630 const VARIABLE_STORE_SIGNATURE: &[u8; 4] = b"3VVN"; const VARIABLE_STORE_VERSION: u8 = 0x1; const VARIABLE_DATA: u16 = 0x55AA; const PARTITION_SIZE: usize = 0x10000; const STORE_HEADER_SIZE: usize = 24; const VAR_HEADER_SIZE: usize = 36; const VAR_ADDED: u8 = 0x7F; const VAR_IN_DELETED_TRANSITION: u8 = 0xFE; const VAR_DELETED: u8 = 0xFD; const APPLE_COMMON_VARIABLE_GUID: &[u8; 16] = &[ 0x7C, 0x43, 0x61, 0x10, 0xAB, 0x2A, 0x4B, 0xBB, 0xA8, 0x80, 0xFE, 0x41, 0x99, 0x5C, 0x9F, 0x82, ]; const APPLE_SYSTEM_VARIABLE_GUID: &[u8; 16] = &[ 0x40, 0xA0, 0xDD, 0xD2, 0x77, 0xF8, 0x43, 0x92, 0xB4, 0xA3, 0x1E, 0x73, 0x04, 0x20, 0x65, 0x16, ]; #[derive(Debug, Default)] enum Slot<T> { Valid(T), Invalid, #[default] Empty, } impl<T> Slot<T> { pub const fn as_ref(&self) -> Slot<&T> { match self { Slot::Valid(v) => Slot::Valid(v), Slot::Invalid => Slot::Invalid, Slot::Empty => Slot::Empty, } } pub fn as_mut(&mut self) -> Slot<&mut T> { match self { Slot::Valid(v) => Slot::Valid(v), Slot::Invalid => Slot::Invalid, Slot::Empty => Slot::Empty, } } pub fn unwrap(self) -> T { match self { Slot::Valid(v) => v, Slot::Invalid => panic!("called `Slot::unwrap()` on an `Invalid` value"), Slot::Empty => panic!("called `Slot::unwrap()` on an `Empty` value"), } } pub fn empty(&self) -> bool { match self { Slot::Empty => true, _ => false, } } } #[derive(Debug)] pub struct Nvram<'a> { partitions: [Slot<Partition<'a>>; 16], partition_count: usize, active: usize, } impl<'a> Nvram<'a> { pub fn parse(nvr: &'a [u8]) -> crate::Result<Nvram<'_>> { let partition_count = nvr.len() / PARTITION_SIZE; let mut partitions: [Slot<Partition<'a>>; 16] = Default::default(); let mut active = 0; let mut max_gen = 0; let mut valid_partitions = 0; for i in 0..partition_count { let offset = i * PARTITION_SIZE; if offset >= nvr.len() { break; } match Partition::parse(&nvr[offset..offset + PARTITION_SIZE]) { Ok(p) => { let p_gen = p.generation(); if p_gen > max_gen { active = i; max_gen = p_gen; } partitions[i] = Slot::Valid(p); valid_partitions += 1; } Err(V3Error::Empty) => { partitions[i] = Slot::Empty; } Err(_) => { partitions[i] = Slot::Invalid; } } } if valid_partitions == 0 { return Err(Error::ParseError); } Ok(Nvram { partitions, partition_count, active, }) } fn partitions(&self) -> impl Iterator<Item = &Partition<'a>> { self.partitions .iter() .take(self.partition_count) .filter_map(|x| match x { Slot::Valid(p) => Some(p), Slot::Invalid => None, Slot::Empty => None, }) } fn active_part(&self) -> &Partition<'a> { self.partitions[self.active].as_ref().unwrap() } #[cfg(test)] fn active_part_mut(&mut self) -> &mut Partition<'a> { self.partitions[self.active].as_mut().unwrap() } } impl<'a> crate::Nvram<'a> for Nvram<'a> { fn serialize(&self) -> crate::Result<Vec<u8>> { let mut v = Vec::with_capacity(self.partition_count * PARTITION_SIZE); for p in self.partitions() { p.serialize(&mut v); } Ok(v) } fn prepare_for_write(&mut self) { // nop } fn partitions(&self) -> Box<dyn Iterator<Item = &dyn crate::Partition<'a>> + '_> { Box::new(self.partitions().map(|p| p as &dyn crate::Partition<'a>)) } fn active_part_mut(&mut self) -> &mut dyn crate::Partition<'a> { self.partitions[self.active].as_mut().unwrap() } fn apply(&mut self, w: &mut dyn crate::NvramWriter) -> crate::Result<()> { let ap = self.active_part(); let offset; // there aren't really any sections in v3 but the store header still // specifies limits for maximum combined size of each kind of variable if ap.system_used() > ap.system_size() { return Err(Error::SectionTooBig); } if ap.common_used() > ap.common_size() { return Err(Error::SectionTooBig); } // if total size is too big, copy added variables to the next bank if ap.total_used() <= ap.usable_size() { offset = (self.active * PARTITION_SIZE) as u32; } else { let new_active = (self.active + 1) % self.partition_count; offset = (new_active * PARTITION_SIZE) as u32; if !self.partitions[new_active].empty() { w.erase_if_needed(offset, PARTITION_SIZE); } // must only clone 0x7F variables to the next partition self.partitions[new_active] = Slot::Valid( self.partitions[self.active] .as_ref() .unwrap() .clone_active(), ); self.active = new_active; // we could still have too many active variables if self.active_part().total_used() > PARTITION_SIZE { return Err(Error::SectionTooBig); } } let mut data = Vec::with_capacity(PARTITION_SIZE); self.active_part().serialize(&mut data); w.write_all(offset, &data) .map_err(|e| Error::ApplyError(e))?; Ok(()) } } #[derive(Debug, Clone)] pub struct Partition<'a> { pub header: StoreHeader<'a>, pub values: Vec<Variable<'a>>, empty_region_end: usize, } #[derive(Debug)] enum V3Error { ParseError, Empty, } type Result<T> = std::result::Result<T, V3Error>; impl<'a> Partition<'a> { fn parse(nvr: &'a [u8]) -> Result<Partition<'a>> { if let Ok(header) = StoreHeader::parse(&nvr[..STORE_HEADER_SIZE]) { let mut offset = STORE_HEADER_SIZE; let mut values = Vec::new(); // one byte past the last 0xFF or the end of partition let mut empty_region_end = header.size(); while offset + VAR_HEADER_SIZE < header.size() { let mut empty = true; for i in 0..VAR_HEADER_SIZE { if nvr[offset + i] != 0 && nvr[offset + i] != 0xFF { empty = false; break; } } if empty { // check where exactly the "empty" space ends // (it might not be at the end of partition) offset += VAR_HEADER_SIZE; while offset < header.size() { if nvr[offset] != 0xFF { empty_region_end = offset; break; } offset += 1 } break; } let Ok(v_header) = VarHeader::parse(&nvr[offset..]) else { // if there's no valid header, just end here and return values parsed so far // we also know there is no space for adding any new or updated variables empty_region_end = offset; break; }; let k_begin = offset + VAR_HEADER_SIZE; let k_end = k_begin + v_header.name_size as usize; let key = &nvr[k_begin..k_end - 1]; let v_begin = k_end; let v_end = v_begin + v_header.data_size as usize; let value = &nvr[v_begin..v_end]; let crc = crc32fast::hash(value); if crc != v_header.crc { return Err(V3Error::ParseError); } let v = Variable { header: v_header, key, value: Cow::Borrowed(value), }; offset += v.size(); values.push(v); } Ok(Partition { header, values, empty_region_end, }) } else { match nvr.iter().copied().try_for_each(|v| match v { 0xFF => ControlFlow::Continue(()), _ => ControlFlow::Break(()), }) { ControlFlow::Continue(_) => Err(V3Error::Empty), ControlFlow::Break(_) => Err(V3Error::ParseError), } } } fn generation(&self) -> u32 { self.header.generation } fn entries(&mut self, key: &'a [u8], typ: VarType) -> impl Iterator<Item = &mut Variable<'a>> { self.values .iter_mut() .filter(move |e| e.key == key && e.typ() == typ) } fn entries_added( &mut self, key: &'a [u8], typ: VarType, ) -> impl Iterator<Item = &mut Variable<'a>> { self.entries(key, typ) .filter(|v| v.header.state == VAR_ADDED) } // total size of store header + all variables including the inactive duplicates fn total_used(&self) -> usize { STORE_HEADER_SIZE + self.values.iter().fold(0, |acc, v| acc + v.size()) } // size of active system variables fn system_used(&self) -> usize { self.values .iter() .filter(|&v| v.header.state == VAR_ADDED && v.header.guid == APPLE_SYSTEM_VARIABLE_GUID) .fold(0, |acc, v| acc + v.size()) } // size of active common variables fn common_used(&self) -> usize { self.values .iter() .filter(|&v| v.header.state == VAR_ADDED && v.header.guid == APPLE_COMMON_VARIABLE_GUID) .fold(0, |acc, v| acc + v.size()) } fn system_size(&self) -> usize { self.header.system_size as usize } fn common_size(&self) -> usize { self.header.common_size as usize } // total usable size, usually equal to partition size // unless there are any non-0xFF bytes after last valid variable fn usable_size(&self) -> usize { self.empty_region_end } fn serialize(&self, v: &mut Vec<u8>) { let start_size = v.len(); self.header.serialize(v); // Here we actually want to iterate over all versions of variables so we use the struct field directly. for var in &self.values { var.serialize(v); } let my_size = v.len() - start_size; debug_assert!(v.len() == self.total_used()); // padding for _ in 0..(self.header.size() - my_size) { v.push(0xFF); } } fn variables(&self) -> impl Iterator<Item = &Variable<'a>> { self.values.iter().filter(|v| v.header.state == VAR_ADDED) } fn clone_active(&self) -> Partition<'a> { let mut header = self.header.clone(); header.generation += 1; Partition { header, values: self .values .iter() .filter_map(|v| { if v.header.state == VAR_ADDED { Some(v.clone()) } else { None } }) .collect(), empty_region_end: self.header.size(), } } } impl<'a> crate::Partition<'a> for Partition<'a> { fn get_variable(&self, key: &[u8], typ: VarType) -> Option<&dyn crate::Variable<'a>> { self.values.iter().find_map(|e| { if e.key == key && e.typ() == typ && e.header.state == VAR_ADDED { Some(e as &dyn crate::Variable<'a>) } else { None } }) } fn insert_variable(&mut self, key: &'a [u8], value: Cow<'a, [u8]>, typ: VarType) { // invalidate any previous variable instances for var in self.entries_added(key, typ) { var.header.state = var.header.state & VAR_DELETED & VAR_IN_DELETED_TRANSITION; } let guid = match typ { VarType::Common => APPLE_COMMON_VARIABLE_GUID, VarType::System => APPLE_SYSTEM_VARIABLE_GUID, }; let var = Variable { header: VarHeader { state: VAR_ADDED, attrs: 0, name_size: (key.len() + 1) as u32, data_size: value.len() as u32, guid, crc: crc32fast::hash(&value), }, key, value, }; self.values.push(var); } fn remove_variable(&mut self, key: &'a [u8], typ: VarType) { // invalidate all previous variable instances for var in self.entries_added(key, typ) { var.header.state = var.header.state & VAR_DELETED & VAR_IN_DELETED_TRANSITION; } } fn variables(&self) -> Box<dyn Iterator<Item = &dyn crate::Variable<'a>> + '_> { Box::new(self.variables().map(|e| e as &dyn crate::Variable<'a>)) } } impl Display for Partition<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "size: {}, total_used: {}, system_used: {}, common_used: {}, generation: 0x{:02x}, state: 0x{:02x}, flags: 0x{:02x}, count: {}", self.header.size, self.total_used(), self.system_used(), self.common_used(), self.generation(), self.header.state, self.header.flags, self.values.len() ) } } #[derive(Debug, Clone)] pub struct StoreHeader<'a> { pub name: &'a [u8], pub size: u32, pub generation: u32, pub state: u8, pub flags: u8, pub version: u8, pub system_size: u32, pub common_size: u32, } impl<'a> StoreHeader<'a> { fn parse(nvr: &[u8]) -> Result<StoreHeader<'_>> { let name = &nvr[..4]; let size = u32::from_le_bytes(nvr[4..8].try_into().unwrap()); let generation = u32::from_le_bytes(nvr[8..12].try_into().unwrap()); let state = nvr[12]; let flags = nvr[13]; let version = nvr[14]; let system_size = u32::from_le_bytes(nvr[16..20].try_into().unwrap()); let common_size = u32::from_le_bytes(nvr[20..24].try_into().unwrap()); if name != VARIABLE_STORE_SIGNATURE { return Err(V3Error::ParseError); } if version != VARIABLE_STORE_VERSION { return Err(V3Error::ParseError); } Ok(StoreHeader { name, size, generation, state, flags, version, system_size, common_size, }) } fn serialize(&self, v: &mut Vec<u8>) { v.extend_from_slice(VARIABLE_STORE_SIGNATURE); v.extend_from_slice(&self.size.to_le_bytes()); v.extend_from_slice(&self.generation.to_le_bytes()); v.push(self.state); v.push(self.flags); v.push(self.version); v.push(0); // reserved v.extend_from_slice(&self.system_size.to_le_bytes()); v.extend_from_slice(&self.common_size.to_le_bytes()); } fn size(&self) -> usize { self.size as usize } } #[derive(Debug, Default, Clone)] pub struct Variable<'a> { pub header: VarHeader<'a>, pub key: &'a [u8], pub value: Cow<'a, [u8]>, } impl<'a> Variable<'a> { fn size(&self) -> usize { VAR_HEADER_SIZE + (self.header.name_size + self.header.data_size) as usize } fn typ(&self) -> VarType { if self.header.guid == APPLE_SYSTEM_VARIABLE_GUID { return VarType::System; } VarType::Common } fn serialize(&self, v: &mut Vec<u8>) { self.header.serialize(v); v.extend_from_slice(self.key); v.push(0); v.extend_from_slice(&self.value); } } impl<'a> crate::Variable<'a> for Variable<'a> { fn value(&self) -> Cow<'a, [u8]> { self.value.clone() } } impl Display for Variable<'_> { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { let key = String::from_utf8_lossy(self.key); let mut value = String::new(); for c in self.value.iter().copied() { if (c as char).is_ascii() && !(c as char).is_ascii_control() { value.push(c as char); } else { value.push_str(&format!("%{c:02x}")); } } write!(f, "{}:{}={}", self.typ(), key, value) } } #[derive(Debug, Default, Clone)] pub struct VarHeader<'a> { pub state: u8, pub attrs: u32, pub name_size: u32, pub data_size: u32, pub guid: &'a [u8], pub crc: u32, } impl<'a> VarHeader<'a> { fn parse(nvr: &[u8]) -> Result<VarHeader<'_>> { let start_id = u16::from_le_bytes(nvr[..2].try_into().unwrap()); if start_id != VARIABLE_DATA { return Err(V3Error::ParseError); } let state = nvr[2]; let attrs = u32::from_le_bytes(nvr[4..8].try_into().unwrap()); let name_size = u32::from_le_bytes(nvr[8..12].try_into().unwrap()); let data_size = u32::from_le_bytes(nvr[12..16].try_into().unwrap()); let guid = &nvr[16..32]; let crc = u32::from_le_bytes(nvr[32..36].try_into().unwrap()); if VAR_HEADER_SIZE + (name_size + data_size) as usize > nvr.len() { return Err(V3Error::ParseError); } Ok(VarHeader { state, attrs, name_size, data_size, guid, crc, }) } fn serialize(&self, v: &mut Vec<u8>) { v.extend_from_slice(&VARIABLE_DATA.to_le_bytes()); v.push(self.state); v.push(0); // reserved v.extend_from_slice(&self.attrs.to_le_bytes()); v.extend_from_slice(&self.name_size.to_le_bytes()); v.extend_from_slice(&self.data_size.to_le_bytes()); v.extend_from_slice(self.guid); v.extend_from_slice(&self.crc.to_le_bytes()); } } #[cfg(test)] mod tests { use super::*; use crate::{Nvram as NvramT, NvramWriter, Partition}; struct TestNvram { data: Vec<u8>, erase_count: usize, } impl TestNvram { fn new(data: Vec<u8>) -> TestNvram { Self { data, erase_count: 0, } } fn get_data(&self) -> &[u8] { &self.data } } impl NvramWriter for TestNvram { fn erase_if_needed(&mut self, offset: u32, size: usize) { for b in self.data.iter_mut().skip(offset as usize).take(size) { *b = 0xFF; } self.erase_count += 1; } fn write_all(&mut self, offset: u32, buf: &[u8]) -> std::io::Result<()> { for (d, s) in self .data .iter_mut() .skip(offset as usize) .take(buf.len()) .zip(buf.iter().copied()) { *d &= s; } Ok(()) } } #[rustfmt::skip] fn store_header() -> &'static [u8] { &[ 0x33, 0x56, 0x56, 0x4e, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x5a, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, ] } fn empty_nvram(bank_count: usize) -> Vec<u8> { let mut data = vec![0xFF; PARTITION_SIZE * bank_count]; data[0..STORE_HEADER_SIZE].copy_from_slice(store_header()); data } #[test] fn test_insert_variable() -> crate::Result<()> { let mut nvr = TestNvram::new(empty_nvram(2)); let data = nvr.get_data().to_owned(); let mut nv = Nvram::parse(&data)?; assert!(matches!(nv.partitions[0], Slot::Valid(_))); assert!(matches!(nv.partitions[1], Slot::Empty)); nv.active_part_mut().insert_variable( b"test-variable", Cow::Borrowed(b"test-value"), VarType::Common, ); // write changes nv.apply(&mut nvr)?; assert_eq!(nvr.erase_count, 0); // try to parse again let data_after = nvr.get_data().to_owned(); let mut nv_after = Nvram::parse(&data_after)?; let test_var = nv_after .active_part() .get_variable(b"test-variable", VarType::Common) .unwrap(); assert_eq!(test_var.value(), Cow::Borrowed(b"test-value")); let test_var_entries: Vec<_> = nv_after .active_part_mut() .entries(b"test-variable", VarType::Common) .collect(); assert_eq!(test_var_entries.len(), 1); assert_eq!(test_var_entries[0].header.state, VAR_ADDED); // update variable nv_after.active_part_mut().insert_variable( b"test-variable", Cow::Borrowed(b"test-value2"), VarType::Common, ); // write changes nv_after.apply(&mut nvr)?; assert_eq!(nvr.erase_count, 0); // try to parse again let data_after2 = nvr.get_data().to_owned(); let mut nv_after2 = Nvram::parse(&data_after2)?; let test_var2 = nv_after2 .active_part() .get_variable(b"test-variable", VarType::Common) .unwrap(); assert_eq!(test_var2.value(), Cow::Borrowed(b"test-value2")); let test_var2_entries: Vec<_> = nv_after2 .active_part_mut() .entries(b"test-variable", VarType::Common) .collect(); assert_eq!(test_var2_entries.len(), 2); assert_eq!( test_var2_entries[0].header.state, VAR_ADDED & VAR_DELETED & VAR_IN_DELETED_TRANSITION ); assert_eq!(test_var2_entries[1].header.state, VAR_ADDED); Ok(()) } #[test] fn test_write_to_next_bank() -> crate::Result<()> { let mut nvr = TestNvram::new(empty_nvram(2)); // write something to the second bank to force nvram erase later nvr.data[0x10000..0x10007].copy_from_slice(b"garbage"); let data = nvr.get_data().to_owned(); let mut nv = Nvram::parse(&data)?; assert!(matches!(nv.partitions[0], Slot::Valid(_))); assert!(matches!(nv.partitions[1], Slot::Invalid)); let orig_sys_val = vec![b'.'; 8192]; nv.active_part_mut().insert_variable( b"test-large-variable", Cow::Borrowed(&orig_sys_val), VarType::System, ); let orig_common_val = vec![b'.'; 24576]; nv.active_part_mut().insert_variable( b"test-large-variable", Cow::Borrowed(&orig_common_val), VarType::Common, ); // write changes nv.apply(&mut nvr)?; assert_eq!(nvr.erase_count, 0); assert_eq!(nv.active, 0); // try to parse again let data_after = nvr.get_data().to_owned(); let mut nv_after = Nvram::parse(&data_after)?; assert_eq!(nv_after.active_part().header.generation, 1); assert!(matches!(nv_after.partitions[0], Slot::Valid(_))); assert!(matches!(nv_after.partitions[1], Slot::Invalid)); // update variable let updated_sys_val = vec![b'.'; 9000]; nv_after.active_part_mut().insert_variable( b"test-large-variable", Cow::Borrowed(&updated_sys_val), VarType::System, ); let updated_common_val = vec![b'.'; 25000]; nv_after.active_part_mut().insert_variable( b"test-large-variable", Cow::Borrowed(&updated_common_val), VarType::Common, ); assert_eq!(nv_after.active_part().values.len(), 4); // write changes nv_after.apply(&mut nvr)?; assert_eq!(nvr.erase_count, 1); assert_eq!(nv_after.active, 1); assert_eq!(nv_after.active_part().values.len(), 2); // try to parse again let data_after2 = nvr.get_data().to_owned(); let nv_after2 = Nvram::parse(&data_after2)?; assert_eq!(nv_after2.active, 1); assert_eq!(nv_after2.active_part().values.len(), 2); assert_eq!(nv_after2.active_part().header.generation, 2); assert!(matches!(nv_after2.partitions[0], Slot::Valid(_))); assert!(matches!(nv_after2.partitions[1], Slot::Valid(_))); let test_sys_var2 = nv_after2 .active_part() .get_variable(b"test-large-variable", VarType::System) .unwrap(); let test_common_var2 = nv_after2 .active_part() .get_variable(b"test-large-variable", VarType::Common) .unwrap(); assert_eq!(test_sys_var2.value(), Cow::Borrowed(&updated_sys_val)); assert_eq!(test_common_var2.value(), Cow::Borrowed(&updated_common_val)); let test_old_sys_var = nv_after2.partitions[0] .as_ref() .unwrap() .get_variable(b"test-large-variable", VarType::System) .unwrap(); let test_old_common_var = nv_after2.partitions[0] .as_ref() .unwrap() .get_variable(b"test-large-variable", VarType::Common) .unwrap(); assert_eq!(test_old_sys_var.value(), Cow::Borrowed(&orig_sys_val)); assert_eq!(test_old_common_var.value(), Cow::Borrowed(&orig_common_val)); Ok(()) } #[test] fn test_insert_with_low_space() -> crate::Result<()> { let mut nvr = TestNvram::new(empty_nvram(2)); // this will shrink usable size to 100 bytes nvr.data[STORE_HEADER_SIZE + 100] = 0x42; let data = nvr.get_data().to_owned(); let mut nv = Nvram::parse(&data)?; assert!(matches!(nv.partitions[0], Slot::Valid(_))); assert!(matches!(nv.partitions[1], Slot::Empty)); nv.active_part_mut().insert_variable( b"test-variable", Cow::Borrowed(b"test-value"), VarType::Common, ); // write changes nv.apply(&mut nvr)?; assert_eq!(nvr.erase_count, 0); assert_eq!(nv.active, 0); // try to parse again let data_after = nvr.get_data().to_owned(); let mut nv_after = Nvram::parse(&data_after)?; let test_var = nv_after .active_part() .get_variable(b"test-variable", VarType::Common) .unwrap(); assert_eq!(test_var.value(), Cow::Borrowed(b"test-value")); let test_var_entries: Vec<_> = nv_after .active_part_mut() .entries(b"test-variable", VarType::Common) .collect(); assert_eq!(test_var_entries.len(), 1); assert_eq!(test_var_entries[0].header.state, VAR_ADDED); // update variable nv_after.active_part_mut().insert_variable( b"test-variable", Cow::Borrowed(b"test-value2"), VarType::Common, ); // write changes nv_after.apply(&mut nvr)?; assert_eq!(nvr.erase_count, 0); assert_eq!(nv_after.active, 1); Ok(()) } } 0707010000000D000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000001E00000000asahi-nvram-0.3.0/asahi-bless0707010000000E000081A400000000000000000000000165CB904F00000222000000000000000000000000000000000000002900000000asahi-nvram-0.3.0/asahi-bless/Cargo.toml[package] name = "asahi-bless" version = "0.3.0" edition = "2021" license = "MIT" description = "A tool to select active boot partition on ARM Macs" homepage = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" repository = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] uuid = "1" gpt = "3" clap = { version = "4.4.11", features = ["derive"] } [dependencies.apple-nvram] path = "../apple-nvram" version = "0.2" 0707010000000F0000A1FF0000000000000000000000016635A1100000000A000000000000000000000000000000000000002600000000asahi-nvram-0.3.0/asahi-bless/LICENSE../LICENSE07070100000010000081A400000000000000000000000165CB904F0000152D000000000000000000000000000000000000002800000000asahi-nvram-0.3.0/asahi-bless/offsets.c#include <inttypes.h> #include <stdio.h> #include <stddef.h> #define MAX_CKSUM_SIZE 8 typedef uint64_t oid_t; typedef uint64_t xid_t; typedef struct obj_phys { uint8_t o_cksum[MAX_CKSUM_SIZE]; oid_t o_oid; xid_t o_xid; uint32_t o_type; uint32_t o_subtype; } obj_phys_t; typedef int64_t paddr_t; typedef struct prange { paddr_t pr_start_paddr; uint64_t pr_block_count; } prange_t; typedef unsigned char uuid_t[16]; #define NX_MAX_FILE_SYSTEMS 100 typedef enum { NX_CNTR_OBJ_CKSUM_SET = 0, NX_CNTR_OBJ_CKSUM_FAIL = 1, NX_NUM_COUNTERS = 32 } nx_counter_id_t; #define NX_EPH_INFO_COUNT 4 typedef struct nx_superblock { obj_phys_t nx_o; uint32_t nx_magic; uint32_t nx_block_size; uint64_t nx_block_count; uint64_t nx_features; uint64_t nx_readonly_compatible_features; uint64_t nx_incompatible_features; uuid_t nx_uuid; oid_t nx_next_oid; xid_t nx_next_xid; uint32_t nx_xp_desc_blocks; uint32_t nx_xp_data_blocks; paddr_t nx_xp_desc_base; paddr_t nx_xp_data_base; uint32_t nx_xp_desc_next; uint32_t nx_xp_data_next; uint32_t nx_xp_desc_index; uint32_t nx_xp_desc_len; uint32_t nx_xp_data_index; uint32_t nx_xp_data_len; oid_t nx_spaceman_oid; oid_t nx_omap_oid; oid_t nx_reaper_oid; uint32_t nx_test_type; uint32_t nx_max_file_systems; oid_t nx_fs_oid[NX_MAX_FILE_SYSTEMS]; uint64_t nx_counters[NX_NUM_COUNTERS]; prange_t nx_blocked_out_prange; oid_t nx_evict_mapping_tree_oid; uint64_t nx_flags; paddr_t nx_efi_jumpstart; uuid_t nx_fusion_uuid; prange_t nx_keylocker; uint64_t nx_ephemeral_info[NX_EPH_INFO_COUNT]; oid_t nx_test_oid; oid_t nx_fusion_mt_oid; oid_t nx_fusion_wbc_oid; prange_t nx_fusion_wbc; uint64_t nx_newest_mounted_version; prange_t nx_mkb_locker; } nx_superblock_t; typedef struct omap_phys { obj_phys_t om_o; uint32_t om_flags; uint32_t om_snap_count; uint32_t om_tree_type; uint32_t om_snapshot_tree_type; oid_t om_tree_oid; oid_t om_snapshot_tree_oid; xid_t om_most_recent_snap; xid_t om_pending_revert_min; xid_t om_pending_revert_max; } omap_phys_t; typedef struct nloc { uint16_t off; uint16_t len; } nloc_t; typedef struct btree_node_phys { obj_phys_t btn_o; uint16_t btn_flags; uint16_t btn_level; uint32_t btn_nkeys; nloc_t btn_table_space; nloc_t btn_free_space; nloc_t btn_key_free_list; nloc_t btn_val_free_list; uint64_t btn_data[]; } btree_node_phys_t; typedef struct btree_info_fixed { uint32_t bt_flags; uint32_t bt_node_size; uint32_t bt_key_size; uint32_t bt_val_size; } btree_info_fixed_t; typedef struct btree_info { btree_info_fixed_t bt_fixed; uint32_t bt_longest_key; uint32_t bt_longest_val; uint64_t bt_key_count; uint64_t bt_node_count; } btree_info_t; typedef uint32_t crypto_flags_t; typedef uint32_t cp_key_class_t; typedef uint32_t cp_key_os_version_t; typedef uint16_t cp_key_revision_t; struct wrapped_meta_crypto_state { uint16_t major_version; uint16_t minor_version; crypto_flags_t cpflags; cp_key_class_t persistent_class; cp_key_os_version_t key_os_version; cp_key_revision_t key_revision; uint16_t unused; } __attribute__((aligned(2), packed)); typedef struct wrapped_meta_crypto_state wrapped_meta_crypto_state_t; #define APFS_MODIFIED_NAMELEN 32 typedef struct apfs_modified_by { uint8_t id[APFS_MODIFIED_NAMELEN]; uint64_t timestamp; xid_t last_xid; } apfs_modified_by_t; #define APFS_MAX_HIST 8 #define APFS_VOLNAME_LEN 256 typedef struct apfs_superblock { obj_phys_t apfs_o; uint32_t apfs_magic; uint32_t apfs_fs_index; uint64_t apfs_features; uint64_t apfs_readonly_compatible_features; uint64_t apfs_incompatible_features; uint64_t apfs_unmount_time; uint64_t apfs_fs_reserve_block_count; uint64_t apfs_fs_quota_block_count; uint64_t apfs_fs_alloc_count; wrapped_meta_crypto_state_t apfs_meta_crypto; uint32_t apfs_root_tree_type; uint32_t apfs_extentref_tree_type; uint32_t apfs_snap_meta_tree_type; oid_t apfs_omap_oid; oid_t apfs_root_tree_oid; oid_t apfs_extentref_tree_oid; oid_t apfs_snap_meta_tree_oid; xid_t apfs_revert_to_xid; oid_t apfs_revert_to_sblock_oid; uint64_t apfs_next_obj_id; uint64_t apfs_num_files; uint64_t apfs_num_directories; uint64_t apfs_num_symlinks; uint64_t apfs_num_other_fsobjects; uint64_t apfs_num_snapshots; uint64_t apfs_total_blocks_alloced; uint64_t apfs_total_blocks_freed; uuid_t apfs_vol_uuid; uint64_t apfs_last_mod_time; uint64_t apfs_fs_flags; apfs_modified_by_t apfs_formatted_by; apfs_modified_by_t apfs_modified_by[APFS_MAX_HIST]; uint8_t apfs_volname[APFS_VOLNAME_LEN]; uint32_t apfs_next_doc_id; uint16_t apfs_role; uint16_t reserved; xid_t apfs_root_to_xid; oid_t apfs_er_state_oid; uint64_t apfs_cloneinfo_id_epoch; uint64_t apfs_cloneinfo_xid; oid_t apfs_snap_meta_ext_oid; uuid_t apfs_volume_group_id; oid_t apfs_integrity_meta_oid; oid_t apfs_fext_tree_oid; uint32_t apfs_fext_tree_type; uint32_t reserved_type; oid_t reserved_oid; } apfs_superblock_t; int main() { printf("%ld\n", offsetof(apfs_superblock_t, apfs_role)); } 07070100000011000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000002200000000asahi-nvram-0.3.0/asahi-bless/src07070100000012000081A400000000000000000000000165CB904F00002A3E000000000000000000000000000000000000002900000000asahi-nvram-0.3.0/asahi-bless/src/lib.rs// SPDX-License-Identifier: MIT #![allow(dead_code)] use apple_nvram::{mtd::MtdWriter, nvram_parse, VarType}; use gpt::{disk::LogicalBlockSize, GptConfig}; use std::{ borrow::Cow, collections::HashMap, fs::{File, OpenOptions}, io::{self, Read, Seek, SeekFrom}, ops::Deref, }; use uuid::Uuid; struct NxSuperblock([u8; NxSuperblock::SIZE]); impl NxSuperblock { const SIZE: usize = 1408; const MAGIC: u32 = 1112758350; //'BSXN' const MAX_FILE_SYSTEMS: usize = 100; fn get_buf(&mut self) -> &mut [u8] { &mut self.0 } fn new() -> Self { NxSuperblock([0; NxSuperblock::SIZE]) } fn magic(&self) -> u32 { u32::from_le_bytes(self.0[32..32 + 4].try_into().unwrap()) } fn block_size(&self) -> u32 { u32::from_le_bytes(self.0[36..36 + 4].try_into().unwrap()) } fn xid(&self) -> u64 { u64::from_le_bytes(self.0[16..16 + 8].try_into().unwrap()) } fn omap_oid(&self) -> u64 { u64::from_le_bytes(self.0[160..160 + 8].try_into().unwrap()) } fn xp_desc_blocks(&self) -> u32 { u32::from_le_bytes(self.0[104..104 + 4].try_into().unwrap()) } fn xp_desc_base(&self) -> u64 { u64::from_le_bytes(self.0[112..112 + 8].try_into().unwrap()) } fn fs_oid(&self, i: usize) -> u64 { let at = 184 + 8 * i; u64::from_le_bytes(self.0[at..at + 8].try_into().unwrap()) } } struct OmapPhys<'a>(&'a [u8]); impl OmapPhys<'_> { const SIZE: usize = 88; fn tree_oid(&self) -> u64 { u64::from_le_bytes(self.0[48..48 + 8].try_into().unwrap()) } } struct NLoc<'a>(&'a [u8]); impl NLoc<'_> { fn off(&self) -> u16 { u16::from_le_bytes(self.0[0..2].try_into().unwrap()) } fn len(&self) -> u16 { u16::from_le_bytes(self.0[2..2 + 2].try_into().unwrap()) } } struct KVOff<'a>(&'a [u8]); impl KVOff<'_> { const SIZE: usize = 4; fn k(&self) -> u16 { u16::from_le_bytes(self.0[0..2].try_into().unwrap()) } fn v(&self) -> u16 { u16::from_le_bytes(self.0[2..2 + 2].try_into().unwrap()) } } struct OmapKey<'a>(&'a [u8]); impl OmapKey<'_> { fn oid(&self) -> u64 { u64::from_le_bytes(self.0[0..8].try_into().unwrap()) } fn xid(&self) -> u64 { u64::from_le_bytes(self.0[8..8 + 8].try_into().unwrap()) } } struct OmapVal<'a>(&'a [u8]); impl OmapVal<'_> { fn flags(&self) -> u32 { u32::from_le_bytes(self.0[0..4].try_into().unwrap()) } fn size(&self) -> u32 { u32::from_le_bytes(self.0[4..4 + 4].try_into().unwrap()) } fn paddr(&self) -> u64 { u64::from_le_bytes(self.0[8..8 + 8].try_into().unwrap()) } } struct BTreeInfo; impl BTreeInfo { const SIZE: usize = 40; } struct BTreeNodePhys<'a>(&'a [u8]); impl BTreeNodePhys<'_> { const FIXED_KV_SIZE: u16 = 0x4; const ROOT: u16 = 0x1; const SIZE: usize = 56; fn flags(&self) -> u16 { u16::from_le_bytes(self.0[32..32 + 2].try_into().unwrap()) } fn level(&self) -> u16 { u16::from_le_bytes(self.0[34..34 + 2].try_into().unwrap()) } fn table_space(&self) -> NLoc<'_> { NLoc(&self.0[40..]) } fn nkeys(&self) -> u32 { u32::from_le_bytes(self.0[36..36 + 4].try_into().unwrap()) } } struct ApfsSuperblock<'a>(&'a [u8]); impl ApfsSuperblock<'_> { fn volname(&self) -> &[u8] { &self.0[704..704 + 128] } fn vol_uuid(&self) -> Uuid { Uuid::from_slice(&self.0[240..240 + 16]).unwrap() } fn volume_group_id(&self) -> Uuid { Uuid::from_slice(&self.0[1008..1008 + 16]).unwrap() } fn role(&self) -> u16 { u16::from_le_bytes(self.0[964..964 + 2].try_into().unwrap()) } } const VOL_ROLE_SYSTEM: u16 = 1; fn pread<T: Read + Seek>(file: &mut T, pos: u64, target: &mut [u8]) -> io::Result<()> { file.seek(SeekFrom::Start(pos))?; file.read_exact(target) } // should probably fix xids here fn lookup(_disk: &mut File, cur_node: &BTreeNodePhys, key: u64) -> Option<u64> { if cur_node.level() != 0 { unimplemented!(); } if cur_node.flags() & BTreeNodePhys::FIXED_KV_SIZE != 0 { let toc_off = cur_node.table_space().off() as usize + BTreeNodePhys::SIZE; let key_start = toc_off + cur_node.table_space().len() as usize; let val_end = cur_node.0.len() - if cur_node.flags() & BTreeNodePhys::ROOT == 0 { 0 } else { BTreeInfo::SIZE }; for i in 0..cur_node.nkeys() as usize { let entry = KVOff(&cur_node.0[(toc_off + i * KVOff::SIZE)..]); let key_off = entry.k() as usize + key_start; let map_key = OmapKey(&cur_node.0[key_off..]); if map_key.oid() == key { let val_off = val_end - entry.v() as usize; let val = OmapVal(&cur_node.0[val_off..]); return Some(val.paddr()); } } None } else { unimplemented!(); } } fn trim_zeroes(s: &[u8]) -> &[u8] { for i in 0..s.len() { if s[i] == 0 { return &s[..i]; } } s } fn scan_volume(disk: &mut File) -> io::Result<HashMap<Uuid, Vec<Volume>>> { let mut sb = NxSuperblock::new(); disk.read_exact(sb.get_buf())?; if sb.magic() != NxSuperblock::MAGIC { return Ok(HashMap::new()); } let block_size = sb.block_size() as u64; for i in 0..sb.xp_desc_blocks() { let mut sbc = NxSuperblock::new(); pread(disk, (sb.xp_desc_base() + i as u64) * block_size, sbc.get_buf())?; if sbc.magic() == NxSuperblock::MAGIC { if sbc.xid() > sb.xid() { sb = sbc; } } } let mut omap_bytes = vec![0; OmapPhys::SIZE]; pread(disk, sb.omap_oid() * block_size, &mut omap_bytes)?; let omap = OmapPhys(&omap_bytes); let mut node_bytes = vec![0; sb.block_size() as usize]; pread(disk, omap.tree_oid() * block_size, &mut node_bytes)?; let node = BTreeNodePhys(&node_bytes); let mut vgs_found = HashMap::<Uuid, Vec<Volume>>::new(); for i in 0..NxSuperblock::MAX_FILE_SYSTEMS { let fs_id = sb.fs_oid(i); if fs_id == 0 { break; } let vsb = lookup(disk, &node, fs_id); let mut asb_bytes = vec![0; sb.block_size() as usize]; if vsb.is_none() { continue; } pread(disk, vsb.unwrap() * sb.block_size() as u64, &mut asb_bytes)?; let asb = ApfsSuperblock(&asb_bytes); if asb.volume_group_id().is_nil() { continue; } if let Ok(name) = std::str::from_utf8(trim_zeroes(asb.volname())) { vgs_found .entry(asb.volume_group_id()) .or_default() .push(Volume { name: name.to_owned(), is_system: asb.role() == VOL_ROLE_SYSTEM }); } } Ok(vgs_found) } #[derive(Debug)] pub struct Volume { pub name: String, pub is_system: bool } #[derive(Debug)] pub struct BootCandidate { pub part_uuid: Uuid, pub vg_uuid: Uuid, pub volumes: Vec<Volume>, } fn swap_uuid(u: &Uuid) -> Uuid { let (a, b, c, d) = u.as_fields(); Uuid::from_fields(a.swap_bytes(), b.swap_bytes(), c.swap_bytes(), d) } #[derive(Debug)] pub enum Error { Parse, SectionTooBig, ApplyError(std::io::Error), OutOfRange, Ambiguous, NvramReadError(std::io::Error), DiskReadError(std::io::Error), } impl From<apple_nvram::Error> for Error { fn from(e: apple_nvram::Error) -> Self { match e { apple_nvram::Error::ParseError => Error::Parse, apple_nvram::Error::SectionTooBig => Error::SectionTooBig, apple_nvram::Error::ApplyError(e) => Error::ApplyError(e), } } } type Result<T> = std::result::Result<T, Error>; pub fn get_boot_candidates() -> Result<Vec<BootCandidate>> { let disk = GptConfig::new() .writable(false) .logical_block_size(LogicalBlockSize::Lb4096) .open("/dev/nvme0n1") .map_err(Error::DiskReadError)?; let mut cands = Vec::new(); for (i, v) in disk.partitions() { if v.part_type_guid.guid != "7C3457EF-0000-11AA-AA11-00306543ECAC" { continue; } let mut part = File::open(format!("/dev/nvme0n1p{i}")).map_err(Error::DiskReadError)?; for (vg_uuid, volumes) in scan_volume(&mut part).unwrap_or_default() { cands.push(BootCandidate { vg_uuid, volumes, part_uuid: swap_uuid(&v.part_guid), }); } } return Ok(cands); } pub fn get_boot_volume(next: bool) -> Result<BootCandidate> { let mut file = OpenOptions::new() .read(true) .write(true) .open("/dev/mtd0") .map_err(Error::NvramReadError)?; let mut data = Vec::new(); file.read_to_end(&mut data).map_err(Error::NvramReadError)?; let mut nv = nvram_parse(&data)?; let active = nv.active_part_mut(); let v; if next { v = active .get_variable(b"alt-boot-volume", VarType::System) .or(active.get_variable(b"boot-volume", VarType::System)) .ok_or(Error::Parse); } else { v = active .get_variable(b"boot-volume", VarType::System) .ok_or(Error::Parse); } let data = String::from_utf8(v?.value().deref().to_vec()).unwrap(); let [_, part_uuid, part_vg_uuid]: [&str; 3] = data.split(":").collect::<Vec<&str>>().try_into().unwrap(); Ok(BootCandidate { volumes: Vec::new(), part_uuid: Uuid::parse_str(part_uuid).unwrap(), vg_uuid: Uuid::parse_str(part_vg_uuid).unwrap(), }) } pub fn set_boot_volume(cand: &BootCandidate, next: bool) -> Result<()> { let mut nvram_key: &[u8] = b"boot-volume".as_ref(); if next { nvram_key = b"alt-boot-volume".as_ref(); } let boot_str = format!( "EF57347C-0000-AA11-AA11-00306543ECAC:{}:{}", cand.part_uuid .hyphenated() .encode_upper(&mut Uuid::encode_buffer()), cand.vg_uuid .hyphenated() .encode_upper(&mut Uuid::encode_buffer()) ); let mut file = OpenOptions::new() .read(true) .write(true) .open("/dev/mtd0").map_err(Error::ApplyError)?; let mut data = Vec::new(); file.read_to_end(&mut data).map_err(Error::ApplyError)?; let mut nv = nvram_parse(&data)?; nv.prepare_for_write(); nv.active_part_mut().insert_variable( nvram_key, Cow::Owned(boot_str.into_bytes()), VarType::System, ); nv.apply(&mut MtdWriter::new(file))?; Ok(()) } 07070100000013000081A400000000000000000000000165CB904F000012C2000000000000000000000000000000000000002A00000000asahi-nvram-0.3.0/asahi-bless/src/main.rs// SPDX-License-Identifier: MIT #![allow(dead_code)] use asahi_bless::{get_boot_candidates, get_boot_volume, set_boot_volume, BootCandidate, Error, Volume}; use clap::Parser; use std::{ io::{stdin, stdout, Write}, process::ExitCode, }; #[cfg(target_os = "macos")] compile_error!("asahi-bless will only work on linux, if you are on macos, use system `bless` instead"); type Result<T> = std::result::Result<T, Error>; #[derive(Parser)] #[command(version)] struct Args { #[arg( short, long, help = "Set boot volume for next boot only" )] next: bool, #[arg( short, long, conflicts_with_all = &["set_boot", "set_boot_macos"], help = "List boot volume candidates" )] list_volumes: bool, #[arg(long, value_name = "idx", help = "Set boot volume by index")] set_boot: Option<isize>, #[arg( long, conflicts_with = "set_boot", help = "Set boot volume to macOS if unambiguous" )] set_boot_macos: bool, #[arg(name = "yes", short, long, help = "Do not ask for confirmation")] autoconfirm: bool, } fn error_to_string(e: Error) -> String { match e { Error::Ambiguous => "Ambiguous boot volume".to_string(), Error::OutOfRange => "Index out of range".to_string(), Error::Parse => "Unable to parse current nvram contents".to_string(), Error::SectionTooBig => "Ran out of space on nvram".to_string(), Error::ApplyError(e) => format!("Failed to save new nvram contents, try running with sudo? Inner error: {:?}", e), Error::NvramReadError(e) => format!("Failed to read nvram contents, try running with sudo? Inner error: {:?}", e), Error::DiskReadError(e) => format!("Failed to collect boot candidates, try running with sudo? Inner error: {:?}", e) } } fn main() -> ExitCode { match real_main() { Ok(_) => ExitCode::SUCCESS, Err(e) => { eprintln!("Error: {}", error_to_string(e)); ExitCode::FAILURE } } } fn real_main() -> Result<()> { let args = Args::parse(); if args.list_volumes { list_boot_volumes(&args)?; } else if let Some(idx) = args.set_boot { let cands = get_boot_candidates()?; set_boot_volume_by_index(&cands, idx - 1, &args, false)?; } else if args.set_boot_macos { let cands = get_boot_candidates()?; let macos_cands: Vec<_> = cands .iter() .filter(|c| { c.volumes .first() .map(|n| n.name.starts_with("Macintosh")) .unwrap_or(false) }) .collect(); if macos_cands.len() == 1 { set_boot_volume_by_ref(&macos_cands[0], &args, false)?; } else { return Err(Error::Ambiguous); } } else { interactive_main(&args)?; } Ok(()) } fn confirm() -> bool { print!("confirm? [y/N]: "); stdout().flush().unwrap(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); input.trim().to_lowercase() == "y" } fn get_vg_name(vg: &[Volume]) -> &str { for v in vg { if v.is_system { return &v.name; } } &vg[0].name } fn list_boot_volumes(args: &Args) -> Result<Vec<BootCandidate>> { let cands = get_boot_candidates()?; let default_cand = get_boot_volume(args.next)?; let mut is_default: &str; for (i, cand) in cands.iter().enumerate() { if (cand.part_uuid == default_cand.part_uuid) && (cand.vg_uuid == default_cand.vg_uuid) { is_default = "*"; } else { is_default = " "; } println!("{}{}) {}", is_default, i + 1, get_vg_name(&cand.volumes)); } Ok(cands) } fn set_boot_volume_by_index(cands: &[BootCandidate], idx: isize, args: &Args, interactive: bool) -> Result<()> { if idx < 0 || idx as usize >= cands.len() { return Err(Error::OutOfRange); } set_boot_volume_by_ref(&cands[idx as usize], args, interactive) } fn set_boot_volume_by_ref(cand: &BootCandidate, args: &Args, interactive: bool) -> Result<()> { if !interactive { println!("Will set volume {} as boot target", get_vg_name(&cand.volumes)); } if !args.autoconfirm && !interactive { if !confirm() { return Ok(()); } } set_boot_volume(cand, args.next)?; Ok(()) } fn interactive_main(args: &Args) -> Result<()> { let cands = list_boot_volumes(args)?; print!("==> "); stdout().flush().unwrap(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); let ix = input.trim().parse::<isize>().unwrap() - 1; set_boot_volume_by_index(&cands, ix, args, true)?; Ok(()) } 07070100000014000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000001F00000000asahi-nvram-0.3.0/asahi-btsync07070100000015000081A400000000000000000000000165CB904F00000230000000000000000000000000000000000000002A00000000asahi-nvram-0.3.0/asahi-btsync/Cargo.toml[package] name = "asahi-btsync" license = "MIT" version = "0.2.0" edition = "2021" description = "A tool to sync Bluetooth pairing keys with macos on ARM Macs" homepage = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" repository = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" [dependencies] rust-ini = "0.18.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies.apple-nvram] path = "../apple-nvram" version = "0.2" [dependencies.clap] version = "3" features = ["cargo"] 070701000000160000A1FF0000000000000000000000016635A1100000000A000000000000000000000000000000000000002700000000asahi-nvram-0.3.0/asahi-btsync/LICENSE../LICENSE07070100000017000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000002300000000asahi-nvram-0.3.0/asahi-btsync/src07070100000018000081A400000000000000000000000165CB904F00001B1B000000000000000000000000000000000000002B00000000asahi-nvram-0.3.0/asahi-btsync/src/main.rs/* SPDX-License-Identifier: MIT */ use std::{ env, fmt::Debug, fs, fs::OpenOptions, io::{self, stdout, Read, Write}, path::Path, }; use apple_nvram::{nvram_parse, VarType, Variable}; use ini::Ini; #[derive(Debug)] enum Error { Parse, SectionTooBig, ApplyError(std::io::Error), VariableNotFound, FileIO, BluezConfigDirNotFound, SliceError, } impl From<apple_nvram::Error> for Error { fn from(e: apple_nvram::Error) -> Self { match e { apple_nvram::Error::ParseError => Error::Parse, apple_nvram::Error::SectionTooBig => Error::SectionTooBig, apple_nvram::Error::ApplyError(e) => Error::ApplyError(e), } } } impl From<io::Error> for Error { fn from(_e: io::Error) -> Self { Error::FileIO } } impl From<std::array::TryFromSliceError> for Error { fn from(_e: std::array::TryFromSliceError) -> Self { Error::SliceError } } type Result<T> = std::result::Result<T, Error>; fn main() { real_main().unwrap(); } fn real_main() -> Result<()> { let matches = clap::command!() .arg(clap::arg!(-d --device [DEVICE] "Path to the nvram device.")) .subcommand(clap::Command::new("list").about("Parse shared Bluetooth keys from nvram")) .subcommand( clap::Command::new("sync") .about("Sync Bluetooth device information from nvram") .arg(clap::arg!(-c --config [CONFIG] "Bluez config path.")) .arg(clap::Arg::new("variable").multiple_values(true)), ) .subcommand( clap::Command::new("dump").about("Dump binary Bluetooth device info from nvram"), ) .get_matches(); let default_name = "/dev/mtd0ro".to_owned(); let default_config = "/var/lib/bluetooth".to_owned(); let bt_var = "BluetoothUHEDevices"; let mut file = OpenOptions::new() .read(true) .open(matches.get_one::<String>("device").unwrap_or(&default_name)) .unwrap(); let mut data = Vec::new(); file.read_to_end(&mut data).unwrap(); let mut nv = nvram_parse(&data)?; let active = nv.active_part_mut(); let bt_devs = active .get_variable(bt_var.as_bytes(), VarType::System) .ok_or(Error::VariableNotFound)?; match matches.subcommand() { Some(("list", _args)) => { print_btkeys(bt_devs).expect("Failed to parse bt device info"); } Some(("sync", args)) => { sync_btkeys( bt_devs, args.get_one::<String>("config").unwrap_or(&default_config), ) .expect("Failed to sync bt device info"); } Some(("dump", _args)) => { dump(bt_devs).expect("Failed to dump bt device info"); } _ => { print_btkeys(bt_devs).expect("Failed to parse bt device info"); } } Ok(()) } fn dump(var: &dyn Variable) -> Result<()> { stdout().write_all(&var.value())?; Ok(()) } struct BtDevice { mac: [u8; 6], class: u16, name: String, vendor_id: u16, product_id: u16, pairing_key: [u8; 16], } struct BtInfo { mac: [u8; 6], devices: Vec<BtDevice>, } fn read_le_u16(input: &mut &[u8]) -> Result<u16> { let (int_bytes, rest) = input.split_at(std::mem::size_of::<u16>()); *input = rest; Ok(u16::from_le_bytes(int_bytes.try_into()?)) } fn parse_bt_device(input: &mut &[u8]) -> Result<BtDevice> { // parse MAC let (mac_bytes, remain) = input.split_at(6_usize); *input = remain; let mac: [u8; 6] = mac_bytes.try_into().expect("end of bytes"); let class = read_le_u16(input)?; // skip 2 bytes *input = &input[2..]; // parse device name (u16_le length + \0 terminated utf-8 string) let name_len = read_le_u16(input)? as usize; let (name_bytes, remain) = input.split_at(name_len); *input = remain; let name = String::from_utf8_lossy(&name_bytes[..name_len - 1]).to_string(); // parse pairing key let (key_bytes, remain) = input.split_at(16); *input = remain; let key: [u8; 16] = key_bytes.try_into().expect("end of bytes"); // parse product / vendor id let product_id = read_le_u16(input)?; let vendor_id = read_le_u16(input)?; // skip 2 unknown trailing bytes *input = &input[2..]; Ok(BtDevice { mac, class, name, vendor_id, product_id, pairing_key: key, }) } fn parse_bt_info(var: &dyn Variable) -> Result<BtInfo> { let data = var.value(); assert!(data.len() >= 8); let adapter_mac: [u8; 6] = data[0..6].try_into()?; let num_devices = data[6]; assert!(data[7] == 0x04); let mut dev_data = &data[8..]; let mut devices: Vec<BtDevice> = Vec::new(); for _n in 0..num_devices { devices.push(parse_bt_device(&mut dev_data)?); } Ok(BtInfo { mac: adapter_mac, devices, }) } fn format_mac(mac: &[u8; 6]) -> Result<String> { Ok(mac .iter() .map(|x| format!("{x:02X}")) .collect::<Vec<String>>() .join(":")) } fn format_key(key: &[u8; 16]) -> Result<String> { Ok(key.iter().map(|x| format!("{x:02X}")).rev().collect()) } fn print_btkeys(var: &dyn Variable) -> Result<()> { let info = parse_bt_info(var)?; for dev in info.devices { println!( "ID {:04x}:{:04x} {} ({})", dev.vendor_id, dev.product_id, dev.name, format_mac(&dev.mac)? ); } Ok(()) } fn sync_btkeys(var: &dyn Variable, config: &String) -> Result<()> { let config_path = Path::new(config); if !config_path.is_dir() { return Err(Error::BluezConfigDirNotFound); } let info = parse_bt_info(var)?; let adapter_path = config_path.join(format_mac(&info.mac)?); if !adapter_path.is_dir() { fs::create_dir(adapter_path.clone())?; } for dev in info.devices { let dev_path = adapter_path.join(format_mac(&dev.mac)?); if !dev_path.is_dir() { fs::create_dir(dev_path.clone())?; } let info_file = dev_path.as_path().join("info"); if info_file.exists() { continue; } let mut info = Ini::new(); info.with_section(Some("General")) .set("Name", dev.name) .set("Class", format!("{:#08X}", dev.class)) .set("Trusted", "true") .set("Blocked", "false") .set("WakeAllowed", "true"); info.with_section(Some("LinkKey")) .set("Key", format_key(&dev.pairing_key)?); info.with_section(Some("DeviceID")) .set("Vendor", format!("{}", dev.vendor_id)) .set("Product", format!("{}", dev.product_id)); info.write_to_file(info_file)?; println!("{}", format_mac(&dev.mac)?); } Ok(()) } 07070100000019000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000001E00000000asahi-nvram-0.3.0/asahi-nvram0707010000001A000081A400000000000000000000000165CB904F00000205000000000000000000000000000000000000002900000000asahi-nvram-0.3.0/asahi-nvram/Cargo.toml[package] name = "asahi-nvram" version = "0.2.1" edition = "2021" license = "MIT" description = "A tool to read and write nvram variables on ARM Macs" homepage = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" repository = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies.apple-nvram] path = "../apple-nvram" version = "0.2.1" [dependencies.clap] version = "3" features = ["cargo"] 0707010000001B0000A1FF0000000000000000000000016635A1100000000A000000000000000000000000000000000000002600000000asahi-nvram-0.3.0/asahi-nvram/LICENSE../LICENSE0707010000001C000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000002200000000asahi-nvram-0.3.0/asahi-nvram/src0707010000001D000081A400000000000000000000000165CB904F0000122E000000000000000000000000000000000000002A00000000asahi-nvram-0.3.0/asahi-nvram/src/main.rs// SPDX-License-Identifier: MIT use std::{borrow::Cow, fs::OpenOptions, io::Read, process::ExitCode}; use apple_nvram::{mtd::MtdWriter, nvram_parse, VarType}; #[derive(Debug)] enum Error { Parse, SectionTooBig, ApplyError(std::io::Error), MissingPartitionName, MissingValue, VariableNotFound, UnknownPartition, InvalidHex, } impl From<apple_nvram::Error> for Error { fn from(e: apple_nvram::Error) -> Self { match e { apple_nvram::Error::ParseError => Error::Parse, apple_nvram::Error::SectionTooBig => Error::SectionTooBig, apple_nvram::Error::ApplyError(e) => Error::ApplyError(e), } } } type Result<T> = std::result::Result<T, Error>; fn main() -> ExitCode { match real_main() { Ok(_) => ExitCode::SUCCESS, Err(e) => { eprintln!("{:?}", e); ExitCode::FAILURE } } } fn real_main() -> Result<()> { let matches = clap::command!() .arg(clap::arg!(-d --device [DEVICE] "Path to the nvram device.")) .subcommand( clap::Command::new("read") .about("Read nvram variables") .arg(clap::Arg::new("variable").multiple_values(true)), ) .subcommand( clap::Command::new("delete") .about("Delete nvram variables") .arg(clap::Arg::new("variable").multiple_values(true)), ) .subcommand( clap::Command::new("write") .about("Write nvram variables") .arg(clap::Arg::new("variable=value").multiple_values(true)), ) .get_matches(); let default_name = "/dev/mtd0".to_owned(); let mut file = OpenOptions::new() .read(true) .write(true) .open(matches.get_one::<String>("device").unwrap_or(&default_name)) .unwrap(); let mut data = Vec::new(); file.read_to_end(&mut data).unwrap(); let mut nv = nvram_parse(&data)?; match matches.subcommand() { Some(("read", args)) => { let active = nv.active_part_mut(); let vars = args.get_many::<String>("variable"); if let Some(vars) = vars { for var in vars { let (part, name) = var.split_once(':').ok_or(Error::MissingPartitionName)?; let typ = part_by_name(part)?; let v = active .get_variable(name.as_bytes(), typ) .ok_or(Error::VariableNotFound)?; println!("{}", v); } } else { for var in active.variables() { println!("{}", var); } } } Some(("write", args)) => { let vars = args.get_many::<String>("variable=value"); nv.prepare_for_write(); let active = nv.active_part_mut(); for var in vars.unwrap_or_default() { let (key, value) = var.split_once('=').ok_or(Error::MissingValue)?; let (part, name) = key.split_once(':').ok_or(Error::MissingPartitionName)?; let typ = part_by_name(part)?; active.insert_variable(name.as_bytes(), Cow::Owned(read_var(value)?), typ); } nv.apply(&mut MtdWriter::new(file))?; } Some(("delete", args)) => { let vars = args.get_many::<String>("variable"); nv.prepare_for_write(); let active = nv.active_part_mut(); for var in vars.unwrap_or_default() { let (part, name) = var.split_once(':').ok_or(Error::MissingPartitionName)?; let typ = part_by_name(part)?; active.remove_variable(name.as_bytes(), typ); } nv.apply(&mut MtdWriter::new(file))?; } _ => {} } Ok(()) } fn part_by_name(name: &str) -> Result<VarType> { match name { "common" => Ok(VarType::Common), "system" => Ok(VarType::System), _ => Err(Error::UnknownPartition), } } fn read_var(val: &str) -> Result<Vec<u8>> { let val = val.as_bytes(); let mut ret = Vec::new(); let mut i = 0; while i < val.len() { if val[i] == b'%' { ret.push( u8::from_str_radix( unsafe { std::str::from_utf8_unchecked(&val[i + 1..i + 3]) }, 16, ) .map_err(|_| Error::InvalidHex)?, ); i += 2; } else { ret.push(val[i]) } i += 1; } Ok(ret) } 0707010000001E000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000002100000000asahi-nvram-0.3.0/asahi-wifisync0707010000001F000081A400000000000000000000000165CB904F0000022A000000000000000000000000000000000000002C00000000asahi-nvram-0.3.0/asahi-wifisync/Cargo.toml[package] name = "asahi-wifisync" license = "MIT" version = "0.2.0" edition = "2021" description = "A tool to sync Wifi passwords with macos on ARM Macs" homepage = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" repository = "https://github.com/WhatAmISupposedToPutHere/asahi-nvram" [dependencies] rust-ini = "0.18.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies.apple-nvram] path = "../apple-nvram" version = "0.2" [dependencies.clap] version = "3" features = ["cargo"] 070701000000200000A1FF0000000000000000000000016635A1100000000A000000000000000000000000000000000000002900000000asahi-nvram-0.3.0/asahi-wifisync/LICENSE../LICENSE07070100000021000041ED00000000000000000000000265CB904F00000000000000000000000000000000000000000000002500000000asahi-nvram-0.3.0/asahi-wifisync/src07070100000022000081A400000000000000000000000165CB904F00001144000000000000000000000000000000000000002D00000000asahi-nvram-0.3.0/asahi-wifisync/src/main.rs/* SPDX-License-Identifier: MIT */ use std::{ env, fmt::Debug, fs::OpenOptions, io::{self, Read}, path::Path, }; use apple_nvram::{nvram_parse, VarType, Variable}; use ini::Ini; #[derive(Debug)] enum Error { Parse, SectionTooBig, ApplyError(std::io::Error), VariableNotFound, FileIO, IWDConfigDirNotFound, } impl From<apple_nvram::Error> for Error { fn from(e: apple_nvram::Error) -> Self { match e { apple_nvram::Error::ParseError => Error::Parse, apple_nvram::Error::SectionTooBig => Error::SectionTooBig, apple_nvram::Error::ApplyError(e) => Error::ApplyError(e), } } } impl From<io::Error> for Error { fn from(_e: io::Error) -> Self { Error::FileIO } } type Result<T> = std::result::Result<T, Error>; fn main() { real_main().unwrap(); } fn real_main() -> Result<()> { let matches = clap::command!() .arg(clap::arg!(-d --device [DEVICE] "Path to the nvram device.")) .subcommand(clap::Command::new("list").about("Parse shared wlan keys from nvram")) .subcommand( clap::Command::new("sync") .about("Sync wlan information from nvram") .arg(clap::arg!(-c --config [CONFIG] "IWD config path.")) .arg(clap::Arg::new("variable").multiple_values(true)), ) .get_matches(); let default_name = "/dev/mtd0ro".to_owned(); let default_config = "/var/lib/iwd".to_owned(); let wlan_var = "preferred-networks"; let mut file = OpenOptions::new() .read(true) .open(matches.get_one::<String>("device").unwrap_or(&default_name)) .unwrap(); let mut data = Vec::new(); file.read_to_end(&mut data).unwrap(); let mut nv = nvram_parse(&data)?; let active = nv.active_part_mut(); let wlan_devs = active .get_variable(wlan_var.as_bytes(), VarType::System) .ok_or(Error::VariableNotFound)?; match matches.subcommand() { Some(("list", _args)) => { print_wlankeys(wlan_devs).expect("Failed to parse wlan device info"); } Some(("sync", args)) => { sync_wlankeys( wlan_devs, args.get_one::<String>("config").unwrap_or(&default_config), ) .expect("Failed to sync wlan device info"); } _ => { print_wlankeys(wlan_devs).expect("Failed to parse wlan device info"); } } Ok(()) } struct Network { ssid: String, psk: Option<Vec<u8>>, } const CHUNK_LEN: usize = 0xc0; fn parse_wlan_info(var: &dyn Variable) -> Vec<Network> { let mut nets = Vec::new(); let data = var.value(); for chunk in data.chunks(CHUNK_LEN) { let ssid_len = u32::from_le_bytes(chunk[0xc..0x10].try_into().unwrap()) as usize; let ssid = String::from_utf8_lossy(&chunk[0x10..0x10 + ssid_len]).to_string(); let secure = u32::from_le_bytes(chunk[0x8..0xc].try_into().unwrap()) != 0; let psk = if secure { Some(chunk[0xa0..0xc0].to_owned()) } else { None }; nets.push(Network { ssid, psk }); } nets } fn format_psk(psk: &[u8]) -> String { psk.iter() .map(|x| format!("{x:02x}")) .collect::<Vec<_>>() .join("") } fn print_wlankeys(var: &dyn Variable) -> Result<()> { let info = parse_wlan_info(var); for network in info { let psk_str = if let Some(psk) = network.psk { format!("PSK {}", format_psk(&psk)) } else { "Open".to_owned() }; println!("SSID {}, {}", network.ssid, psk_str); } Ok(()) } fn sync_wlankeys(var: &dyn Variable, config: &String) -> Result<()> { let config_path = Path::new(config); if !config_path.is_dir() { return Err(Error::IWDConfigDirNotFound); } let nets = parse_wlan_info(var); for net in nets { let suffix = if net.psk.is_some() { ".psk" } else { ".open" }; let net_path = config_path.join(format!("{}{}", net.ssid, suffix)); if net_path.exists() { continue; } let mut info = Ini::new(); if let Some(psk) = net.psk { info.with_section(Some("Security")) .set("PreSharedKey", format_psk(&psk)); } info.write_to_file(net_path)?; } Ok(()) } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!205 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor