Skip to content

Commit e48ebdf

Browse files
committed
Fixed a bug w/wintweaks
Please enter the commit message for your changes. Lines starting with '' will be ignored, and an empty message aborts the commit. On branch dev Your branch is ahead of 'origin/dev' by 1 commit. (use "git push" to publish your local commits) Changes to be committed: modified: scripts/build-appimage.sh deleted: scripts/build-standalone.sh modified: src/lufus/block_ops.py modified: src/lufus/gui/dialogs.py modified: src/lufus/gui/gui.py modified: src/lufus/gui/i18n.py modified: src/lufus/writing/windows/flash.py modified: src/lufus/writing/windows/tweaks.py modified: tests/test_flash_windows_and_install_ventoy_fixes.py
1 parent 79f43e7 commit e48ebdf

9 files changed

Lines changed: 467 additions & 157 deletions

File tree

scripts/build-appimage.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ rm -rf build dist AppDir
5252
$PYTHON -m PyInstaller src/lufus/__main__.py \
5353
--name lufus \
5454
--windowed \
55+
--clean \
5556
--paths src \
5657
--hidden-import PyQt6.QtCore \
5758
--hidden-import PyQt6.QtGui \
@@ -69,6 +70,18 @@ $PYTHON -m PyInstaller src/lufus/__main__.py \
6970
mkdir -p AppDir/usr/bin
7071
cp -r dist/lufus/* AppDir/usr/bin/
7172

73+
# Bundle system binaries needed at runtime (same list as build-standalone.sh)
74+
for bin in \
75+
mkfs.vfat mkfs.ntfs mkfs.exfat mkfs.ext4 mkudffs \
76+
ntfslabel fatlabel e2label udflabel \
77+
badblocks wimlib-imagex chntpw \
78+
udevadm xdg-open; do
79+
path=$(command -v "$bin" 2>/dev/null || true)
80+
if [ -n "$path" ]; then
81+
cp "$path" AppDir/usr/bin/
82+
fi
83+
done
84+
7285
# Create desktop file
7386
cat > AppDir/lufus.desktop <<'DESKTOP'
7487
[Desktop Entry]

scripts/build-standalone.sh

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/lufus/block_ops.py

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def _get_libc():
2929
return _LIBC
3030

3131

32+
_MS_RDONLY = 1
3233
_MS_BIND = 4096
3334
_MS_MOVE = 8192
3435
_MS_REC = 16384
@@ -216,6 +217,103 @@ def reread_partitions(device: str) -> bool:
216217
os.close(fd)
217218

218219

220+
# -------- Loop device helpers (pure Python replacement for losetup) --------
221+
222+
_LOOP_CTL_GET_FREE = 0x4C82
223+
_LOOP_SET_FD = 0x4C00
224+
_LOOP_CLR_FD = 0x4C01
225+
226+
_loop_devices: dict[str, str] = {} # mount_point -> loop_device
227+
228+
229+
def _setup_loop(file_path: str) -> str | None:
230+
"""Associate a regular file with a free loop device.
231+
232+
Returns the loop device path (e.g. /dev/loop0) or None on failure.
233+
"""
234+
import fcntl
235+
236+
try:
237+
ctl_fd = os.open("/dev/loop-control", os.O_RDWR | os.O_CLOEXEC)
238+
try:
239+
idx = fcntl.ioctl(ctl_fd, _LOOP_CTL_GET_FREE)
240+
finally:
241+
os.close(ctl_fd)
242+
except OSError as e:
243+
log.error("Cannot get free loop device: %s", e)
244+
return None
245+
246+
loop_dev = f"/dev/loop{idx}"
247+
try:
248+
file_fd = os.open(file_path, os.O_RDONLY | os.O_CLOEXEC)
249+
loop_fd = os.open(loop_dev, os.O_RDWR | os.O_CLOEXEC)
250+
try:
251+
fcntl.ioctl(loop_fd, _LOOP_SET_FD, file_fd)
252+
except OSError as e:
253+
log.error("LOOP_SET_FD on %s failed: %s", loop_dev, e)
254+
os.close(loop_fd)
255+
os.close(file_fd)
256+
return None
257+
os.close(loop_fd)
258+
os.close(file_fd)
259+
except OSError as e:
260+
log.error("Cannot set up loop for %s: %s", file_path, e)
261+
return None
262+
263+
log.info("Loop device %s <- %s", loop_dev, file_path)
264+
return loop_dev
265+
266+
267+
def _detach_loop(loop_dev: str) -> None:
268+
"""Detach a loop device."""
269+
import fcntl
270+
271+
try:
272+
fd = os.open(loop_dev, os.O_RDWR | os.O_CLOEXEC)
273+
try:
274+
fcntl.ioctl(fd, _LOOP_CLR_FD)
275+
log.info("Loop device %s detached", loop_dev)
276+
except OSError as e:
277+
log.warning("Cannot detach loop device %s: %s", loop_dev, e)
278+
finally:
279+
os.close(fd)
280+
except OSError:
281+
pass
282+
283+
284+
def mount_iso(iso_path: str, mount_point: str) -> bool:
285+
"""Mount an ISO file on *mount_point* using a loop device.
286+
287+
Creates the mount point directory if needed.
288+
Returns True on success.
289+
"""
290+
os.makedirs(mount_point, exist_ok=True)
291+
loop_dev = _setup_loop(iso_path)
292+
if loop_dev is None:
293+
return False
294+
295+
# Windows installer ISOs normally store the real payload in UDF. Their
296+
# ISO9660 compatibility view may contain only a small readme.txt.
297+
for fstype in ("udf", "iso9660"):
298+
if mount(loop_dev, mount_point, fstype=fstype, flags=_MS_RDONLY):
299+
_loop_devices[mount_point] = loop_dev
300+
log.info("ISO %s mounted as %s on %s", iso_path, fstype, mount_point)
301+
return True
302+
303+
log.error("mount(2) failed for loop device %s on %s as UDF or ISO9660", loop_dev, mount_point)
304+
_detach_loop(loop_dev)
305+
return False
306+
307+
308+
def umount_iso(mount_point: str) -> bool:
309+
"""Unmount an ISO mounted via mount_iso and detach its loop device."""
310+
ok = umount(mount_point)
311+
loop_dev = _loop_devices.pop(mount_point, None)
312+
if loop_dev:
313+
_detach_loop(loop_dev)
314+
return ok
315+
316+
219317
# -------- GPT partition table writer --------
220318

221319
_GPT_SIGNATURE = b"EFI PART"
@@ -238,6 +336,13 @@ def _crc32(data: bytes) -> int:
238336
return binascii.crc32(data) & 0xFFFFFFFF
239337

240338

339+
def _gpt_header_crc(header: bytes, header_size: int = 92) -> int:
340+
"""Return a GPT header CRC over HeaderSize bytes with the CRC field zeroed."""
341+
crc_data = bytearray(header[:header_size])
342+
crc_data[16:20] = b"\x00\x00\x00\x00"
343+
return _crc32(bytes(crc_data))
344+
345+
241346
def _write_gpt(device: str, disk_guid: bytes, partitions: list[dict]) -> bool:
242347
"""Write a GPT partition table to device.
243348
@@ -318,8 +423,9 @@ def _write_gpt(device: str, disk_guid: bytes, partitions: list[dict]) -> bool:
318423
)
319424
assert len(header) == 512
320425

321-
# Compute and set header CRC
322-
header_crc = _crc32(header[:16] + b"\x00\x00\x00\x00" + header[20:])
426+
# Compute and set header CRC. GPT requires the CRC to cover only
427+
# HeaderSize bytes, not the full 512-byte sector.
428+
header_crc = _gpt_header_crc(header, 92)
323429
header = header[:16] + struct.pack("<I", header_crc) + header[20:]
324430

325431
# Protective MBR
@@ -355,7 +461,7 @@ def _write_gpt(device: str, disk_guid: bytes, partitions: list[dict]) -> bool:
355461
+ struct.pack("<I", partition_entries_crc)
356462
+ b"\x00" * (512 - 92)
357463
)
358-
backup_crc = _crc32(backup_header[:16] + b"\x00\x00\x00\x00" + backup_header[20:])
464+
backup_crc = _gpt_header_crc(backup_header, 92)
359465
backup_header = backup_header[:16] + struct.pack("<I", backup_crc) + backup_header[20:]
360466

361467
try:
@@ -375,6 +481,7 @@ def _write_gpt(device: str, disk_guid: bytes, partitions: list[dict]) -> bool:
375481
# Write backup GPT header (last sector)
376482
os.lseek(fd, backup_header_lba * 512, os.SEEK_SET)
377483
os.write(fd, backup_header)
484+
os.fsync(fd)
378485
finally:
379486
os.close(fd)
380487
log.info("GPT written to %s (%d partitions)", device, len(partitions))

src/lufus/gui/dialogs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ def _on_language_changed(self):
301301
class WinTweaks(QDialog):
302302
def __init__(self, parent=None):
303303
super().__init__(parent)
304+
self._T = parent._T if parent else {}
304305
re = QRegularExpression("^[a-zA-Z0-9_]*$")
305306
validator = QRegularExpressionValidator(re)
306307
self.setWindowTitle("Windows Tweaks (MAY BREAK! USE CAUTION)")

src/lufus/gui/gui.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pathlib import Path
1717
from PySide6.QtWidgets import (
1818
QApplication,
19+
QDialog,
1920
QMainWindow,
2021
QWidget,
2122
QVBoxLayout,
@@ -53,7 +54,6 @@
5354
from lufus.gui.redirector import StdoutRedirector
5455
from lufus.gui.dialogs import LogWindow, AboutWindow, SettingsDialog, WinTweaks
5556
from lufus.gui.workers import FlashWorker, VerifyWorker
56-
from lufus.writing.windows.tweaks import *
5757

5858
# log level mapping for colors and methods
5959
_LOG_LEVELS = {
@@ -1537,10 +1537,14 @@ def start_process(self):
15371537
self.verify_worker.start()
15381538
else:
15391539
# skip verification and start flash :3
1540-
if states.image_option == 0 and states.currentflash == 0:
1540+
if state.image_option == 0 and state.flash_mode == 0:
15411541
dlg = WinTweaks(self)
1542-
if dlg.exec() == QDialog.DialogCode.Rejected:
1542+
result = dlg.exec()
1543+
self.log_message(f"WinTweaks dialog closed with result: {result}")
1544+
if result == QDialog.DialogCode.Rejected:
1545+
self.log_message("Flash cancelled in WinTweaks dialog", level="WARN")
15431546
return
1547+
self.log_message("Proceeding to perform_flash")
15441548
self.perform_flash()
15451549

15461550
def on_verify_finished(self, success: bool):
@@ -1549,7 +1553,7 @@ def on_verify_finished(self, success: bool):
15491553
self.log_message("SHA256 verification successful, proceeding to flash")
15501554
self.progress_bar.setFormat("")
15511555
self._clear_speed_eta()
1552-
if states.image_option == 0 and states.currentflash == 0:
1556+
if state.image_option == 0 and state.flash_mode == 0:
15531557
dlg = WinTweaks(self)
15541558
if dlg.exec() == QDialog.DialogCode.Rejected:
15551559
self.btn_start.setEnabled(True)
@@ -1574,6 +1578,7 @@ def on_verify_finished(self, success: bool):
15741578

15751579
def perform_flash(self):
15761580
# Rufus-style warning before flashing :3
1581+
self.log_message("perform_flash: showing confirmation dialog")
15771582
device_name = self.combo_device.currentText()
15781583
warning_title = self._T.get("msgbox_flash_warning_title", "WARNING: ALL DATA ON DEVICE WILL BE DESTROYED!")
15791584
warning_body = self._T.get(
@@ -1699,18 +1704,7 @@ def on_flash_finished(self, success: bool):
16991704
# flash succeeded :D
17001705
self.progress_bar.setValue(100)
17011706
self.progress_bar.setFormat(self._T.get("progress_complete", "Complete"))
1702-
# change from fo to tweaks
17031707
self.log_message("Flash operation finished with result: SUCCESS")
1704-
if states.image_option == 0 and states.currentflash == 0:
1705-
if getattr(states, "win_hardware_bypass", 0) == 1:
1706-
win_hardware_bypass()
1707-
if getattr(states, "win_microsoft_acc", 0) == 1:
1708-
if getattr(states, "win_local_acc_chk", 0) == 1:
1709-
win_local_acc_name()
1710-
else:
1711-
win_local_acc()
1712-
if getattr(states, "win_privacy", 0) == 1:
1713-
win_skip_privacy_questions()
17141708
QMessageBox.information(
17151709
self,
17161710
self._T.get("msgbox_success_title", "Success"),

src/lufus/gui/i18n.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,23 @@ def detect_system_language() -> str:
3131
3232
Falls back to English if detection fails or no match is found.
3333
"""
34-
try:
35-
# Read the system locale without mutating global locale state.
36-
loc = locale.getdefaultlocale()
37-
except Exception:
38-
loc = (None, None)
34+
# Read locale from environment directly (replaces deprecated
35+
# locale.getdefaultlocale() which is removed in Python 3.15).
36+
lang_code = ""
37+
for var in ("LC_ALL", "LC_MESSAGES", "LC_CTYPE", "LANG", "LANGUAGE"):
38+
val = os.environ.get(var)
39+
if val:
40+
lang_code = val
41+
break
3942

40-
# Fallback to the process locale if the default locale could not be determined.
41-
if not loc or not loc[0]:
43+
# Fallback to the process locale if env vars are empty.
44+
if not lang_code:
4245
try:
4346
loc = locale.getlocale()
47+
if loc and loc[0]:
48+
lang_code = loc[0]
4449
except Exception:
45-
loc = (None, None)
46-
47-
lang_code = (loc[0] or "") or os.environ.get("LANG", "")
50+
pass
4851

4952
if not lang_code:
5053
return "English"

0 commit comments

Comments
 (0)