From 9d6505d5d306f3a627abfc7e4a840300e5735ac0 Mon Sep 17 00:00:00 2001 From: ali ghanati Date: Sun, 17 May 2026 07:51:08 +0330 Subject: [PATCH 1/2] Add support for custom temporary directories in image creation scripts - Updated `make_image.bat`, `mkexfat_macos.sh`, and `mkexfat.sh` to accept a temporary directory argument for improved flexibility. - Enhanced error handling for missing or invalid temporary directories. - Modified `New-OsfExfatImage.ps1` to utilize a custom temp directory for logs and temporary files. - Updated usage instructions in all scripts to reflect new options. --- New-OsfExfatImage.ps1 | 40 +++++++++++++++++- make_image.bat | 62 ++++++++++++++++++++++++---- mkexfat.sh | 94 ++++++++++++++++++++++++++++++++++++++----- mkexfat_macos.sh | 92 ++++++++++++++++++++++++++++++++++++++---- 4 files changed, 263 insertions(+), 25 deletions(-) mode change 100644 => 100755 mkexfat.sh diff --git a/New-OsfExfatImage.ps1 b/New-OsfExfatImage.ps1 index ccce53a..af682a5 100644 --- a/New-OsfExfatImage.ps1 +++ b/New-OsfExfatImage.ps1 @@ -24,6 +24,13 @@ -Label "DATA" ` -ForceOverwrite + 2b) Custom temp directory (large copies / slow system drive): + powershell.exe -ExecutionPolicy Bypass -File .\New-OsfExfatImage.ps1 ` + -ImagePath "D:\images\data.img" ` + -SourceDir "C:\payload" ` + -TempDir "D:\temp" ` + -ForceOverwrite + 3) Create empty image and keep mounted (manual format/copy): powershell.exe -ExecutionPolicy Bypass -File .\New-OsfExfatImage.ps1 ` -ImagePath "C:\images\data.exfat" ` @@ -38,6 +45,8 @@ -Size Optional. If omitted, an optimal size is computed to fit all files. Suffixes: K/M/G/T (1024), k/m/g/t (1000), b (512-byte blocks), or bytes. -Label Volume label. + -TempDir Optional. Directory for temporary files (format.com logs, etc.). + Aliases: -t. When omitted, Windows default temp is used. -ForceOverwrite Recreate image if it already exists. -CreateEmptyAndMount Create and mount image only. Skip format/copy and leave mounted. @@ -64,6 +73,9 @@ param( [string]$Label = "OSFIMG", + [Alias("t")] + [string]$TempDir, + [switch]$ForceOverwrite, [switch]$CreateEmptyAndMount @@ -123,6 +135,28 @@ function Parse-SizeToBytes([string]$s) { throw "Failed to parse size string: '$s'" } +$script:ScriptTempDir = $null + +function Initialize-ScriptTempDir([string]$dir) { + if ([string]::IsNullOrWhiteSpace($dir)) { return } + $resolved = $dir.Trim() + if (-not (Test-Path -LiteralPath $resolved -PathType Container)) { + throw "Temp directory not found: $resolved" + } + $script:ScriptTempDir = (Resolve-Path -LiteralPath $resolved).Path + $env:TEMP = $script:ScriptTempDir + $env:TMP = $script:ScriptTempDir + Write-Host "[Info] Temp directory: $script:ScriptTempDir" +} + +function New-ScriptTempFile { + if ($script:ScriptTempDir) { + $name = [System.IO.Path]::GetRandomFileName() + return (Join-Path $script:ScriptTempDir $name) + } + return [System.IO.Path]::GetTempFileName() +} + function Format-Bytes([Int64]$bytes) { if ($bytes -ge 1TB) { return "{0:N2} TB" -f ($bytes/1TB) } if ($bytes -ge 1GB) { return "{0:N2} GB" -f ($bytes/1GB) } @@ -255,8 +289,8 @@ function Invoke-FormatVolume([string]$driveLetter, [int]$clusterSize, [string]$l $lastFormatExitCode = -1 foreach ($attempt in $attempts) { Write-Host "[Info] format attempt: $($attempt.Name)" - $stdoutPath = [System.IO.Path]::GetTempFileName() - $stderrPath = [System.IO.Path]::GetTempFileName() + $stdoutPath = New-ScriptTempFile + $stderrPath = New-ScriptTempFile try { $proc = Start-Process -FilePath "format.com" -ArgumentList $attempt.Args -Wait -PassThru -NoNewWindow ` -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath @@ -295,6 +329,8 @@ if (-not (Test-Admin)) { throw "Please run PowerShell as Administrator." } if (-not (Test-Path $SourceDir -PathType Container)) { throw "Source directory not found: $SourceDir" } if (-not (Test-Path (Join-Path $SourceDir "eboot.bin") -PathType Leaf)) { throw "eboot.bin not found in source directory: $SourceDir" } +Initialize-ScriptTempDir -dir $TempDir + # Ensure output directory exists $outDir = Split-Path -Parent $ImagePath if ($outDir -and -not (Test-Path $outDir)) { diff --git a/make_image.bat b/make_image.bat index e6703da..850e07d 100644 --- a/make_image.bat +++ b/make_image.bat @@ -2,13 +2,45 @@ setlocal EnableExtensions REM make_image.bat -REM Usage: make_image.bat "C:\images\data.exfat" "C:\payload" +REM Usage: make_image.bat [-t tmp_dir|--tmp-dir tmp_dir|--temp-dir tmp_dir] "C:\images\data.exfat" "C:\payload" -if "%~1"=="" goto :usage -if "%~2"=="" goto :usage +set "IMAGE=" +set "SRCDIR=" +set "TEMPDIR=" -set "IMAGE=%~1" -set "SRCDIR=%~2" +:parse_args +if "%~1"=="" goto :parse_done +if /i "%~1"=="-t" goto :take_temp +if /i "%~1"=="--tmp-dir" goto :take_temp +if /i "%~1"=="--temp-dir" goto :take_temp +if /i "%~1"=="-h" goto :usage +if /i "%~1"=="--help" goto :usage +if not defined IMAGE ( + set "IMAGE=%~1" + shift + goto :parse_args +) +if not defined SRCDIR ( + set "SRCDIR=%~1" + shift + goto :parse_args +) +echo [ERROR] Too many arguments. +goto :usage + +:take_temp +if "%~2"=="" ( + echo [ERROR] Missing value for %~1 + goto :usage +) +set "TEMPDIR=%~2" +shift +shift +goto :parse_args + +:parse_done +if not defined IMAGE goto :usage +if not defined SRCDIR goto :usage REM Script is expected to be in the same directory as this BAT set "SCRIPT=%~dp0New-OsfExfatImage.ps1" @@ -29,10 +61,21 @@ if not exist "%SRCDIR%\eboot.bin" ( exit /b 4 ) +if defined TEMPDIR ( + if not exist "%TEMPDIR%\" ( + echo [ERROR] Temp directory not found: "%TEMPDIR%" + exit /b 5 + ) +) + REM Run elevated? This BAT does not auto-elevate. REM Right-click -> Run as administrator, or start cmd as admin. -powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT%" -ImagePath "%IMAGE%" -SourceDir "%SRCDIR%" -ForceOverwrite +if defined TEMPDIR ( + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT%" -ImagePath "%IMAGE%" -SourceDir "%SRCDIR%" -TempDir "%TEMPDIR%" -ForceOverwrite +) else ( + powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT%" -ImagePath "%IMAGE%" -SourceDir "%SRCDIR%" -ForceOverwrite +) set "RC=%ERRORLEVEL%" if not "%RC%"=="0" ( @@ -45,7 +88,12 @@ exit /b 0 :usage echo Usage: -echo %~nx0 "C:\path\to\image.img" "C:\path\to\folder" +echo %~nx0 [-t tmp_dir ^| --tmp-dir tmp_dir ^| --temp-dir tmp_dir] "C:\path\to\image.img" "C:\path\to\folder" +echo. +echo Examples: +echo %~nx0 "D:\out\game.exfat" "C:\payload" +echo %~nx0 -t D:\temp "D:\out\game.exfat" "C:\payload" +echo %~nx0 "D:\out\game.exfat" "C:\payload" --temp-dir D:\temp echo. echo Notes: echo - Run this BAT as Administrator. diff --git a/mkexfat.sh b/mkexfat.sh old mode 100644 new mode 100755 index 0edb516..b86faad --- a/mkexfat.sh +++ b/mkexfat.sh @@ -1,17 +1,75 @@ #!/bin/sh # For WSL2/Ubuntu/Debian: sudo apt-get install -y exfatprogs exfat-fuse fuse3 rsync # Create an exFAT image from a directory -# Usage: mkexfat.sh [output_file] +# Usage: mkexfat.sh [-t tmp_dir|--tmp-dir tmp_dir] [output_file] set -e -if [ -z "$1" ]; then - echo "Usage: $0 [output_file]" +TMP_BASE="/tmp" +INPUT_DIR="" +OUTPUT="" + +usage() { + echo "Usage: $0 [-t tmp_dir|--tmp-dir tmp_dir|--temp-dir tmp_dir] [output_file]" +} + +while [ $# -gt 0 ]; do + case "$1" in + -t|--tmp-dir|--temp-dir) + if [ -z "$2" ]; then + echo "Error: missing value for $1" + usage + exit 1 + fi + TMP_BASE="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + while [ $# -gt 0 ]; do + if [ -z "$INPUT_DIR" ]; then + INPUT_DIR="$1" + elif [ -z "$OUTPUT" ]; then + OUTPUT="$1" + else + echo "Error: too many positional arguments" + usage + exit 1 + fi + shift + done + break + ;; + -*) + echo "Error: unknown option: $1" + usage + exit 1 + ;; + *) + if [ -z "$INPUT_DIR" ]; then + INPUT_DIR="$1" + elif [ -z "$OUTPUT" ]; then + OUTPUT="$1" + else + echo "Error: too many positional arguments" + usage + exit 1 + fi + shift + ;; + esac +done + +if [ -z "$INPUT_DIR" ]; then + usage exit 1 fi -INPUT_DIR="$1" -OUTPUT="${2:-test.exfat}" +OUTPUT="${OUTPUT:-test.exfat}" if [ ! -d "$INPUT_DIR" ]; then echo "Error: input directory not found: $INPUT_DIR" @@ -23,6 +81,11 @@ if [ ! -f "$INPUT_DIR/eboot.bin" ]; then exit 1 fi +if [ ! -d "$TMP_BASE" ]; then + echo "Error: temp directory not found: $TMP_BASE" + exit 1 +fi + # More accurate sizing for exFAT: # - file payload rounded to cluster size # - FAT + allocation bitmap estimates from cluster count @@ -84,13 +147,26 @@ echo "Input size (exFAT alloc): $DATA_BYTES bytes" echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT" echo "exFAT profile: -c $MKFS_CLUSTER_ARG (avg file=$AVG_FILE_BYTES bytes)" echo "Image size: ${MB}MB" +echo "Temp directory: $TMP_BASE" truncate -s "${MB}M" "$OUTPUT" mkfs.exfat -c "$MKFS_CLUSTER_ARG" "$OUTPUT" -mkdir -p /mnt/exfat -mount -t exfat-fuse -o loop "$OUTPUT" /mnt/exfat -rsync -r --info=progress2 "$INPUT_DIR"/ /mnt/exfat/ +MOUNT_DIR=$(mktemp -d "$TMP_BASE/mkexfat.XXXXXX") -umount /mnt/exfat +cleanup() { + if mountpoint -q "$MOUNT_DIR" 2>/dev/null; then + umount "$MOUNT_DIR" || true + fi + rmdir "$MOUNT_DIR" 2>/dev/null || true +} + +trap cleanup EXIT INT TERM + +# exfat-fuse can open a plain file without a kernel loop device. mount -o loop +# uses libmount/losetup and can fail on some removable exFAT targets (~large images). +if ! mount -t exfat-fuse "$OUTPUT" "$MOUNT_DIR"; then + mount -t exfat-fuse -o loop "$OUTPUT" "$MOUNT_DIR" +fi +rsync -r --info=progress2 "$INPUT_DIR"/ "$MOUNT_DIR"/ echo "Created $OUTPUT" diff --git a/mkexfat_macos.sh b/mkexfat_macos.sh index dbac0f6..785bc5e 100644 --- a/mkexfat_macos.sh +++ b/mkexfat_macos.sh @@ -1,19 +1,79 @@ #!/bin/sh # macOS exFAT image builder — requires macOS 10.6.5+ (exFAT and rsync built-in) -# Usage: mkexfat_macos.sh [output_file] +# Usage: mkexfat_macos.sh [-t tmp_dir|--tmp-dir tmp_dir|--temp-dir tmp_dir] [output_file] # (c) @drakmor,@betmoar set -e # ── argument validation ─────────────────────────────────────────────────────── -if [ -z "$1" ]; then - echo "Usage: $0 [output_file]" +USE_TMP_BASE=0 +TMP_BASE="" +INPUT_DIR="" +OUTPUT="" + +usage() { + echo "Usage: $0 [-t tmp_dir|--tmp-dir tmp_dir|--temp-dir tmp_dir] [output_file]" +} + +while [ $# -gt 0 ]; do + case "$1" in + -t|--tmp-dir|--temp-dir) + if [ -z "$2" ]; then + echo "Error: missing value for $1" + usage + exit 1 + fi + USE_TMP_BASE=1 + TMP_BASE="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + while [ $# -gt 0 ]; do + if [ -z "$INPUT_DIR" ]; then + INPUT_DIR="$1" + elif [ -z "$OUTPUT" ]; then + OUTPUT="$1" + else + echo "Error: too many positional arguments" + usage + exit 1 + fi + shift + done + break + ;; + -*) + echo "Error: unknown option: $1" + usage + exit 1 + ;; + *) + if [ -z "$INPUT_DIR" ]; then + INPUT_DIR="$1" + elif [ -z "$OUTPUT" ]; then + OUTPUT="$1" + else + echo "Error: too many positional arguments" + usage + exit 1 + fi + shift + ;; + esac +done + +if [ -z "$INPUT_DIR" ]; then + usage exit 1 fi -INPUT_DIR="$1" -OUTPUT="${2:-test.exfat}" +OUTPUT="${OUTPUT:-test.exfat}" # newfs_exfat interprets relative paths as device names under /dev/ — resolve to absolute OUTPUT="$(cd "$(dirname "$OUTPUT")" && pwd)/$(basename "$OUTPUT")" @@ -27,6 +87,13 @@ if [ ! -f "$INPUT_DIR/eboot.bin" ]; then exit 1 fi +if [ "$USE_TMP_BASE" -eq 1 ]; then + if [ ! -d "$TMP_BASE" ]; then + echo "Error: temp directory not found: $TMP_BASE" + exit 1 + fi +fi + # ── sizing constants ────────────────────────────────────────────────────────── CLUSTER_SIZE=32768 @@ -40,7 +107,11 @@ ENTRY_META_BYTES=256 # Single filesystem traversal: collect all file sizes into a temp file. # FILE_COUNT and RAW_FILE_BYTES are derived from it in one awk pass. # DIR_COUNT needs a separate traversal (different -type). -SIZES_FILE=$(mktemp) +if [ "$USE_TMP_BASE" -eq 1 ]; then + SIZES_FILE=$(mktemp "$TMP_BASE/mkexfat_sizes.XXXXXX") +else + SIZES_FILE=$(mktemp) +fi MOUNT_POINT="" LOOP_DEVICE="" @@ -104,10 +175,17 @@ echo "Input size (exFAT alloc): $DATA_BYTES bytes" echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT" echo "exFAT profile: -b ${CLUSTER_SIZE} (avg file=$AVG_FILE_BYTES bytes)" echo "Image size: ${MB}MB" +if [ "$USE_TMP_BASE" -eq 1 ]; then + echo "Temp directory: $TMP_BASE" +fi # ── create, attach, format, mount ──────────────────────────────────────────── -MOUNT_POINT=$(mktemp -d) +if [ "$USE_TMP_BASE" -eq 1 ]; then + MOUNT_POINT=$(mktemp -d "$TMP_BASE/mkexfat.XXXXXX") +else + MOUNT_POINT=$(mktemp -d) +fi # mkfile -n creates a sparse file without writing zeros (equivalent to Linux truncate) mkfile -n "${MB}m" "$OUTPUT" From b45396c1341f811a0cbb9082301d0571bda7d105 Mon Sep 17 00:00:00 2001 From: ali ghanati Date: Sun, 17 May 2026 12:06:34 +0330 Subject: [PATCH 2/2] fixed things told by coderabbitai --- New-OsfExfatImage.ps1 | 10 +++++++++- make_image.bat | 4 ++-- mkexfat.sh | 4 ++++ mkexfat_macos.sh | 11 ++++++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/New-OsfExfatImage.ps1 b/New-OsfExfatImage.ps1 index af682a5..71487ec 100644 --- a/New-OsfExfatImage.ps1 +++ b/New-OsfExfatImage.ps1 @@ -143,7 +143,15 @@ function Initialize-ScriptTempDir([string]$dir) { if (-not (Test-Path -LiteralPath $resolved -PathType Container)) { throw "Temp directory not found: $resolved" } - $script:ScriptTempDir = (Resolve-Path -LiteralPath $resolved).Path + $resolvedPath = (Resolve-Path -LiteralPath $resolved).Path + $probe = Join-Path $resolvedPath ([System.IO.Path]::GetRandomFileName()) + try { + Set-Content -LiteralPath $probe -Value "" -NoNewline + Remove-Item -LiteralPath $probe -Force -ErrorAction SilentlyContinue + } catch { + throw "Temp directory is not writable: $resolvedPath" + } + $script:ScriptTempDir = $resolvedPath $env:TEMP = $script:ScriptTempDir $env:TMP = $script:ScriptTempDir Write-Host "[Info] Temp directory: $script:ScriptTempDir" diff --git a/make_image.bat b/make_image.bat index 850e07d..efc4ced 100644 --- a/make_image.bat +++ b/make_image.bat @@ -72,9 +72,9 @@ REM Run elevated? This BAT does not auto-elevate. REM Right-click -> Run as administrator, or start cmd as admin. if defined TEMPDIR ( - powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT%" -ImagePath "%IMAGE%" -SourceDir "%SRCDIR%" -TempDir "%TEMPDIR%" -ForceOverwrite + powershell.exe -NoProfile -ExecutionPolicy RemoteSigned -File "%SCRIPT%" -ImagePath "%IMAGE%" -SourceDir "%SRCDIR%" -TempDir "%TEMPDIR%" -ForceOverwrite ) else ( - powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT%" -ImagePath "%IMAGE%" -SourceDir "%SRCDIR%" -ForceOverwrite + powershell.exe -NoProfile -ExecutionPolicy RemoteSigned -File "%SCRIPT%" -ImagePath "%IMAGE%" -SourceDir "%SRCDIR%" -ForceOverwrite ) set "RC=%ERRORLEVEL%" diff --git a/mkexfat.sh b/mkexfat.sh index b86faad..df35f4d 100755 --- a/mkexfat.sh +++ b/mkexfat.sh @@ -85,6 +85,10 @@ if [ ! -d "$TMP_BASE" ]; then echo "Error: temp directory not found: $TMP_BASE" exit 1 fi +if [ ! -w "$TMP_BASE" ] || [ ! -x "$TMP_BASE" ]; then + echo "Error: temp directory is not writable/executable: $TMP_BASE" + exit 1 +fi # More accurate sizing for exFAT: # - file payload rounded to cluster size diff --git a/mkexfat_macos.sh b/mkexfat_macos.sh index 785bc5e..bf396af 100644 --- a/mkexfat_macos.sh +++ b/mkexfat_macos.sh @@ -75,7 +75,16 @@ fi OUTPUT="${OUTPUT:-test.exfat}" # newfs_exfat interprets relative paths as device names under /dev/ — resolve to absolute -OUTPUT="$(cd "$(dirname "$OUTPUT")" && pwd)/$(basename "$OUTPUT")" +OUT_DIR="$(dirname "$OUTPUT")" +if [ ! -d "$OUT_DIR" ]; then + echo "Error: output directory not found: $OUT_DIR (for output $OUTPUT)" + exit 1 +fi +if [ ! -w "$OUT_DIR" ] || [ ! -x "$OUT_DIR" ]; then + echo "Error: output directory is not writable: $OUT_DIR (for output $OUTPUT)" + exit 1 +fi +OUTPUT="$(cd "$OUT_DIR" && pwd)/$(basename "$OUTPUT")" if [ ! -d "$INPUT_DIR" ]; then echo "Error: input directory not found: $INPUT_DIR"