Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ clap-verbosity-flag = "3.0.2"
env_logger = "0.11.7"
fatfs = "0.3.6"
gpt = "4.1.0"
hex = "0.4"
imago = { version = "0.1.4", features = ["sync-wrappers"] }
log = "0.4.27"
open = "5.3.2"
Expand Down
156 changes: 130 additions & 26 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod qcow2;
mod serial;

use std::{
io::{Seek, SeekFrom, Write},
io::{stdin, stdout, Seek, SeekFrom, Write},
path::PathBuf,
};

Expand Down Expand Up @@ -48,9 +48,47 @@ fn main() -> Result<()> {

let mut plist: MacPlist = plist::from_reader(&mut conf_plist)?;

let serial = serial::find_desired(plist.get_product_name())?;
let uuid = Uuid::new_v4();
let rom = {
let mut needs_update = false;

// Check if valid serials already exist
if plist.has_valid_serials() && !args.force_regenerate {
println!("Valid serial numbers already configured:");
println!(" Serial Number: {}", plist.get_serial_number());
println!(" MLB: {}", plist.get_mlb());
println!();

if !args.dry_run {
print!("Do you want to regenerate new serial numbers? (y/N) ");
stdout().flush()?;
let mut buffer = String::new();
stdin().read_line(&mut buffer)?;
if !buffer.trim().eq_ignore_ascii_case("y") && !buffer.trim().eq_ignore_ascii_case("yes") {
println!("Keeping existing serial numbers.");
} else {
needs_update = true;
}
}
} else {
needs_update = true;
}

let serial = if needs_update {
serial::find_desired(plist.get_product_name())?
} else {
serial::Serial {
serial_number: plist.get_serial_number().to_string(),
board_serial: plist.get_mlb().to_string(),
}
};

let uuid = if needs_update {
Uuid::new_v4()
} else {
// Keep existing UUID
Uuid::new_v4() // We'll keep this for now; ideally we'd parse the existing one
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When needs_update is false, a new UUID is always generated instead of preserving the existing one. This contradicts the comment and the intention to keep existing values. The existing UUID should be retrieved from the plist and reused.

Suggested change
Uuid::new_v4() // We'll keep this for now; ideally we'd parse the existing one
Uuid::parse_str(plist.get_uuid())
.context("Failed to parse existing UUID from plist")?

Copilot uses AI. Check for mistakes.
};

let rom = if needs_update {
let mut rom = [0; 12];
let mut rng = rand::rng();

Expand All @@ -69,37 +107,92 @@ fn main() -> Result<()> {
}

rom
} else {
[0; 12] // Keep existing ROM
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When needs_update is false, ROM is set to all zeros instead of preserving the existing ROM value from the plist. This will overwrite valid ROM data with invalid zeros. The existing ROM should be retrieved and preserved.

Suggested change
[0; 12] // Keep existing ROM
plist.get_rom() // Keep existing ROM

Copilot uses AI. Check for mistakes.
};

// Check and add Sequoia patches if requested
let mut patches_added = false;
if args.add_sequoia_patches || args.force_sequoia_patches {
if !plist.has_sequoia_patches() || args.force_sequoia_patches {
println!();
println!("Adding macOS Sequoia kernel patches for VM detection bypass...");
plist.add_sequoia_kernel_patches();
patches_added = true;
} else {
println!();
println!("Sequoia kernel patches already present.");
}
} else if !plist.has_sequoia_patches() {
println!();
println!("Note: Sequoia kernel patches not found in config.plist.");
println!("For macOS Sequoia 15.7.1+, you need kernel patches to enable Apple ID login.");
print!("Would you like to add them now? (Y/n) ");
stdout().flush()?;
let mut buffer = String::new();
stdin().read_line(&mut buffer)?;
let answer = buffer.trim();
if answer.is_empty() || answer.eq_ignore_ascii_case("y") || answer.eq_ignore_ascii_case("yes") {
plist.add_sequoia_kernel_patches();
patches_added = true;
}
}

if args.dry_run {
println!();
println!("Set serial number to {}", serial.serial_number);
println!("Set MLB to {}", serial.board_serial);
println!("Set UUID to {}", uuid);
println!(
"Set ROM to {:?}",
std::str::from_utf8(&rom).context("ROM should always be valid UTF-8")?
);
if needs_update {
println!("Would set serial number to {}", serial.serial_number);
println!("Would set MLB to {}", serial.board_serial);
println!("Would set UUID to {}", uuid);
println!(
"Would set ROM to {:?}",
std::str::from_utf8(&rom).context("ROM should always be valid UTF-8")?
);
}
if patches_added {
println!("Would add Sequoia kernel patches");
}
return Ok(());
}

plist.set_serial_number(serial.serial_number);
plist.set_mlb(serial.board_serial);
plist.set_uuid(uuid);
plist.set_rom(rom);
// Only update if changes were made
if needs_update || patches_added {
if needs_update {
plist.set_serial_number(serial.serial_number);
plist.set_mlb(serial.board_serial);
plist.set_uuid(uuid);
plist.set_rom(rom);
}

plist.debug();
conf_plist
.truncate()
.context("Failed to truncate config.plist")?;
conf_plist.seek(SeekFrom::Start(0))?;
plist::to_writer_xml(&mut conf_plist, &plist).context("Failed to write config.plist")?;
plist.debug();
conf_plist
.truncate()
.context("Failed to truncate config.plist")?;
conf_plist.seek(SeekFrom::Start(0))?;
plist::to_writer_xml(&mut conf_plist, &plist).context("Failed to write config.plist")?;

conf_plist.flush()?;
drop(conf_plist);
fs.unmount()?;
first_partition.flush()?;
qcow2.flush()?;
conf_plist.flush()?;
drop(conf_plist);
fs.unmount()?;
first_partition.flush()?;
qcow2.flush()?;

println!();
println!("✓ Configuration updated successfully!");
if patches_added {
println!("✓ Sequoia kernel patches added");
println!();
println!("IMPORTANT: These patches may prevent macOS system updates.");
println!("To update macOS:");
println!(" 1. Temporarily disable the patches in OpenCore boot menu");
println!(" 2. Reset NVRAM");
println!(" 3. Install the update");
println!(" 4. Re-enable the patches after updating");
}
} else {
println!();
println!("No changes needed.");
}

Ok(())
}
Expand All @@ -120,8 +213,19 @@ fn first_partition_subset(mut qcow2: &mut Qcow2) -> Result<IoSubset<&mut Qcow2>>
struct Args {
#[clap(long, help = "Path to the bootloader ('OpenCore.qcow2')")]
bootloader: PathBuf,

#[clap(short, long, help = "Don't commit changes to disk")]
dry_run: bool,

#[clap(short = 'f', long, help = "Force regeneration of serial numbers even if valid ones exist")]
force_regenerate: bool,

#[clap(short = 's', long, help = "Add Sequoia kernel patches for VM detection bypass")]
add_sequoia_patches: bool,

#[clap(long, help = "Force add Sequoia patches even if they already exist")]
force_sequoia_patches: bool,

#[clap(flatten)]
verbose: clap_verbosity_flag::Verbosity<clap_verbosity_flag::WarnLevel>,
}
Loading