From 1d00936a4a67fac9c9c132e68166699ed87a537f Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Sat, 7 Oct 2023 17:44:19 +0300 Subject: [PATCH] support downloading and unpacking GrapheneOS images --- src/frontend/source.ts | 18 ++++++++++++++---- src/images/device-image.ts | 18 +++++++++++++++--- src/images/download.ts | 12 ++++++++++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/frontend/source.ts b/src/frontend/source.ts index d8a9244..a7b69aa 100644 --- a/src/frontend/source.ts +++ b/src/frontend/source.ts @@ -388,7 +388,11 @@ async function unpackFactoryImage(factoryImagePath: string, image: DeviceImage, // There's a TOCTOU race (file is accessed after check), but it affects all other generated files too. // Fixing it for this particular case is not worth the complexity increase } else { - throw new Error(`SHA-256 mismatch for '${image.fileName}': expected ${image.sha256} got ${sha256}`) + if (image.skipSha256Check) { + console.warn(`skipping SHA-256 check for ${image.fileName}, SHA-256: ${sha256}`) + } else { + throw new Error(`SHA-256 mismatch for '${image.fileName}': expected ${image.sha256} got ${sha256}`) + } } let fd = await fs.open(factoryImagePath, 'r') @@ -400,9 +404,15 @@ async function unpackFactoryImage(factoryImagePath: string, image: DeviceImage, let entry: yauzl.Entry = entryP let entryName = entry.filename - let isInnerZip = - entryName.includes(`-${image.buildId.toLowerCase()}/image-${image.deviceConfig.device.name}`) && - entryName.endsWith(`-${image.buildId.toLowerCase()}.zip`) + let isInnerZip = false + + let deviceName = image.deviceConfig.device.name + if (image.isGrapheneOS) { + isInnerZip = entryName.includes(`/image-${deviceName}-`) && entryName.endsWith('.zip') + } else { + isInnerZip = entryName.includes(`-${image.buildId.toLowerCase()}/image-${deviceName}`) && + entryName.endsWith(`-${image.buildId.toLowerCase()}.zip`) + } if (!isInnerZip) { continue diff --git a/src/images/device-image.ts b/src/images/device-image.ts index 275b95f..17fff4a 100644 --- a/src/images/device-image.ts +++ b/src/images/device-image.ts @@ -5,6 +5,8 @@ import { DeviceConfig, getDeviceBuildId } from '../config/device' import { IMAGE_DOWNLOAD_DIR } from '../config/paths' import { BuildIndex, DEFAULT_BASE_DOWNLOAD_URL, ImageType } from './build-index' +const GRAPHENEOS_PSEUDO_BUILD_ID_PREFIX = 'gos-' + export class DeviceImage { constructor( readonly deviceConfig: DeviceConfig, @@ -13,8 +15,9 @@ export class DeviceImage { readonly fileName: string, readonly sha256: string, readonly url: string, - ) { - } + readonly skipSha256Check: boolean, + readonly isGrapheneOS: boolean, + ) {} getPath() { return path.join(IMAGE_DOWNLOAD_DIR, this.fileName) @@ -32,6 +35,15 @@ export class DeviceImage { let deviceBuildId = getDeviceBuildId(deviceConfig, buildId) let buildProps = index.get(deviceBuildId) if (buildProps === undefined) { + if (buildId.startsWith(GRAPHENEOS_PSEUDO_BUILD_ID_PREFIX)) { + let fileName = deviceConfig.device.name + '-factory-' + + buildId.substring(GRAPHENEOS_PSEUDO_BUILD_ID_PREFIX.length) + '.zip' + + let url = 'https://releases.grapheneos.org/' + fileName + + return new DeviceImage(deviceConfig, type, buildId, fileName, 'no sha256', url, true, true) + } + throw new Error(`no images for '${deviceBuildId}'`) } @@ -54,7 +66,7 @@ export class DeviceImage { fileName = ending url = DEFAULT_BASE_DOWNLOAD_URL + fileName } - return new DeviceImage(deviceConfig, type, buildId, fileName, sha256, url) + return new DeviceImage(deviceConfig, type, buildId, fileName, sha256, url, false, false) } static async getMissing(arr: DeviceImage[]) { diff --git a/src/images/download.ts b/src/images/download.ts index 2ae7fb8..587e247 100644 --- a/src/images/download.ts +++ b/src/images/download.ts @@ -70,13 +70,21 @@ async function downloadImage(image: DeviceImage, outDir: string) { let sha256Digest: string = sha256.digest('hex') console.log('SHA-256: ' + sha256Digest) - assert(sha256Digest === image.sha256, 'SHA256 mismatch, expected ' + image.sha256) + if (image.skipSha256Check) { + console.warn('skipping SHA-256 check for ' + completeOutFile) + } else { + assert(sha256Digest === image.sha256, 'SHA256 mismatch, expected ' + image.sha256) + } await fs.rename(tmpOutFile, completeOutFile) } function logTermsAndConditionsNotice(images: DeviceImage[]) { - if (images.filter(i => i.type === ImageType.Factory || i.type === ImageType.Ota).length == 0) { + if ( + images.filter(i => { + return !i.isGrapheneOS && (i.type === ImageType.Factory || i.type === ImageType.Ota) + }).length == 0 + ) { // vendor images show T&C notice themselves as part of unpacking return }