├── .gitignore ├── README.md └── repack.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.cab 2 | *.cat 3 | *.inf 4 | *.zip 5 | *.bin 6 | *.metainfo.xml 7 | !template.metainfo.xml 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UEFI firmware updates for linux-surface 2 | 3 | This is a (mostly) automated patcher for converting the UEFI firmware updates 4 | that Microsoft ships with their official driver installation packages into a 5 | format that can be installed under linux using fwupd. 6 | 7 | Luckily for us Microsoft uses UEFI capsules for their firmwares, which is a 8 | standarized format already supported by fwupd. All we have to do is add a 9 | little bit of metadata. 10 | 11 | ### How? 12 | 13 | ```C 14 | #include 15 | 16 | /* 17 | * You are attempting to flash the firmware on an extemely locked down system. 18 | * It is unknown if the device can recover itself from a bad firmware flash. 19 | * 20 | * NO responsibility is taken for damages to your hardware or any other 21 | * consequences you may face because of flashing your firmware using unofficial 22 | * and unsupported tools. 23 | * 24 | * Be careful! 25 | */ 26 | ``` 27 | 28 | First you need to download the driver package for your surface model from 29 | Microsofts website. It will present you with a list of files, they are for 30 | multiple versions of Windows. Just use the first one. 31 | 32 | https://support.microsoft.com/en-us/help/4023482/surface-download-drivers-and-firmware-for-surface 33 | 34 | You will also have to install the `msiextract`, `gcab` and `dos2unix` programs through 35 | your distributions package manager. 36 | 37 | ```bash 38 | # Debian based 39 | $ sudo apt install msitools gcab dos2unix 40 | 41 | # Arch based, msitools is in the AUR, use whatever helper you like 42 | $ yay -S gcab msitools dos2unix 43 | 44 | # Fedora 45 | $ sudo dnf install msitools gcab dos2unix 46 | ``` 47 | 48 | We are going to assume you have a directory tree that looks like this, and 49 | that you are currently in the `surface-uefi-firmware` directory: 50 | 51 | ``` 52 | . 53 | ├── SurfaceBook2_Win10_19041_22.023.33295.0.msi 54 | └── surface-uefi-firmware 55 | ├── README.sh 56 | ├── repack.sh 57 | └── template.metainfo.xml 58 | 59 | 1 directory, 4 files 60 | ``` 61 | 62 | You will need to run the `repack.sh` script, which will unpack the MSI, 63 | extract all UEFI firmwares from it, and generate fwupd metadata for it. 64 | 65 | ```bash 66 | $ ./repack.sh ../SurfaceBook2_Win10_19041_22.023.33295.0.msi -o fwupdates 67 | ``` 68 | 69 | Once the script finishes you can find a list of cab files inside of 70 | the fwupdates folder: 71 | 72 | ```bash 73 | $ tree fwupdates/ 74 | fwupdates/ 75 | ├── SurfaceKeyboard_8.0.2048_96729509-963f-4608-bb2f-4788d0c76404.cab 76 | ├── surfaceme_208.8.13570_f3d5747d-24e3-44dd-9118-d332d932bced.cab 77 | ├── SurfacePD_1.0.1025_d8a91eed-fb95-4a5f-84db-9497294247e7.cab 78 | ├── surfacesam_6.1.12427_52ef6898-ded3-40bc-a1ee-36cc0459b1d4.cab 79 | ├── surfacesmf_57.0.299_8230d1a7-94f1-4f2d-934b-5fa2fb6a91c0.cab 80 | ├── surfacetouch_4.1.1536_408b2012-cc30-4abc-9fb9-545f18841262.cab 81 | ├── SurfaceTPM_7.2.512_a1adec1f-c12a-461d-b69c-114259d40cb6.cab 82 | ├── surfacetrackpad_0.8.0_1c12a6dd-54c2-4b20-8c17-3d3372a11096.cab 83 | └── surfaceuefi_15.0.2956_a1bb21b6-5cd1-48ea-ad29-f7c3236ebf0a.cab 84 | ``` 85 | 86 | Using fwupd, you can install all the firmware files at once 87 | 88 | ``` bash 89 | $ for f in fwupdates/*; do 90 | sudo fwupdmgr install --allow-older --allow-reinstall --no-reboot-check "$f" 91 | done 92 | ``` 93 | or à la carte 94 | 95 | ```bash 96 | $ fwupdmgr install --allow-older --allow-reinstall --force 97 | ``` 98 | 99 | 100 | ### Why? 101 | 102 | Because I don't want to dualboot, and reinstalling Windows every few months 103 | will cost more time over the long run than developing this. 104 | 105 | Additionally, this is more open than the Windows firmware update process. This 106 | allows you to downgrade the firmware in the event that you *really really 107 | need to*, unlike Windows. 108 | -------------------------------------------------------------------------------- /repack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eEuo pipefail 3 | trap 'echo "ERROR: Repacking failed"' ERR 4 | 5 | # GLOBALS 6 | declare -g FILE="" 7 | declare -g OUTPUT="fwupdates" 8 | declare -g CAB_ARRAY=() 9 | 10 | usage() 11 | { 12 | echo "Usage: $0 [OUTPUTDIR]" 13 | echo "Repackages Microsoft Surface firmware for fwupd" 14 | echo 15 | echo "Arguments:" 16 | echo " FILE The file to repack" 17 | echo " (can be .msi, .cab, .inf, or a directory)" 18 | echo " OUTPUTDIR The directory where to save the output" 19 | echo " (Optional; default is '$OUTPUT')" 20 | echo "Options:" 21 | echo " -h This help message" 22 | } 23 | 24 | 25 | # For backwards compatibility, allow -f and -o flags. 26 | eval set -- "$(getopt -o 'hf:o:' --long 'help,input:,output:' -- "$@")" 27 | while true; do 28 | case "${1}" in 29 | -f|--input) 30 | FILE="$2" 31 | shift 2 32 | ;; 33 | -o|--output) 34 | OUTPUT="$2" 35 | shift 2 36 | ;; 37 | -h|--help) 38 | usage 39 | exit 40 | ;; 41 | --) 42 | shift 43 | break 44 | ;; 45 | *) 46 | echo "ERROR: Invalid command line option '${1}'" 47 | exit 1 48 | ;; 49 | esac 50 | done 51 | 52 | if [ "$FILE" = "" ] && [ $# -gt 0 ]; then 53 | FILE="${1}" 54 | shift 55 | fi 56 | 57 | if [ "$#" -gt 0 ]; then 58 | OUTPUT="${1}" 59 | shift 60 | fi 61 | 62 | if [ "$#" -gt 0 ]; then 63 | echo "ERROR: Excess arguments: $*" 64 | exit 1 65 | fi 66 | 67 | if [ "$FILE" = "" ]; then 68 | echo "ERROR: No filename specified!" 69 | usage 70 | exit 1 71 | fi 72 | 73 | if [ "$OUTPUT" = "" ]; then 74 | echo "ERROR: No output directory specified!" 75 | exit 1 76 | fi 77 | 78 | for c in msiextract gcab dos2unix; do 79 | if ! command -v $c > /dev/null; then 80 | echo "ERROR: command '$c' not found, please install the corresponding package" 81 | exit 1 82 | fi 83 | done 84 | 85 | 86 | main() 87 | { 88 | mkdir -p "${OUTPUT}" 89 | 90 | case "${FILE}" in 91 | *.msi) repackmsi "${FILE}" "${OUTPUT}" 92 | ;; 93 | *.cab) repackcab "${FILE}" "${OUTPUT}" 94 | ;; 95 | *.inf) repackinf "${FILE}" "${OUTPUT}" 96 | ;; 97 | *) if [ -d "${FILE}" ]; then 98 | repackdir "${FILE}" "${OUTPUT}" 99 | else 100 | echo "==> ${FILE}: Invalid file type!" 101 | exit 1 102 | fi 103 | esac 104 | 105 | if [[ ${#CAB_ARRAY[@]} -gt 0 ]]; then 106 | echo "Success!" 107 | echo "If you wish, you may now install the firmware like so:" 108 | echo 109 | local f 110 | for f in "${CAB_ARRAY[@]}"; do 111 | echo -n " sudo fwupdmgr install --allow-older --allow-reinstall --no-reboot-check --force " 112 | echo "'$f'" 113 | done 114 | else 115 | echo "No firmware found in '${FILE}'" 116 | fi 117 | } 118 | 119 | 120 | repackinf() 121 | { 122 | # Parse parameters 123 | local INF="${1}" 124 | local OUT="${2}" 125 | 126 | # What is the name of the firmware? 127 | local DIR="$(dirname "${INF}")" 128 | local FIRMWARE="$(basename "${DIR}")" 129 | 130 | # Create a working directory 131 | local TEMP="$(mktemp -p . -d)" 132 | 133 | # Copy over files 134 | local BINFILE CATFILE INFFILE 135 | BINFILE="$(find "${DIR}" \( -iname '*.bin' -or -iname '*.cap' \) -print -quit)" 136 | CATFILE="$(find "${DIR}" -iname '*.cat' -print -quit)" 137 | INFFILE="$(find "${DIR}" -iname '*.inf' -print -quit)" 138 | 139 | if [ "$BINFILE" = "" ]; then 140 | echo "==> Skipping ${INF}" 141 | return 0 142 | fi 143 | 144 | cp "${BINFILE}" "${TEMP}/firmware.bin" 145 | cp "${CATFILE}" "${TEMP}/firmware.cat" 146 | cp "${INFFILE}" "${TEMP}/firmware.inf" 147 | 148 | # Update paths in the .inf file 149 | sed -i "s|$(basename "${BINFILE}")|firmware.bin|g" "${TEMP}/firmware.inf" 150 | sed -i "s|$(basename "${CATFILE}")|firmware.cat|g" "${TEMP}/firmware.inf" 151 | 152 | # Update the device GUID 153 | local -l DEVICE # -l: Values are always lowercase 154 | DEVICE="$(awk -F'[{}]' '/Firmware_Install, *UEFI/{print $2; exit}' "${TEMP}/firmware.inf")" 155 | 156 | # Update firmware type 157 | local CATEGORY 158 | case "$(basename "${INFFILE}")" in 159 | *UEFI*) CATEGORY="X-System" ;; 160 | *ME*) CATEGORY="X-ManagementEngine" ;; 161 | *) CATEGORY="X-Device" ;; 162 | esac 163 | 164 | # Update firmware version 165 | local VERSION 166 | VERSION="$(grep FirmwareVersion "${TEMP}/firmware.inf" | cut -d, -f5)" 167 | VERSION=${VERSION%$'\r'} 168 | local MAJOR="$(( (VERSION >> 24) & 0xff ))" 169 | local MINOR="$(( (VERSION >> 16) & 0xff ))" 170 | local REV="$(( VERSION & 0xffff ))" 171 | VERSION="${MAJOR}.${MINOR}.${REV}" 172 | 173 | # Update firmware timestamp 174 | local TIMESTAMP 175 | TIMESTAMP="$(awk -F'[=,]' '/^DriverVer/{print $2}' "${TEMP}/firmware.inf")" 176 | TIMESTAMP="$(date '+%s' --date "${TIMESTAMP}")" 177 | 178 | # Create metainfo file from $DEVICE, $CATEGORY, $VERSION, & $TIMESTAMP 179 | filltemplate "$DEVICE" "$CATEGORY" "$VERSION" "$TIMESTAMP" \ 180 | > "${TEMP}/firmware.metainfo.xml" 181 | 182 | # Create a cab file of the firmware 183 | local cabfile="${OUT}/${FIRMWARE}_${VERSION}_${DEVICE}.cab" 184 | gcab -cn "$cabfile" "${TEMP}"/* 185 | rm -r "${TEMP}" 186 | 187 | # Remember the cab filename for later 188 | CAB_ARRAY+=("$cabfile") 189 | } 190 | 191 | repackdir() 192 | { 193 | local DIR="${1}" 194 | local OUT="${2}" 195 | 196 | # Convert all .inf files to UTF-8 197 | find "${DIR}" -iname '*.inf' -exec dos2unix --quiet {} \; 198 | 199 | # Repack all UEFI capsule updates found in the directory 200 | local inffiles=($(grep -lR 'Firmware_Install,UEFI' "${DIR}")) 201 | 202 | local INF 203 | for INF in "${inffiles[@]}"; do 204 | echo "==> Repacking ${INF}" 205 | repackinf "${INF}" "${OUT}" 206 | done 207 | } 208 | 209 | repackmsi() 210 | { 211 | local MSI="${1}" 212 | local OUT="${2}" 213 | 214 | echo "==> Extracting ${MSI}" 215 | 216 | # Extract the MSI 217 | TEMP="$(mktemp -p . -d)" 218 | msiextract -C "${TEMP}" "${MSI}" > /dev/null 219 | 220 | # Repack all .inf files in the extracted MSI 221 | repackdir "${TEMP}" "${OUT}" 222 | 223 | # Clean up 224 | rm -r "${TEMP}" 225 | } 226 | 227 | repackcab() 228 | { 229 | local CAB="${1}" 230 | local OUT="${2}" 231 | 232 | echo "==> Extracting ${CAB}" 233 | 234 | # Extract the CAB 235 | local TEMP="$(mktemp -p . -d)" 236 | gcab -C "${TEMP}" -x "${CAB}" > /dev/null 237 | 238 | # Repack all .inf files in the extracted CAB 239 | repackdir "${TEMP}" "${OUT}" 240 | 241 | # Clean up 242 | rm -r "${TEMP}" 243 | } 244 | 245 | filltemplate() 246 | { 247 | # Fill in the XML template 248 | local DEVICE="$1" CATEGORY="$2" VERSION="$3" TIMESTAMP="$4" 249 | 250 | cat < 252 | 253 | com.surfacelinux.firmware.${DEVICE} 254 | 255 | ${DEVICE} 256 | 257 | Surface Firmware 258 | Firmware for ${DEVICE} 259 | 260 |

Updating the firmware on your device improves performance and adds new features.

261 |
262 | 263 | ${CATEGORY} 264 | 265 | 266 | org.uefi.capsule 267 | 268 | https://www.microsoft.com 269 | CC0-1.0 270 | proprietary 271 | Microsoft 272 | 273 | 274 | 275 |

Please visit the Microsoft homepage to find more information about this update.

276 |

The computer will be restarted automatically after updating completely. Do NOT turn off your computer or remove the AC adaptor while update is in progress.

277 |
278 |
279 |
280 |
281 | EOF 282 | 283 | } 284 | 285 | main "$@" 286 | 287 | --------------------------------------------------------------------------------