Reactos
1#!/bin/sh
2# shellcheck disable=SC3003,SC3045
3
4##
5## PROJECT: ReactOS ISO Remastering Script
6## LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
7## PURPOSE: Allows easy remastering of customized ReactOS ISO images.
8## Based on the boot/boot_images.cmake script in the ReactOS
9## source tree. Requires a MKISOFS-compatible utility.
10## COPYRIGHT: Copyright 2025 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
11##
12## POSIX shell compliance checker: https://www.shellcheck.net
13##
14
15## echo -ne "\033]0;ReactOS ISO Remastering Script\007"
16
17##
18## Customizable settings
19##
20## ISO image identifier names
21ISO_MANUFACTURER="ReactOS Project" # For both the publisher and the preparer
22ISO_VOLNAME="ReactOS" # For both the Volume ID and the Volume set ID
23
24## Image names of the MKISOFS and ISOHYBRID tools
25MKISOFS=mkisofs
26ISOHYBRID=isohybrid
27
28
29##
30## Main script
31##
32clear
33echo "*******************************************************************************"
34echo "* *"
35echo "* ReactOS ISO Remastering Script *"
36echo "* *"
37echo "*******************************************************************************"
38echo
39
40exit_script()
41{
42 ERRLVL=${1:-$?} # Use parameter, otherwise default to $?
43 [ "$ERRLVL" -eq 0 ] && echo "Success!"
44 # shellcheck disable=SC3045
45 read -n 1 -r -s -p $'Press any key to quit...\n'
46 exit "$ERRLVL"
47}
48
49##
50## Prompt the user for a choice.
51## Usage example:
52## choice YN "Yes or No [Y,N]? "
53##
54## Return in $REPLY the index of the reply in the choices list.
55##
56## See also:
57## https://stackoverflow.com/q/226703
58##
59choice()
60{
61 ## Normalize the choices list to lowercase, and prepare
62 ## the pattern to match for only one single character.
63 CHOICE_LIST="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
64 CHOICE_PATTERN="^[$CHOICE_LIST]$"
65
66 ## Echo prompt and wait for keypress
67 shift 1
68 # shellcheck disable=SC3037
69 echo -n "$@"
70 REPLY_LOWER=
71 until # Adapted from https://unix.stackexchange.com/a/249036
72 # shellcheck disable=SC3045
73 read -N 1 -r -s
74 REPLY_LOWER="$(echo "$REPLY" | tr '[:upper:]' '[:lower:]')"
75 expr "$REPLY_LOWER" : "$CHOICE_PATTERN" 1>/dev/null
76 do :; done
77 echo "$REPLY"
78
79 ## Return the index of the reply in the choices list
80 #REPLY=$(expr index "$CHOICE_LIST" "$REPLY_LOWER")
81 REPLY=${CHOICE_LIST%%"$REPLY_LOWER"*}
82 #REPLY=$(( $(expr "$REPLY" : ".*")+1 ))
83 REPLY=$(( ${#REPLY}+1 ))
84}
85
86
87## Verify that we have access to a temporary directory.
88for TMPDIR in "$TMPDIR" "$TEMP" "$TMP" "/tmp"; do
89 [ -n "$TMPDIR" ] && [ -d "$TMPDIR" ] && break # Valid directory found
90done
91if [ -z "$TMPDIR" ] || [ ! -d "$TMPDIR" ]; then
92 echo No temporary directory exists on your system.
93 echo Please create one and assign it to the TMPDIR environment variable.
94 echo
95 exit_script 1
96fi
97
98
99## Try to auto-locate MKISOFS and if not, prompt the user for a directory.
100TOOL_DIR=
101TOOL_PATH=$(type -p $MKISOFS)
102if [ -z "$TOOL_PATH" ]; then
103 read -e -r -p $'Please enter the directory path where '$MKISOFS$' can be found:\n' TOOL_DIR
104 echo
105 # Non-POSIX-compatible test: [ "${TOOL_DIR:(-1)}" = "/" ]
106 [ "${TOOL_DIR#"${TOOL_DIR%?}"}" = "/" ] && TOOL_DIR=${TOOL_DIR%?}
107 TOOL_PATH=$TOOL_DIR/$MKISOFS
108else
109 # Get the directory without the '/filename' part (and doesn't include trailing /)
110 TOOL_DIR=${TOOL_PATH%/*}
111fi
112MKISOFS=$TOOL_PATH
113
114
115read -e -r -p $'Please enter the path of the directory tree to image into the ISO:\n' INPUT_DIR
116echo
117read -e -r -p $'Please enter the file path of the ISO image that will be created:\n' OUTPUT_ISO
118echo
119
120
121## Retrieve the full paths to the 'isombr', 'isoboot', 'isobtrt' and 'efisys' files
122isombr_file=loader/isombr.bin
123isoboot_file=loader/isoboot.bin
124isobtrt_file=loader/isobtrt.bin
125efisys_file=loader/efisys.bin
126
127#choice 12 $'Please choose the ISO boot file: 1) isoboot.bin ; 2) isobtrt.bin\n[default: 1]: '
128choice YN $'Do you want the ReactOS media to wait for a key-press before booting [Y,N]? '
129echo
130ISOBOOT_PATH=$isoboot_file
131if [ "$REPLY" -eq 1 ]; then
132 ISOBOOT_PATH=$isoboot_file
133elif [ "$REPLY" -eq 2 ]; then
134 ISOBOOT_PATH=$isobtrt_file
135fi
136
137## Enable (U)EFI boot support if possible: check the
138## presence of '$efisys_file' in the ISO directory tree.
139#ISO_EFI_BOOT_PARAMS
140ISO_BOOT_EFI_OPTIONS=$([ -f "$INPUT_DIR/$efisys_file" ] && \
141 echo "-eltorito-alt-boot -eltorito-platform efi -eltorito-boot $efisys_file -no-emul-boot")
142
143## Summary of the boot files.
144echo "ISO boot file: '$ISOBOOT_PATH'"
145[ -n "$ISO_BOOT_EFI_OPTIONS" ] && echo "EFI boot file: '$efisys_file'"
146echo
147
148
149choice YN $'Do you want to store duplicated files only once (reduces the size\nof the ISO image) [Y,N]? '
150echo
151DUPLICATES_ONCE=$([ "$REPLY" -eq 1 ] && echo "-duplicates-once")
152
153
154echo "Creating the ISO image..."
155echo
156
157## Create a mkisofs sort file to specify an explicit ordering for the boot files
158## to place them at the beginning of the image (makes ISO image analysis easier).
159## See mkisofs/schilytools/mkisofs/README.sort and boot/boot_images.cmake script
160## in the ReactOS source tree for more details.
161
162## echo ${CMAKE_CURRENT_BINARY_DIR}/empty/boot.catalog 4
163cat > "$TMPDIR/bootfiles.sort" << EOF
164boot.catalog 4
165$INPUT_DIR/$isoboot_file 3
166$INPUT_DIR/$isobtrt_file 2
167$INPUT_DIR/$efisys_file 1
168EOF
169
170## Finally, create the ISO image proper.
171#echo "Running command:
172#$MKISOFS \
173# -o \"$OUTPUT_ISO\" -iso-level 4 \
174# -publisher \"$ISO_MANUFACTURER\" -preparer \"$ISO_MANUFACTURER\" \
175# -volid \"$ISO_VOLNAME\" -volset \"$ISO_VOLNAME\" \
176# -eltorito-boot $ISOBOOT_PATH -no-emul-boot -boot-load-size 4 \
177# $ISO_BOOT_EFI_OPTIONS \
178# -hide boot.catalog -sort \"$TMPDIR/bootfiles.sort\" \
179# $DUPLICATES_ONCE -no-cache-inodes \"$INPUT_DIR\"
180#"
181$MKISOFS \
182 -o "$OUTPUT_ISO" -iso-level 4 \
183 -publisher "$ISO_MANUFACTURER" -preparer "$ISO_MANUFACTURER" \
184 -volid "$ISO_VOLNAME" -volset "$ISO_VOLNAME" \
185 -eltorito-boot $ISOBOOT_PATH -no-emul-boot -boot-load-size 4 \
186 $ISO_BOOT_EFI_OPTIONS \
187 -hide boot.catalog -sort "$TMPDIR/bootfiles.sort" \
188 $DUPLICATES_ONCE -no-cache-inodes "$INPUT_DIR"; ERRLVL=$?
189## -graft-points -path-list "some/directory/iso_image.lst"
190echo
191rm "$TMPDIR/bootfiles.sort"
192
193if [ "$ERRLVL" -ne 0 ]; then
194 echo "An error $ERRLVL happened while creating the ISO image \"$OUTPUT_ISO\"."
195 exit_script $ERRLVL
196fi
197echo "The ISO image \"$OUTPUT_ISO\" has been successfully created."
198echo
199
200
201## Check whether ISOHYBRID is also available and if so, propose to post-process
202## the generated ISO image to allow hybrid booting as a CD-ROM or as a hard disk.
203TOOL_PATH=$TOOL_DIR/$ISOHYBRID
204if [ -z "$TOOL_PATH" ] || [ ! -f "$TOOL_PATH" ]; then
205 TOOL_PATH=$(type -p $ISOHYBRID)
206 if [ -z "$TOOL_PATH" ]; then
207 echo "$ISOHYBRID patching skipped."
208 exit_script 0
209 fi
210fi
211ISOHYBRID=$TOOL_PATH
212
213
214choice YN $'Do you want to post-process the ISO image to allow hybrid booting\nas a CD-ROM or as a hard disk [Y,N]? '
215echo
216[ "$REPLY" -ne 1 ] && exit_script 0
217
218echo "Patching the ISO image..."
219
220"$ISOHYBRID" -b "$INPUT_DIR/$isombr_file" -t 0x96 "$OUTPUT_ISO"; ERRLVL=$?
221echo
222
223if [ "$ERRLVL" -ne 0 ]; then
224 echo "An error $ERRLVL happened while patching the ISO image \"$OUTPUT_ISO\"."
225 exit_script $ERRLVL
226## else
227## echo "The ISO image \"$OUTPUT_ISO\" has been successfully patched."
228fi
229echo
230
231exit_script 0