Skip to content

Commit d0b0a1f

Browse files
committed
Support Pi4 and other non-A76 ARM cores with old windows build
Fixes honoring disable_updates value Adds non-A76 core detection, and downloading of windows 11 version 22631 if detected Adds wimtools dependency Ensure vmdir is not FAT partition Try to support Rockchip RK3399 by only running on performance cores (untested) Closes #5
1 parent c6f8d86 commit d0b0a1f

File tree

1 file changed

+153
-28
lines changed

1 file changed

+153
-28
lines changed

bvm

Lines changed: 153 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ ${white}BOTSPOT VIRTUAL MACHINE ${black} ${rpired} ${black}
6565
"Introducing: a really good way to use up storage space!"
6666
"How many of these captions do I have to write!?" -Botspot
6767
"Is pure Linux better? Yes. But this helps more people stick around."
68-
"We’re pleased to announce a more efficient way to run inefficient software."
68+
"Pleased to announce an even more efficient way to run inefficient software."
6969
"PROTIP: Try selecting the lines of text above for an easter egg! :)"
7070
"Mixing Linux and Windows... what could possibly go wrong?"
7171
"Turns out Raspberry and Windows do mix... forming a very crunchy smoothie."
@@ -99,11 +99,13 @@ get_space_free() { #Input: folder to check. Output: show many bytes can fit befo
9999
df -B 1 "$1" --output=avail | tail -1 | tr -d ' '
100100
}
101101

