├── .gitattributes ├── LICENSE ├── README.md ├── flash.sh └── screenshot.png /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gabriel Zimmerli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwitchrootAndroidUtils 2 | Utils for Switchroot's LineageOS Android ROM for Nintendo Switch. 3 | 4 | ## flash.sh 5 | An interactive bash script to flash Switchroot's Android image on a microSD card. It allows customizing the size for the Nintendo Switch partition and Android user partition, therefore enables the usage of the same microSD card for the Nintendo Switch and Android (and a Homebrew enabled Switch on a [EMUMMC partition](https://nh-server.github.io/switch-guide/user_guide/emummc/making_emummc/)). Unlike flashing the whole image with Etcher and resizing the partitions afterwards, the script has the following advantages: 6 | * Size for Nintendo Switch partition and Android user partition can be defined by the user 7 | * Support for an additional partition for EMUMMC 8 | * No wasted space on the microSD card, no matter what size the card has 9 | * The whole process is faster, because it does not dump Gigabytes of empty partition data to the microSD card 10 | * No fragmentation or breaking the (hybrid MBR) partition table because of moving and resizing partitions 11 | * The partitions are properly aligned (to 1 MiB) 12 | 13 | ### Requirements 14 | * bash (v4+) 15 | * sfdisk 16 | * awk 17 | * lsblk 18 | * sfdisk 19 | * mkfs 20 | * gdisk 21 | * dd 22 | * losetup 23 | 24 | All these programs / commands should be preinstalled on most recent Linux distributions. If you don't have Linux installed, it is possible to run everything from the [Ubuntu Live-USB](https://tutorials.ubuntu.com/tutorial/tutorial-create-a-usb-stick-on-windows). 25 | 26 | Unfortunately, I was not able to run the script within WSL on Windows 10, because there is no access to block level devices. 27 | 28 | ### Usage 29 | 1. Download the 16GB image from [Switchroot's XDA-Developers post](https://forum.xda-developers.com/nintendo-switch/nintendo-switch-news-guides-discussion--development/rom-switchroot-lineageos-15-1-t3951389) and extract the ZIP file. 30 | 2. Download "flash.sh" to the same directory where the image is. 31 | 3. Open Terminal emulator and navigate to the directory where the image and script are (in Ubuntu, you can use the File explorer to navigate there, right click the folder and select "Open in Terminal"). 32 | 4. Execute the script and pass the path to the Android image as a parameter: 33 | ``` 34 | sudo ./flash.sh ./android-16gb.img 35 | ``` 36 | 5. Follow the instructions in the interactive script. 37 | 6. Follow the remaining instructions in the XDA-Developers post (from step 3). 38 | 39 | ![Screenshot](/screenshot.png) 40 | -------------------------------------------------------------------------------- /flash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RED=$(printf '\033[01;31m') 4 | NC=$(printf '\033[0m') 5 | 6 | CR=$'\n> ' 7 | NL=${CR%> } 8 | 9 | EMUMMC_SECTORS=61145088 10 | MIN_NINTENDO_SWITCH_SIZE_IN_MIB=2048 11 | MIN_ANDROID_USER_SIZE_IN_MIB=8192 12 | LSBLK_OPTIONS='-d -e 1,7 -p -o' 13 | LSKBL_COLUMNS='NAME,TRAN,VENDOR,MODEL,SIZE' 14 | 15 | androidImage=$1 16 | 17 | devices=[] 18 | devicePaths=[] 19 | deviceIndex=-1 20 | emummc=false 21 | 22 | shouldContinueCheck () { 23 | while : 24 | do 25 | read -p "${NL}Continue? ${RED}[y,n]${NC}:$CR" yesNo 26 | if [[ $yesNo =~ ^[yY]e?s?$ ]] 27 | then 28 | break 29 | fi 30 | if [[ $yesNo =~ ^[nN]o?$ ]] 31 | then 32 | exit 1 33 | fi 34 | echo "${NL}Enter ${RED}Y${NC}es or ${RED}N${NC}o" 35 | done 36 | } 37 | 38 | getDevPartitionPath () { 39 | echo -n $1 40 | [[ $1 == *mmcblk* ]] && echo -n p 41 | echo -n $2 42 | } 43 | 44 | # exit if android image is either not provided or cannot be found 45 | 46 | if [ ! -f "$androidImage" ] 47 | then 48 | echo 'Android image file not found. Please provide path to file as script param.' 49 | exit 1 50 | fi 51 | 52 | 53 | # dump android image partition table config to variable 54 | 55 | partitionTable=$(sfdisk -d $androidImage) 56 | 57 | 58 | # read offsets, sizes and names form android image partition scheme 59 | 60 | imagePartitionOffsets=[] 61 | imagePartitionSizes=[] 62 | imagePartitionNames=[] 63 | 64 | mapfile -t imagePartitionOffsets < <(echo "$partitionTable" | awk '{ if ($4) { print int($4); } }') 65 | mapfile -t imagePartitionSizes < <(echo "$partitionTable" | awk '{ if ($6) { print int($6); } }') 66 | mapfile -t imagePartitionNames < <(echo "$partitionTable" | awk '{ if ($9) { print substr($9, 6); } }') 67 | 68 | 69 | # fix partition alignment from android image (align to 1 MiB) 70 | 71 | partitionSizes=[] 72 | 73 | mapfile -t partitionSizes < <(echo "$partitionTable" | awk \ 74 | '{ 75 | if ($6) { 76 | $6=int(($6+2047)/2048)*2048; 77 | print $6 78 | } 79 | }') 80 | 81 | numOfImagePartitions=${#partitionSizes} 82 | 83 | 84 | # let user select device 85 | 86 | echo "${NL}Available devices:${NL}" 87 | 88 | mapfile -t -s 1 devices < <(lsblk $LSBLK_OPTIONS $LSKBL_COLUMNS) 89 | mapfile -t -s 1 devicePaths < <(lsblk $LSBLK_OPTIONS NAME) 90 | lsblk $LSBLK_OPTIONS $LSKBL_COLUMNS | awk -v r=$RED -v n=$NC \ 91 | 'NR == 1 { 92 | print(" "$0); 93 | } 94 | NR > 1 { 95 | print(r"["NR-2"] "n$0); 96 | }' 97 | 98 | while : 99 | do 100 | read -p "${NL}Choose device ${RED}[0-$((${#devicePaths[@]} - 1))]${NC}:$CR" deviceIndex 101 | if [[ $deviceIndex =~ ^[0-9]+$ ]] && (( $deviceIndex >= 0 )) && (( $deviceIndex < ${#devicePaths[@]} )) 102 | then 103 | break 104 | fi 105 | echo "${NL}Enter a valid number between ${RED}0${NC} and ${RED}$((${#devicePaths[@]} - 1))${NC}" 106 | done 107 | 108 | devicePath=${devicePaths[$deviceIndex]} 109 | device=$(lsblk $LSBLK_OPTIONS $LSKBL_COLUMNS $devicePath) 110 | partitionTable=$(echo "$partitionTable" | sed -e "s|${androidImage}|${devicePath}|g" -e "/first-lba/d" -e "/last-lba/d") 111 | 112 | echo "${NL}The following device will be used:${NL}${NL}${device}" 113 | 114 | shouldContinueCheck 115 | 116 | 117 | # read some basic information from selected device 118 | 119 | umount ${devicePath}?* 120 | 121 | totalSectors=$(blockdev --getsz $devicePath) 122 | lastUsableSector=$(($totalSectors - 34)) 123 | usableSectors=$(($lastUsableSector - 2048 + 1)) 124 | usableSizeInMiB=$(($usableSectors / 2048)) 125 | occupiedSectors=0 126 | 127 | for i in "${!partitionSizes[@]}" 128 | do 129 | if [ $i != 0 ] && [ $i != $((${numOfImagePartitions} - 1)) ] 130 | then 131 | ((occupiedSectors+=${partitionSizes[$i]})) 132 | fi 133 | done 134 | 135 | 136 | # let user choose to create partition for emummc 137 | 138 | while : 139 | do 140 | read -p "${NL}Create partition for emummc? ${RED}[y,n]${NC}:$CR" yesNo 141 | if [[ $yesNo =~ ^[yY]e?s?$ ]] 142 | then 143 | emummc=true 144 | partitionSizes+=($EMUMMC_SECTORS) 145 | ((occupiedSectors+=$EMUMMC_SECTORS)) 146 | partDevPath=$(getDevPartitionPath "${devicePath}" "$((${numOfImagePartitions} + 1))") 147 | emummcPartitionConfig="${partDevPath} : start= $(printf '%11s' '0'), size= $(printf '%11s' ${EMUMMC_SECTORS}), type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, name=emummc, attrs=RequiredPartition" 148 | partitionTable=$(echo "$partitionTable" | sed -e "$ a${emummcPartitionConfig}") 149 | break 150 | fi 151 | if [[ $yesNo =~ ^[nN]o?$ ]] 152 | then 153 | emummc=false 154 | break 155 | fi 156 | echo "${NL}Enter ${RED}Y${NC}es or ${RED}N${NC}o" 157 | done 158 | 159 | occupiedSizeInMiB=$(($occupiedSectors / 2048)) 160 | availableSectors=$(($usableSectors - $occupiedSectors)) 161 | availableSizeInMiB=$(($availableSectors / 2048)) 162 | 163 | 164 | # let user choose size for android user partition and use remaining space for nintendo switch partition 165 | 166 | nintendoSwitchSizeInMiB=$MIN_NINTENDO_SWITCH_SIZE_IN_MIB 167 | androidUserSizeInMiB=$(($availableSizeInMiB - $nintendoSwitchSizeInMiB)) 168 | maxAndroidUserSizeInMiB=$androidUserSizeInMiB 169 | 170 | echo $NL 171 | echo "Total usable device size:${NL}${RED}${usableSizeInMiB} MiB${NC}" 172 | echo "Reserved size for Android system (and emummc) partitions:${NL}${RED}${occupiedSizeInMiB} MiB${NC}" 173 | echo "Available size for Android user and Nintendo Switch partitions:${NL}${RED}${availableSizeInMiB} MiB${NC}" 174 | 175 | while : 176 | do 177 | read -p "${NL}Size in [MiB] for Android user partition ${RED}[$MIN_ANDROID_USER_SIZE_IN_MIB-$maxAndroidUserSizeInMiB]${NC}:$CR" androidUserSizeInMiB 178 | if [[ $androidUserSizeInMiB =~ ^[0-9]+$ ]] && (( $androidUserSizeInMiB >= $MIN_ANDROID_USER_SIZE_IN_MIB )) && (( $androidUserSizeInMiB <= $maxAndroidUserSizeInMiB )) 179 | then 180 | nintendoSwitchSizeInMiB=$(($availableSizeInMiB - $androidUserSizeInMiB)) 181 | break 182 | fi 183 | echo "${NL}Enter a valid size in [MiB] between ${RED}${MIN_ANDROID_USER_SIZE_IN_MIB}${NC} and ${RED}${maxAndroidUserSizeInMiB}${NC}" 184 | done 185 | 186 | echo "${NL}Partitions will have the following size:${NL}" 187 | echo "${RED}${nintendoSwitchSizeInMiB} MiB${NC} for Nintendo Switch partition" 188 | echo "${RED}${androidUserSizeInMiB} MiB${NC} for Android user partition" 189 | 190 | 191 | # warn user about data loss 192 | 193 | echo "${RED}${NL}All data on the following device will be lost:${NL}" 194 | echo "${device}${NC}" 195 | 196 | shouldContinueCheck 197 | 198 | 199 | # update partition table config 200 | 201 | partitionSizes[0]=$(($nintendoSwitchSizeInMiB * 2048)) 202 | partitionSizes[(($numOfImagePartitions - 1))]=$(($androidUserSizeInMiB * 2048)) 203 | 204 | sectorsString=$(echo "${partitionSizes[@]}" | tr ' ' ',') 205 | 206 | partitionTable=$(echo "$partitionTable" | \ 207 | awk -v pointer=2048 -v sectorsString=$sectorsString \ 208 | 'BEGIN { 209 | split(sectorsString, sectors, ","); 210 | } { 211 | if (!$6) { 212 | NR=0 213 | } 214 | if ($6) { 215 | $6=sprintf("%12s", sectors[NR]","); 216 | $4=sprintf("%12s", pointer","); 217 | pointer=(pointer + $6) 218 | }; 219 | print $0 220 | }') 221 | 222 | echo "${NL}${partitionTable}${NL}" 223 | 224 | 225 | # write new partition table according to partition table config 226 | 227 | echo "$partitionTable" | sfdisk -w always -W always $devicePath 228 | sfdisk --verify $devicePath 229 | 230 | 231 | # make empty fs 232 | 233 | echo "${NL}Making empty file systems:" 234 | mkfs.fat -F 32 $(getDevPartitionPath "${devicePath}" 1) 235 | mkfs.fat -F 32 $(getDevPartitionPath "${devicePath}" "$((${numOfImagePartitions} + 1))") 236 | mkfs.ext4 $(getDevPartitionPath "${devicePath}" "${numOfImagePartitions}") 237 | 238 | 239 | # dump data from android image to sd card 240 | 241 | for i in $(seq 2 $((${numOfImagePartitions} - 1))) 242 | do 243 | index=$(($i - 1)) 244 | 245 | # in case the image is not properly aligned (which is the case for some partitions), get the offset of the last aligned MiB 246 | lastAlignedMbOffset=$((${imagePartitionOffsets[$index]} + (${imagePartitionSizes[$index]} / 2048 * 2048) )) 247 | alignedSize=$(($lastAlignedMbOffset - ${imagePartitionOffsets[$index]})) 248 | alignmentErrorSize=$((${imagePartitionOffsets[$index]} + ${imagePartitionSizes[$index]} - $lastAlignedMbOffset)) 249 | 250 | sizeInMiB=$((${partitionSizes[$index]} * 512 / (1024 * 1024))) 251 | sizeInMB=$((${partitionSizes[$index]} * 512 / (1000 * 1000))) 252 | 253 | partDevPath=$(getDevPartitionPath "${devicePath}" "$i") 254 | 255 | echo "${NL}Dumping partition ${imagePartitionNames[$index]} to sd card ($sizeInMB MB, $sizeInMiB MiB):" 256 | dd bs=1M status=progress if=$androidImage of=${partDevPath} skip=$((${imagePartitionOffsets[$index]} * 512)) count=$(($alignedSize * 512)) iflag=skip_bytes,count_bytes oflag=direct && sync 257 | 258 | # if there is an alignment error, dump zeros to the last MiB of the sd card partition and afterwards dump the last 0-1 MiB from the image to the sd card partition 259 | # using conv=sync in the previous dd is not an option, because it could read into the next partition 260 | if [ $alignmentErrorSize != 0 ] 261 | then 262 | dd bs=512 if=/dev/zero of=${partDevPath} seek=$alignedSize count=2048 && sync 263 | dd bs=512 if=$androidImage of=${partDevPath} skip=$lastAlignedMbOffset seek=$alignedSize count=$alignmentErrorSize && sync 264 | fi 265 | done 266 | 267 | 268 | # make hybrid mbr 269 | 270 | no="N\n" 271 | makeHybridMbr="r\nh\n" 272 | mbrPartitions="1\n" 273 | configureMbrPartitions="EE\n${no}" 274 | writePartitionTable="o\nw\ny\n" 275 | 276 | if $emummc 277 | then 278 | mbrPartitions="1 $(($numOfImagePartitions + 1))\n" 279 | configureMbrPartitions+=$configureMbrPartitions 280 | fi 281 | 282 | printf "${makeHybridMbr}${mbrPartitions}${no}${configureMbrPartitions}${no}${writePartitionTable}" | sudo gdisk $devicePath 283 | 284 | 285 | # copy data from nintendo switch partition of android image to sd card 286 | 287 | loopDevice=$(losetup -o $((${imagePartitionOffsets[0]} * 512)) --sizelimit $((${imagePartitionSizes[0]} * 512)) -LPr --show -f $androidImage) 288 | 289 | imgMountPoint='img-switch' 290 | sdMountPoint='sd-switch' 291 | 292 | mkdir -p /mnt/$imgMountPoint 293 | mkdir -p /mnt/$sdMountPoint 294 | mount -r $loopDevice /mnt/$imgMountPoint 295 | mount $(getDevPartitionPath "${devicePath}" 1) /mnt/$sdMountPoint 296 | 297 | cp -r /mnt/$imgMountPoint/* /mnt/$sdMountPoint/ 298 | 299 | umount $loopDevice 300 | umount $(getDevPartitionPath "${devicePath}" 1) 301 | rmdir /mnt/$imgMountPoint 302 | rmdir /mnt/$sdMountPoint 303 | 304 | losetup -d $loopDevice 305 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gabri3lZ/SwitchrootAndroidUtils/8498919cc4cc6f9e26e7d693dab7abf2807c8b91/screenshot.png --------------------------------------------------------------------------------