mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-18 20:59:59 -05:00
111 lines
3.4 KiB
Bash
111 lines
3.4 KiB
Bash
#!/bin/sh
|
|
# Create a PS4-compatible UFS2 image from a directory
|
|
# Usage: mkufs2.sh <input_dir> [output_file]
|
|
|
|
set -e
|
|
|
|
if [ -z "$1" ]; then
|
|
echo "Usage: $0 <input_dir> [output_file]"
|
|
exit 1
|
|
fi
|
|
|
|
INPUT_DIR="$1"
|
|
OUTPUT="${2:-download0.dat}"
|
|
|
|
if [ ! -d "$INPUT_DIR" ]; then
|
|
echo "Error: input directory not found: $INPUT_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$INPUT_DIR/eboot.bin" ]; then
|
|
echo "Error: eboot.bin not found in source directory: $INPUT_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
# More accurate sizing for UFS2:
|
|
# - file payload rounded to the actual allocation unit (-f)
|
|
# - minimal per-directory allocation
|
|
# - inode table estimate
|
|
# - fixed filesystem metadata headroom
|
|
|
|
BLOCK_SIZE=65536
|
|
FRAG_SIZE=65536
|
|
SECTOR_SIZE=4096
|
|
MINFREE_PERCENT=0
|
|
DEFAULT_BYTES_PER_INODE=$((4 * FRAG_SIZE))
|
|
MIN_BYTES_PER_INODE=$FRAG_SIZE
|
|
INODE_SIZE=256
|
|
INODE_SPARE=2048
|
|
META_FIXED=$((64 * 1024 * 1024)) # superblocks, cg metadata, allocator slack
|
|
MIN_SLACK=$((64 * 1024 * 1024)) # minimum copy/runtime safety margin
|
|
SPARE_MIN=$((64 * 1024 * 1024)) # lower bound for dynamic headroom
|
|
SPARE_MAX=$((512 * 1024 * 1024)) # upper bound for dynamic headroom
|
|
|
|
FILE_COUNT=$(find "$INPUT_DIR" -type f | wc -l | tr -d ' ')
|
|
DIR_COUNT=$(find "$INPUT_DIR" -type d | wc -l | tr -d ' ')
|
|
RAW_FILE_BYTES=$(find "$INPUT_DIR" -type f -exec stat -f '%z' {} + | \
|
|
awk '{s += $1} END {print s + 0}')
|
|
|
|
DATA_BYTES=$(find "$INPUT_DIR" -type f -exec stat -f '%z' {} + | \
|
|
awk -v frag="$FRAG_SIZE" '{s += int(($1 + frag - 1) / frag) * frag} END {print s + 0}')
|
|
DIR_BYTES=$((DIR_COUNT * FRAG_SIZE))
|
|
INODE_COUNT=$((FILE_COUNT + DIR_COUNT + INODE_SPARE))
|
|
INODE_BYTES=$((INODE_COUNT * INODE_SIZE))
|
|
|
|
BASE_TOTAL=$((DATA_BYTES + DIR_BYTES + INODE_BYTES + META_FIXED))
|
|
SPARE_BYTES=$((BASE_TOTAL / 200)) # ~0.5%
|
|
if [ "$SPARE_BYTES" -lt "$SPARE_MIN" ]; then
|
|
SPARE_BYTES=$SPARE_MIN
|
|
fi
|
|
if [ "$SPARE_BYTES" -gt "$SPARE_MAX" ]; then
|
|
SPARE_BYTES=$SPARE_MAX
|
|
fi
|
|
TOTAL=$((BASE_TOTAL + SPARE_BYTES))
|
|
MIN_TOTAL=$((RAW_FILE_BYTES + MIN_SLACK))
|
|
if [ "$TOTAL" -lt "$MIN_TOTAL" ]; then
|
|
TOTAL=$MIN_TOTAL
|
|
fi
|
|
|
|
# Round up to nearest MB
|
|
MB=$(( (TOTAL + 1024*1024 - 1) / (1024*1024) ))
|
|
IMAGE_BYTES=$((MB * 1024 * 1024))
|
|
|
|
# Tune inode density for the actual tree size. Keep the documented default
|
|
# unless the source contains enough files/directories to require denser inodes.
|
|
BYTES_PER_INODE=$DEFAULT_BYTES_PER_INODE
|
|
if [ "$INODE_COUNT" -gt 0 ]; then
|
|
AUTO_BYTES_PER_INODE=$((IMAGE_BYTES / INODE_COUNT))
|
|
AUTO_BYTES_PER_INODE=$(( (AUTO_BYTES_PER_INODE / SECTOR_SIZE) * SECTOR_SIZE ))
|
|
if [ "$AUTO_BYTES_PER_INODE" -lt "$MIN_BYTES_PER_INODE" ]; then
|
|
AUTO_BYTES_PER_INODE=$MIN_BYTES_PER_INODE
|
|
fi
|
|
if [ "$AUTO_BYTES_PER_INODE" -lt "$BYTES_PER_INODE" ]; then
|
|
BYTES_PER_INODE=$AUTO_BYTES_PER_INODE
|
|
fi
|
|
fi
|
|
|
|
echo "Input size (raw files): $RAW_FILE_BYTES bytes"
|
|
echo "Input size (UFS alloc): $DATA_BYTES bytes"
|
|
echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT"
|
|
echo "Inode target: $INODE_COUNT"
|
|
echo "UFS profile: -b $BLOCK_SIZE -f $FRAG_SIZE -m $MINFREE_PERCENT -S $SECTOR_SIZE -i $BYTES_PER_INODE"
|
|
echo "Image size: ${MB}MB"
|
|
|
|
truncate -s "${MB}M" "$OUTPUT"
|
|
|
|
MD=$(mdconfig -a -t vnode -f "$(realpath "$OUTPUT")")
|
|
newfs -O 2 -b "$BLOCK_SIZE" -f "$FRAG_SIZE" -m "$MINFREE_PERCENT" -S "$SECTOR_SIZE" -i "$BYTES_PER_INODE" /dev/${MD}
|
|
|
|
mkdir -p /mnt
|
|
mount /dev/${MD} /mnt
|
|
|
|
# Copy contents safely (includes hidden files)
|
|
tar -C "$INPUT_DIR" -cf - . | tar -C /mnt -xpf -
|
|
|
|
chmod -R 777 /mnt/*
|
|
|
|
umount /mnt
|
|
mdconfig -d -u ${MD}
|
|
|
|
echo "Created $OUTPUT"
|