102-
list_download_languages() {
103-
#derived from quickemu project
104-
echo -e "Arabic\nBrazilian Portuguese\nBulgarian\nChinese (Simplified)\nChinese (Traditional)\nCroatian\nCzech\nDanish\nDutch\nEnglish (United States)\nEnglish International
105-
Estonian\nFinnish\nFrench\nFrench Canadian\nGerman\nGreek\nHebrew\nHungarian\nItalian\nJapanese\nKorean\nLatvian\nLithuanian\nNorwegian\nPolish\nPortuguese\nRomanian\nRussian
106-
Serbian Latin\nSlovak\nSlovenian\nSpanish\nSpanish (Mexico)\nSwedish\nThai\nTurkish\nUkrainian"
102+
list_download_languages() { #langcode:language
103+
echo -e "ar-sa:Arabic\npt-br:Brazilian Portuguese\nbg-bg:Bulgarian\nzh-cn:Chinese (Simplified)\nzh-tw:Chinese (Traditional)\nhr-hr:Croatian\ncs-cz:Czech
104+
da-dk:Danish\nnl-nl:Dutch\nen-us:English (United States)\nen-gb:English International\net-ee:Estonian\nfi-fi:Finnish\nfr-fr:French\nfr-ca:French Canadian
105+
de-de:German\nel-gr:Greek\nhe-il:Hebrew\nhu-hu:Hungarian\nit-it:Italian\nja-jp:Japanese\nko-kr:Korean\nlv-lv:Latvian\nlt-lt:Lithuanian\nnb-no:Norwegian
106+
pl-pl:Polish\npt-pt:Portuguese\nro-ro:Romanian\nru-ru:Russian\nsr-latn-rs:Serbian Latin\nsk-sk:Slovak\nsl-si:Slovenian\nes-es:Spanish\nes-mx:Spanish (Mexico)
107+
sv-se:Swedish\nth-th:Thai\ntr-tr:Turkish\nuk-ua:Ukrainian"
108+
107109
}
108110

109111
process_exists() { #return 0 if the $1 PID is running, otherwise 1
@@ -167,6 +169,7 @@ mount_qcow2() { #mount the $1 qcow2 disk image to /media/$USER/bvm-mount$rdp_por
167169
unmount_qcow2() { #unmount what was mounted by the mount_qcow2 function
168170
#find which /dev/nbd* is used by this mountpoint, before unmounting it
169171
local nbd_device="/dev/$(lsblk -no MOUNTPOINTS,PKNAME | grep "^/media/$USER/bvm-mount$rdp_port " | awk '{print $2}' | head -n1)"
172+
[ "$nbd_device" == /dev/ ] && return 1
170173

171174
umount_retry /media/$USER/bvm-mount$rdp_port
172175

@@ -178,6 +181,72 @@ unmount_qcow2() { #unmount what was mounted by the mount_qcow2 function
178181
sudo rmdir /media/$USER/bvm-mount$rdp_port
179182
}
180183

184+
download_windows_11arm64_22631() { #downloads older W11 version for ARMv8.0 CPUs such as the Raspberry Pi 4 or 3
185+
#from https://github.com/mattieb/windows-esd-to-iso/blob/main/windows-esd-to-iso
186+
#and from wor-flasher
187+
188+
#convert from pretty language name to short-code used by esd releases - example: en-us
189+
local WIN_LANG="$(list_download_languages | grep ":${download_language}$" | awk -F: '{print $1}')"
190+
[ -z "$WIN_LANG" ] && error "download_windows_11arm64_22631: language must be specified in download_language variable. Get list of available languages by running $DIRECTORY/bvm list-languages"
191+
[ -z "$vmdir" ] && error "download_windows_11arm64_22631: download folder for installer.iso must be specified in vmdir variable"
192+
193+
status "Downloading Windows 11 ARM64 (${download_language})"
194+
195+
echo " - Getting ESD download URL..."
196+
catalog="$(wget -nv -O- "https://worproject.com/dldserv/esd/getcatalog.php?build=22631.2861&arch=ARM64&edition=Professional")"
197+
catalog="$(echo "$catalog" | sed 's/></>\n</g' | sed -n '/<Languages>/q;p' | sed -n '/^<LanguageCode>'"${WIN_LANG}"'/,${p;/^<\/File>/q}')"
198+
199+
[ -z "$catalog" ] && error "Could not get list of Windows ESD releases. If you ran this step several times recently, the site likely temporarily banned your IP address. Give it time, and check if this site is accessible from your network: https://worproject.com/dldserv/esd/getcatalog.php"
200+
201+
rm -rf "$vmdir/esdextract" || error "Failed to remove esdextract folder"
202+
mkdir "$vmdir/esdextract" || error "Directory creation failed"
203+
204+
#Get download link, size, and SHA1 hash for ESD
205+
local URL="$(echo "$catalog" | grep '<FilePath>' -m 1 | sed 's/<FilePath>//g' | sed 's/<\/FilePath>//g')"
206+
local SIZE="$(echo "$catalog" | grep '<Size>' -m 1 | sed 's/<Size>//g' | sed 's/<\/Size>//g')"
207+
local SHA1="$(echo "$catalog" | grep '<Sha1>' -m 1 | sed 's/<Sha1>//g' | sed 's/<\/Sha1>//g')"
208+
local SOURCE_FILE="$vmdir/image.esd"
209+
210+
if [ -f "$SOURCE_FILE" ] && [ "$SHA1" == "$(echo " - Checking validity of already downloaded image.esd" 1>&2 ; sha1sum "$SOURCE_FILE" | awk '{print $1}')" ];then
211+
echo " - Not downloading $SOURCE_FILE - file exists"
212+
else
213+
echo " - Downloading Windows ESD image"
214+
wget -nv --show-progress "$URL" -O "$SOURCE_FILE" || error "Failed to download ESD image"
215+
echo " - Verifying download... "
216+
local LOCAL_SHA1="$(sha1sum "$SOURCE_FILE" | awk '{print $1}')"
217+
if [ "$SHA1" != "$LOCAL_SHA1" ];then
218+
error "\nSuccessfully downloaded ESD image $SOURCE_FILE, but it appears to be corrupted. Please run this script again.\n(Expected SHA1 hash is $SHA1, but downloaded file has SHA1 hash $LOCAL_SHA1"
219+
fi
220+
echo "Done"
221+
fi
222+
223+
echo " - Scanning ESD image for partitions... "
224+
#should always be 6, but doubles as a validity check
225+
local professional_partition_num="$(wiminfo "$SOURCE_FILE" | grep -xB1 'Name: *Windows 11 Pro' | head -n1 | awk '{print $2}')"
226+
[ -z "$professional_partition_num" ] && error "\nCould not find Windows Professional in image.esd"
227+
228+
status "Extracting Windows Setup Media to esdextract"
229+
wimapply "$SOURCE_FILE" 1 "$vmdir/esdextract" || error "Operation failed"
230+
231+
status "Extracting Microsoft Windows PE to boot.wim"
232+
wimexport "$SOURCE_FILE" 2 "$vmdir/esdextract/sources/boot.wim" --compress=LZX --chunk-size=32K || error "Operation failed"
233+
234+
status "Extracting Microsoft Windows Setup to boot.wim"
235+
wimexport "$SOURCE_FILE" 3 "$vmdir/esdextract/sources/boot.wim" --compress=LZX --chunk-size=32K --boot || error "Operation failed"
236+
237+
status "Extracting Windows 11 Pro to install.wim" #compression is usually LZMS with 128k chunk size, but this was taking over an hour.
238+
wimexport "$SOURCE_FILE" $professional_partition_num "$vmdir/esdextract/sources/install.wim" --compress=none || error "Operation failed"
239+
240+
#Make boot noninteractive - do what patch_iso_noprompt does but ahead of time as we are creating the ISO
241+
cp -f "$vmdir/esdextract/efi/microsoft/boot/efisys_noprompt.bin" "$vmdir/esdextract/efi/microsoft/boot/efisys.bin" || error "Failed to copy efisys_noprompt.bin"
242+
243+
rm -f "$vmdir/installer.iso"
244+
status "Making installer.iso disk image..."
245+
(cd "$vmdir/esdextract"; genisoimage -o "$vmdir/installer.iso" -iso-level 3 -udf -b efi/microsoft/boot/efisys.bin -no-emul-boot -V "ESD_ISO" -allow-limited-size . 2>&1 | tee >(grep -v " done," 1>&2) | tr '\n' '\r' ; exit "${PIPESTATUS[0]}") || error "Operation failed"
246+
247+
rm -rf "$vmdir/esdextract" "$vmdir/image.esd"
248+
}
249+
181250
download_windows_11arm64() { #download latest stable windows 11 ISO language $download_language to $vmdir
182251
# This function is adapted from the Mido project:
183252
# https://github.com/ElliotKillick/Mido
@@ -197,8 +266,8 @@ download_windows_11arm64() { #download latest stable windows 11 ISO language $do
197266
Make sure the file is named installer.iso
198267
Then run this action again."
199268

200-
[ -z "$download_language" ] && error "download_windows_11arm64: language must be specified in download_language variable Available languages:\n$(list_download_languages | tr '\n' '\t' | sed 's/\t/, \t/g')"
201-
list_download_languages | grep -qF "$download_language" || error "download_windows_11arm64: unrecognized language '$download_language'. Available languages:\n$(list_download_languages | tr '\n' '\t' | sed 's/\t/, \t/g')"
269+
[ -z "$download_language" ] && error "download_windows_11arm64: language must be specified in download_language variable. Get list of available languages by running $DIRECTORY/bvm list-languages"
270+
list_download_languages | sed 's/.*://g' | grep -qFx "$download_language" || error "download_windows_11arm64: unrecognized language '$download_language'. Get list of available languages by running $DIRECTORY/bvm list-language"
202271
[ -z "$vmdir" ] && error "download_windows_11arm64: download folder for installer.iso must be specified in vmdir variable"
203272

204273
status "Downloading Windows 11 ARM64 (${download_language})"
@@ -371,8 +440,8 @@ get_codename() { #get debian/ubuntu codename
371440
}
372441

373442
install_dependencies() { #try to install everything BVM needs
374-
required_commands="git jq wget mkisofs qemu-img qemu-system-aarch64 remmina nmap wget yad uuidgen"
375-
apt_packages="git jq wget genisoimage qemu-utils qemu-system-arm qemu-system-gui qemu-efi-aarch64 remmina remmina-plugin-rdp nmap wget yad uuid-runtime seabios ipxe-qemu"
443+
required_commands="git jq wget mkisofs qemu-img qemu-system-aarch64 remmina nmap wget yad uuidgen wiminfo"
444+
apt_packages="git jq wget genisoimage qemu-utils qemu-system-arm qemu-system-gui qemu-efi-aarch64 remmina remmina-plugin-rdp nmap wget yad uuid-runtime seabios ipxe-qemu wimtools"
376445

377446
#using freerdp version 2 instead of 3 because it does not freeze on login and has more reliable clipboard sync
378447
if [ "$XDG_SESSION_TYPE" == wayland ];then
@@ -449,6 +518,7 @@ update_check() { #check for updates and reload the script if necessary
449518
if [ "$localhash" != "$latesthash" ] && [ ! -z "$latesthash" ] && [ ! -z "$localhash" ];then
450519
if [ "$disable_updates" == true ];then
451520
echo "Updates available, but you have auto-updates disabled."
521+
return 0
452522
fi
453523
echo "Auto-updating BVM for the latest features and improvements..."
454524
(cd "$DIRECTORY"
@@ -476,6 +546,23 @@ copy_icons() { #install icons to a location where the panel will notice them
476546
#for wf-panel-pi at least, updating icon caches seems to do no good
477547
}
478548

549+
find_a76_cores() { #Some rockchip CPUs have A55 and A76 cores, and latest Windows will only support A76. Return 1 if not all cores are the same, list the A76 ones no matter what
550+
local cores="$(cat /proc/cpuinfo | grep 'processor\|Features\|^$' | tr '\n' '\r' | sed 's/\r\r/\n/g ; s/\r/ /g')"
551+
552+
local a76_cores="$(echo "$cores" | grep -w fphp | awk '{print $3}')"
553+
local non_a76_cores="$(echo "$cores" | grep -vw fphp | awk '{print $3}')"
554+
555+
if [ -z "$a76_cores" ] || [ -z "$non_a76_cores" ];then
556+
#all the cores are the same
557+
echo "$a76_cores"
558+
return 0
559+
else
560+
#list the cores that are A76
561+
echo "$a76_cores"
562+
return 1
563+
fi
564+
}
565+
479566
DIRECTORY="$(readlink -f "$(dirname "$0")")"
480567

481568
original_flags=("$@")
@@ -558,8 +645,12 @@ if ! sudo modprobe kvm ;then
558645
#check RAM and choose how much to allocate the VM
559646
elif [ "$(awk '/MemTotal/ {print $2}' /proc/meminfo)" -le $((1*1024*1024)) ];then
560647
error "Your system needs at least 2 GB of RAM. Be aware that a VM might be able to boot on 1 GB of RAM, but it cannot install Windows with less than 2 GB."
648+
#check OS architecture: ARM64 userland required
561649
elif [ "$(od -An -t x1 -j 4 -N 1 "$(readlink -f /sbin/init)")" = ' 01' ];then
562650
error "OS CPU architecture is 32-bit! BVM only works on ARM 64-bit operating systems."
651+
#check storage location for ISO is not a FAT partition
652+
elif [ ! -z "$vmdir" ] && df -T "$vmdir" 2>/dev/null | grep -q 'fat' ;then
653+
error "The $DL_DIR directory is on a FAT32/FAT16/vfat partition. This type of partition cannot contain files larger than 4GB, however the Windows image will be larger than that.\nPlease format the drive with an Ext4 partition, or use another drive."
563654
fi
564655

565656
install_dependencies || exit 1
@@ -599,7 +690,7 @@ To get a fresh VM up and running, use a sequence like this:
599690
;;
600691

601692
list-languages)
602-
list_download_languages
693+
list_download_languages | sed 's/.*://g'
603694
;;
604695

605696
gui)
@@ -772,8 +863,19 @@ To get a fresh VM up and running, use a sequence like this:
772863
error "Insufficient free disk space. $disksize GB is needed, but you only have $(($(get_space_free "$vmdir")/1024/1024/1024)) GB. You can try reducing the desired disk size in the config file to get around this error."
773864
fi
774865

775-
#vmdir and $download_language must be set for this function
776-
download_windows_11arm64 || exit 1
866+
#get either latest ISO if CPU supports it, or W11 22631 if CPU is not ARMv8.1+
867+
if grep -wq fphp /proc/cpuinfo ;then
868+
#ARMv8.1+, get latest windows (Pi5, A76 core)
869+
#vmdir and $download_language must be set for this function
870+
download_windows_11arm64 || exit 1
871+
872+
#Patch the ISO to not require a keypress to boot
873+
patch_iso_noprompt "$vmdir/installer.iso" || exit 1
874+
else
875+
warning "Downloading an older Windows version that is compatible with your device. Later versions of Windows 11 require ARM instructions that your CPU lacks."
876+
#get Windows build 22631 for Pi4s and other A53 CPUs
877+
download_windows_11arm64_22631 || exit 1
878+
fi
777879

778880
status "Downloading virtio drivers"
779881
wget -nv --show-progress "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" -O "${vmdir}/virtio-win.iso" || error "Downloading virtio-win.iso failed"
@@ -839,8 +941,6 @@ To get a fresh VM up and running, use a sequence like this:
839941

840942
prepare) #take downloaded files and get everything ready for first boot
841943

842-
patch_iso_noprompt "$vmdir/installer.iso" || exit 1
843-
844944
#make drive to pass to installer (virtio arm drivers with unattended stuff and debloating script)
845945
status -n "Making unattended.iso... "
846946
#lesson learned: -J -r -allow-lowercase -allow-multidot fixes debloat script names being truncated
@@ -870,7 +970,19 @@ To get a fresh VM up and running, use a sequence like this:
870970

871971
firstboot) #minimal QEMU command-line to get through OS installation from installer.iso to disk.qcow2
872972

873-
full_qemu_flags=(-M virt,accel=kvm -cpu host -m ${vm_mem}G -smp $(nproc) \
973+
#determine if only some CPU cores can be used
974+
if a76_cores="$(find_a76_cores)" ;then
975+
#function returned 0, so all cores are the same and taskset is not needed
976+
num_cores=$(nproc) #give QEMU all cores the CPU has
977+
use_taskset=false
978+
else
979+
#some cores are performance cores, use them
980+
warning "CPU detected with 2 types of cores. Trying to only use the performance cores, but this is untested code. PLEASE reach out to Botspot if it works, or if it does not work."
981+
num_cores="$(echo "$a76_cores" | wc -l)"
982+
use_taskset=true
983+
fi
984+
985+
full_qemu_flags=(-M virt,accel=kvm -cpu host -m ${vm_mem}G -smp $num_cores \
874986
-name BVM \
875987
-pidfile "$vmdir/qemu.pid" \
876988
-device ramfb -display gtk,grab-on-hover=on,gl=on \
@@ -888,7 +1000,12 @@ To get a fresh VM up and running, use a sequence like this:
8881000

8891001
status "Windows should install 100% automatically. This will take several hours."
8901002
#run qemu with these flags
891-
qemu-system-aarch64 "${full_qemu_flags[@]}" || error "QEMU did not exit successfully."
1003+
if [ "$use_taskset" == false ];then
1004+
qemu-system-aarch64 "${full_qemu_flags[@]}" || error "QEMU did not exit successfully."
1005+
else
1006+
#For rockchip (untested but based on https://gist.github.com/Vogtinator/293c4f90c5e92838f7e72610725905fd?permalink_comment_id=5378278#gistcomment-5378278)
1007+
taskset -c "$(echo "$a76_cores" | tr '\n' ',' | sed 's/,$//g')" qemu-system-aarch64 "${full_qemu_flags[@]}" || error "QEMU did not exit successfully."
1008+
fi
8921009
status "QEMU closed."
8931010

8941011
#check disk.qcow2 and remove microsoft defender if debloat enabled
@@ -961,20 +1078,23 @@ To get a fresh VM up and running, use a sequence like this:
9611078
usb_forwarding_flags=(-device usb-host,vendorid=0x'05dc',productid=0x'a720',driver=usb-host)
9621079
done
9631080

964-
#handle network passthrough preferences
965-
966-
#To allow multiple VMs and RDP connections, pick a unique port for each one (handled by config file now)
967-
#while sudo lsof -nP -i:"${rdp_port}" >/dev/null ;do
968-
#start with 3389, but increment if necessary
969-
# rdp_port=$((rdp_port+1))
970-
#done
971-
#debug "found available port: $rdp_port"
1081+
#determine if only some CPU cores can be used
1082+
if a76_cores="$(find_a76_cores)" ;then
1083+
#function returned 0, so all cores are the same and taskset is not needed
1084+
num_cores=$(nproc) #give QEMU all cores the CPU has
1085+
use_taskset=false
1086+
else
1087+
#some cores are performance cores, use them
1088+
warning "CPU detected with 2 types of cores. Trying to only use the performance cores, but this is untested code. PLEASE reach out to Botspot if it works, or if it does not work."
1089+
num_cores="$(echo "$a76_cores" | wc -l)"
1090+
use_taskset=true
1091+
fi
9721092

9731093
#forward guest's port 3389 to localhost port of our choice (handled by config file now)
9741094
#network_flags=(-netdev user,id=nic,hostfwd=tcp:127.0.0.1:${rdp_port}-:3389 -device virtio-net-pci,netdev=nic)
9751095

9761096
#all QEMU flags are combined together here
977-
full_qemu_flags=(-M virt,accel=kvm -cpu host -m ${vm_mem}G -smp $(nproc) \
1097+
full_qemu_flags=(-M virt,accel=kvm -cpu host -m ${vm_mem}G -smp $num_cores \
9781098
-name BVM,process=bvm \
9791099
-pidfile "$vmdir/qemu.pid" \
9801100
-device virtio-balloon \
@@ -994,8 +1114,13 @@ To get a fresh VM up and running, use a sequence like this:
9941114
debug "full_qemu_flags: " "${full_qemu_flags[@]}"
9951115

9961116
#run qemu with these flags
997-
qemu-system-aarch64 "${full_qemu_flags[@]}" || error "QEMU did not exit successfully."
998-
1117+
if [ "$use_taskset" == false ];then
1118+
qemu-system-aarch64 "${full_qemu_flags[@]}" || error "QEMU did not exit successfully."
1119+
else
1120+
#For rockchip (untested but based on https://gist.github.com/Vogtinator/293c4f90c5e92838f7e72610725905fd?permalink_comment_id=5378278#gistcomment-5378278)
1121+
taskset -c "$(echo "$a76_cores" | tr '\n' ',' | sed 's/,$//g')" qemu-system-aarch64 "${full_qemu_flags[@]}" || error "QEMU did not exit successfully."
1122+
fi
1123+
status "QEMU closed."
9991124
;;
10001125
connect*)
10011126
#make sure the qemu process is running

0 commit comments

Comments
 (0)