From 49d3c0e57dab2364e8a902d617b0f543edd59d6f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 5 Oct 2024 15:13:40 -0500 Subject: [PATCH 1/7] WIP --- stores/firmwareStore.ts | 22 +- types/resources.ts | 1203 +++++++++++++++++++++++++++------------ 2 files changed, 852 insertions(+), 373 deletions(-) diff --git a/stores/firmwareStore.ts b/stores/firmwareStore.ts index f920d54..1caebdc 100644 --- a/stores/firmwareStore.ts +++ b/stores/firmwareStore.ts @@ -8,6 +8,7 @@ import { saveAs } from 'file-saver'; import { mande } from 'mande'; import { defineStore } from 'pinia'; import type { Terminal } from 'xterm'; +import { timezones } from '~/types/resources'; import { track } from '@vercel/analytics'; import { useSessionStorage } from '@vueuse/core'; @@ -178,7 +179,18 @@ export const useFirmwareStore = defineStore('firmware', { }; const transport = new Transport(this.port, true); const espLoader = await this.connectEsp32(transport, terminal); - const appContent = await this.fetchBinaryContent(fileName); + let appContent = await this.fetchBinaryContent(fileName, true); + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + // get posix timezone string based on browser locale + const posixTz = timezones[tz as keyof typeof timezones] || 'CST6CDT,M3.2.0,M11.1.0'; + appContent.replace('tzplaceholder ', posixTz); + const sha256 = new Uint8Array(32); + const sha256sum = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(appContent)); + new Uint8Array(sha256sum).forEach((byte, index) => { + sha256[index] = byte; + }); + appContent += convertToBinaryString(sha256); + const otaContent = await this.fetchBinaryContent(otaFileName); const littleFsContent = await this.fetchBinaryContent(littleFsFileName); this.isFlashing = true; @@ -201,12 +213,16 @@ export const useFirmwareStore = defineStore('firmware', { }; await this.startWrite(terminal, espLoader, transport, flashOptions); }, - async fetchBinaryContent(fileName: string): Promise { + async fetchBinaryContent(fileName: string, truncateLast32 = false): Promise { if (this.selectedFirmware?.zip_url) { const baseUrl = getCorsFriendyReleaseUrl(this.selectedFirmware.zip_url); const response = await fetch(`${baseUrl}/${fileName}`); const blob = await response.blob(); - const data = await blob.arrayBuffer(); + let data = await blob.arrayBuffer(); + debugger + if (truncateLast32) + data = new Uint8Array(data).slice(0, -32); + return convertToBinaryString(new Uint8Array(data)); } if (this.selectedFile && this.isZipFile) { diff --git a/types/resources.ts b/types/resources.ts index baf5418..28ca90c 100644 --- a/types/resources.ts +++ b/types/resources.ts @@ -1,374 +1,838 @@ import type { FirmwareResource } from './api'; export const OfflineHardwareList = [ - { - "hwModel": 1, - "hwModelSlug": "TLORA_V2", - "platformioTarget": "tlora-v2", - "architecture": "esp32", - "activelySupported": false, - "displayName": "T-LoRa V2" - }, - { - "hwModel": 2, - "hwModelSlug": "TLORA_V1", - "platformioTarget": "tlora-v1", - "architecture": "esp32", - "activelySupported": false, - "displayName": "T-LoRa V1" - }, - { - "hwModel": 3, - "hwModelSlug": "TLORA_V2_1_1P6", - "platformioTarget": "tlora-v2-1-1_6", - "architecture": "esp32", - "activelySupported": true, - "displayName": "T-LoRa V2.1-1.6" - }, - { - "hwModel": 4, - "hwModelSlug": "TBEAM", - "platformioTarget": "tbeam", - "architecture": "esp32", - "activelySupported": true, - "displayName": "T-Beam" - }, - { - "hwModel": 5, - "hwModelSlug": "HELTEC_V2_0", - "platformioTarget": "heltec-v2_0", - "architecture": "esp32", - "activelySupported": false, - "displayName": "Heltec V2.0" - }, - { - "hwModel": 6, - "hwModelSlug": "TBEAM_V0P7", - "platformioTarget": "tbeam0_7", - "architecture": "esp32", - "activelySupported": false, - "displayName": "T-Beam V0.7" - }, - { - "hwModel": 7, - "hwModelSlug": "T_ECHO", - "platformioTarget": "t-echo", - "architecture": "nrf52840", - "activelySupported": true, - "displayName": "T-Echo" - }, - { - "hwModel": 8, - "hwModelSlug": "TLORA_V1_1P3", - "platformioTarget": "tlora-v1_3", - "architecture": "esp32", - "activelySupported": false, - "displayName": "T-LoRa V1.1-1.3" - }, - { - "hwModel": 9, - "hwModelSlug": "RAK4631", - "platformioTarget": "rak4631", - "architecture": "nrf52840", - "activelySupported": true, - "displayName": "RAK4631" - }, - { - "hwModel": 10, - "hwModelSlug": "HELTEC_V2_1", - "platformioTarget": "heltec-v2_1", - "architecture": "esp32", - "activelySupported": false, - "displayName": "Heltec V2.1" - }, - { - "hwModel": 11, - "hwModelSlug": "HELTEC_V1", - "platformioTarget": "heltec-v1", - "architecture": "esp32", - "activelySupported": false, - "displayName": "Heltec V1" - }, - { - "hwModel": 12, - "hwModelSlug": "TBEAM_S3_CORE", - "platformioTarget": "tbeam-s3-core", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "T-Beam S3 Core" - }, - { - "hwModel": 13, - "hwModelSlug": "RAK11200", - "platformioTarget": "rak11200", - "architecture": "esp32", - "activelySupported": false, - "displayName": "RAK11200" - }, - { - "hwModel": 14, - "hwModelSlug": "NANO_G1", - "platformioTarget": "nano-g1", - "architecture": "esp32", - "activelySupported": true, - "displayName": "Nano G1" - }, - { - "hwModel": 15, - "hwModelSlug": "TLORA_V2_1_1P8", - "platformioTarget": "tlora-v2-1-1_8", - "architecture": "esp32", - "activelySupported": true, - "displayName": "T-LoRa V2.1-1.8" - }, - { - "hwModel": 16, - "hwModelSlug": "TLORA_T3_S3", - "platformioTarget": "tlora-t3s3-v1", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "T-LoRa T3-S3" - }, - { - "hwModel": 16, - "hwModelSlug": "TLORA_T3_S3", - "platformioTarget": "tlora-t3s3-epaper", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "T-LoRa T3-S3 E-Paper" - }, - { - "hwModel": 17, - "hwModelSlug": "NANO_G1_EXPLORER", - "platformioTarget": "nano-g1-explorer", - "architecture": "esp32", - "activelySupported": true, - "displayName": "Nano G1 Explorer" - }, - { - "hwModel": 18, - "hwModelSlug": "NANO_G2_ULTRA", - "platformioTarget": "nano-g2-ultra", - "architecture": "nrf52840", - "activelySupported": true, - "displayName": "Nano G2 Ultra" - }, - { - "hwModel": 25, - "hwModelSlug": "STATION_G1", - "platformioTarget": "station-g1", - "architecture": "esp32", - "activelySupported": true, - "displayName": "Station G1" - }, - { - "hwModel": 26, - "hwModelSlug": "RAK11310", - "platformioTarget": "rak11310", - "architecture": "rp2040", - "activelySupported": true, - "displayName": "RAK11310" - }, - { - "hwModel": 29, - "hwModelSlug": "CANARYONE", - "platformioTarget": "canaryone", - "architecture": "nrf52840", - "activelySupported": true, - "displayName": "CanaryOne" - }, - { - "hwModel": 30, - "hwModelSlug": "RP2040_LORA", - "platformioTarget": "rp2040-lora", - "architecture": "rp2040", - "activelySupported": true, - "displayName": "RP2040 LoRa" - }, - { - "hwModel": 31, - "hwModelSlug": "STATION_G2", - "platformioTarget": "station-g2", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Station G2", - }, - { - "hwModel": 39, - "hwModelSlug": "DIY_V1", - "platformioTarget": "meshtastic-diy-v1", - "architecture": "esp32", - "activelySupported": true, - "displayName": "DIY V1" - }, - { - "hwModel": 39, - "hwModelSlug": "HYDRA", - "platformioTarget": "hydra", - "architecture": "esp32", - "activelySupported": true, - "displayName": "Hydra" - }, - { - "hwModel": 41, - "hwModelSlug": "DR_DEV", - "platformioTarget": "meshtastic-dr-dev", - "architecture": "esp32", - "activelySupported": true, - "displayName": "DR-DEV" - }, - { - "hwModel": 42, - "hwModelSlug": "M5STACK", - "platformioTarget": "m5stack-core", - "architecture": "esp32", - "activelySupported": true, - "displayName": "M5 Stack" - }, - { - "hwModel": 43, - "hwModelSlug": "HELTEC_V3", - "platformioTarget": "heltec-v3", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Heltec V3" - }, - { - "hwModel": 44, - "hwModelSlug": "HELTEC_WSL_V3", - "platformioTarget": "heltec-wsl-v3", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Heltec Wireless Stick Lite V3" - }, - { - "hwModel": 47, - "hwModelSlug": "RPI_PICO", - "platformioTarget": "pico", - "architecture": "rp2040", - "activelySupported": true, - "displayName": "Raspberry Pi Pico" - }, - { - "hwModel": 47, - "hwModelSlug": "RPI_PICO", - "platformioTarget": "picow", - "architecture": "rp2040", - "activelySupported": true, - "displayName": "Raspberry Pi Pico W" - }, - { - "hwModel": 48, - "hwModelSlug": "HELTEC_WIRELESS_TRACKER", - "platformioTarget": "heltec-wireless-tracker", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Heltec Wireless Tracker V1.1" - }, - { - "hwModel": 58, - "hwModelSlug": "HELTEC_WIRELESS_TRACKER_V1_0", - "platformioTarget": "heltec-wireless-tracker-V1-0", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Heltec Wireless Tracker V1.0", - }, - { - "hwModel": 49, - "hwModelSlug": "HELTEC_WIRELESS_PAPER", - "platformioTarget": "heltec-wireless-paper", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Heltec Wireless Paper" - }, - { - "hwModel": 50, - "hwModelSlug": "T_DECK", - "platformioTarget": "t-deck", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "T-Deck" - }, - { - "hwModel": 51, - "hwModelSlug": "T_WATCH_S3", - "platformioTarget": "t-watch-s3", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "T-Watch S3" - }, - { - "hwModel": 52, - "hwModelSlug": "PICOMPUTER_S3", - "platformioTarget": "picomputer-s3", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Pi Computer S3" - }, - { - "hwModel": 53, - "hwModelSlug": "HELTEC_HT62", - "platformioTarget": "heltec-ht62-esp32c3-sx1262", - "architecture": "esp32-c3", - "activelySupported": true, - "displayName": "Heltec HT62" - }, - { - "hwModel": 59, - "hwModelSlug": "UNPHONE", - "platformioTarget": "unphone", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "unPhone", - }, - { - "hwModel": 61, - "hwModelSlug": "CDEBYTE_EORA_S3", - "platformioTarget": "CDEBYTE_EoRa-S3", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "EBYTE EoRa-S3", - }, - { - "hwModel": 64, - "hwModelSlug": "RADIOMASTER_900_BANDIT_NANO", - "platformioTarget": "radiomaster_900_bandit_nano", - "architecture": "esp32", - "activelySupported": true, - "displayName": "RadioMaster 900 Bandit Nano", - }, - { - "hwModel": 21, - "hwModelSlug": "WIO_WM1110", - "platformioTarget": "wio-tracker-wm1110", - "architecture": "nrf52840", - "activelySupported": true, - "displayName": "Seeed Wio WM1110 Tracker", - }, - { - "hwModel": 70, - "hwModelSlug": "SENSECAP_INDICATOR", - "platformioTarget": "seeed-sensecap-indicator", - "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "SenseCAP Indicator", - }, - { - "hwModel": 71, - "hwModelSlug": "TRACKER_T1000_E", - "platformioTarget": "tracker-t1000-e", - "architecture": "nrf52840", - "activelySupported": true, - "displayName": "Seeed Card Tracker T1000-E", - } -] + { + hwModel: 1, + hwModelSlug: "TLORA_V2", + platformioTarget: "tlora-v2", + architecture: "esp32", + activelySupported: false, + displayName: "T-LoRa V2", + }, + { + hwModel: 2, + hwModelSlug: "TLORA_V1", + platformioTarget: "tlora-v1", + architecture: "esp32", + activelySupported: false, + displayName: "T-LoRa V1", + }, + { + hwModel: 3, + hwModelSlug: "TLORA_V2_1_1P6", + platformioTarget: "tlora-v2-1-1_6", + architecture: "esp32", + activelySupported: true, + displayName: "T-LoRa V2.1-1.6", + }, + { + hwModel: 4, + hwModelSlug: "TBEAM", + platformioTarget: "tbeam", + architecture: "esp32", + activelySupported: true, + displayName: "T-Beam", + }, + { + hwModel: 5, + hwModelSlug: "HELTEC_V2_0", + platformioTarget: "heltec-v2_0", + architecture: "esp32", + activelySupported: false, + displayName: "Heltec V2.0", + }, + { + hwModel: 6, + hwModelSlug: "TBEAM_V0P7", + platformioTarget: "tbeam0_7", + architecture: "esp32", + activelySupported: false, + displayName: "T-Beam V0.7", + }, + { + hwModel: 7, + hwModelSlug: "T_ECHO", + platformioTarget: "t-echo", + architecture: "nrf52840", + activelySupported: true, + displayName: "T-Echo", + }, + { + hwModel: 8, + hwModelSlug: "TLORA_V1_1P3", + platformioTarget: "tlora-v1_3", + architecture: "esp32", + activelySupported: false, + displayName: "T-LoRa V1.1-1.3", + }, + { + hwModel: 9, + hwModelSlug: "RAK4631", + platformioTarget: "rak4631", + architecture: "nrf52840", + activelySupported: true, + displayName: "RAK4631", + }, + { + hwModel: 10, + hwModelSlug: "HELTEC_V2_1", + platformioTarget: "heltec-v2_1", + architecture: "esp32", + activelySupported: false, + displayName: "Heltec V2.1", + }, + { + hwModel: 11, + hwModelSlug: "HELTEC_V1", + platformioTarget: "heltec-v1", + architecture: "esp32", + activelySupported: false, + displayName: "Heltec V1", + }, + { + hwModel: 12, + hwModelSlug: "TBEAM_S3_CORE", + platformioTarget: "tbeam-s3-core", + architecture: "esp32-s3", + activelySupported: true, + displayName: "T-Beam S3 Core", + }, + { + hwModel: 13, + hwModelSlug: "RAK11200", + platformioTarget: "rak11200", + architecture: "esp32", + activelySupported: false, + displayName: "RAK11200", + }, + { + hwModel: 14, + hwModelSlug: "NANO_G1", + platformioTarget: "nano-g1", + architecture: "esp32", + activelySupported: true, + displayName: "Nano G1", + }, + { + hwModel: 15, + hwModelSlug: "TLORA_V2_1_1P8", + platformioTarget: "tlora-v2-1-1_8", + architecture: "esp32", + activelySupported: true, + displayName: "T-LoRa V2.1-1.8", + }, + { + hwModel: 16, + hwModelSlug: "TLORA_T3_S3", + platformioTarget: "tlora-t3s3-v1", + architecture: "esp32-s3", + activelySupported: true, + displayName: "T-LoRa T3-S3", + }, + { + hwModel: 16, + hwModelSlug: "TLORA_T3_S3", + platformioTarget: "tlora-t3s3-epaper", + architecture: "esp32-s3", + activelySupported: true, + displayName: "T-LoRa T3-S3 E-Paper", + }, + { + hwModel: 17, + hwModelSlug: "NANO_G1_EXPLORER", + platformioTarget: "nano-g1-explorer", + architecture: "esp32", + activelySupported: true, + displayName: "Nano G1 Explorer", + }, + { + hwModel: 18, + hwModelSlug: "NANO_G2_ULTRA", + platformioTarget: "nano-g2-ultra", + architecture: "nrf52840", + activelySupported: true, + displayName: "Nano G2 Ultra", + }, + { + hwModel: 25, + hwModelSlug: "STATION_G1", + platformioTarget: "station-g1", + architecture: "esp32", + activelySupported: true, + displayName: "Station G1", + }, + { + hwModel: 26, + hwModelSlug: "RAK11310", + platformioTarget: "rak11310", + architecture: "rp2040", + activelySupported: true, + displayName: "RAK11310", + }, + { + hwModel: 29, + hwModelSlug: "CANARYONE", + platformioTarget: "canaryone", + architecture: "nrf52840", + activelySupported: true, + displayName: "CanaryOne", + }, + { + hwModel: 30, + hwModelSlug: "RP2040_LORA", + platformioTarget: "rp2040-lora", + architecture: "rp2040", + activelySupported: true, + displayName: "RP2040 LoRa", + }, + { + hwModel: 31, + hwModelSlug: "STATION_G2", + platformioTarget: "station-g2", + architecture: "esp32-s3", + activelySupported: true, + displayName: "Station G2", + }, + { + hwModel: 39, + hwModelSlug: "DIY_V1", + platformioTarget: "meshtastic-diy-v1", + architecture: "esp32", + activelySupported: true, + displayName: "DIY V1", + }, + { + hwModel: 39, + hwModelSlug: "HYDRA", + platformioTarget: "hydra", + architecture: "esp32", + activelySupported: true, + displayName: "Hydra", + }, + { + hwModel: 41, + hwModelSlug: "DR_DEV", + platformioTarget: "meshtastic-dr-dev", + architecture: "esp32", + activelySupported: true, + displayName: "DR-DEV", + }, + { + hwModel: 42, + hwModelSlug: "M5STACK", + platformioTarget: "m5stack-core", + architecture: "esp32", + activelySupported: true, + displayName: "M5 Stack", + }, + { + hwModel: 43, + hwModelSlug: "HELTEC_V3", + platformioTarget: "heltec-v3", + architecture: "esp32-s3", + activelySupported: true, + displayName: "Heltec V3", + }, + { + hwModel: 44, + hwModelSlug: "HELTEC_WSL_V3", + platformioTarget: "heltec-wsl-v3", + architecture: "esp32-s3", + activelySupported: true, + displayName: "Heltec Wireless Stick Lite V3", + }, + { + hwModel: 47, + hwModelSlug: "RPI_PICO", + platformioTarget: "pico", + architecture: "rp2040", + activelySupported: true, + displayName: "Raspberry Pi Pico", + }, + { + hwModel: 47, + hwModelSlug: "RPI_PICO", + platformioTarget: "picow", + architecture: "rp2040", + activelySupported: true, + displayName: "Raspberry Pi Pico W", + }, + { + hwModel: 48, + hwModelSlug: "HELTEC_WIRELESS_TRACKER", + platformioTarget: "heltec-wireless-tracker", + architecture: "esp32-s3", + activelySupported: true, + displayName: "Heltec Wireless Tracker V1.1", + }, + { + hwModel: 58, + hwModelSlug: "HELTEC_WIRELESS_TRACKER_V1_0", + platformioTarget: "heltec-wireless-tracker-V1-0", + architecture: "esp32-s3", + activelySupported: true, + displayName: "Heltec Wireless Tracker V1.0", + }, + { + hwModel: 49, + hwModelSlug: "HELTEC_WIRELESS_PAPER", + platformioTarget: "heltec-wireless-paper", + architecture: "esp32-s3", + activelySupported: true, + displayName: "Heltec Wireless Paper", + }, + { + hwModel: 50, + hwModelSlug: "T_DECK", + platformioTarget: "t-deck", + architecture: "esp32-s3", + activelySupported: true, + displayName: "T-Deck", + }, + { + hwModel: 51, + hwModelSlug: "T_WATCH_S3", + platformioTarget: "t-watch-s3", + architecture: "esp32-s3", + activelySupported: true, + displayName: "T-Watch S3", + }, + { + hwModel: 52, + hwModelSlug: "PICOMPUTER_S3", + platformioTarget: "picomputer-s3", + architecture: "esp32-s3", + activelySupported: true, + displayName: "Pi Computer S3", + }, + { + hwModel: 53, + hwModelSlug: "HELTEC_HT62", + platformioTarget: "heltec-ht62-esp32c3-sx1262", + architecture: "esp32-c3", + activelySupported: true, + displayName: "Heltec HT62", + }, + { + hwModel: 59, + hwModelSlug: "UNPHONE", + platformioTarget: "unphone", + architecture: "esp32-s3", + activelySupported: true, + displayName: "unPhone", + }, + { + hwModel: 61, + hwModelSlug: "CDEBYTE_EORA_S3", + platformioTarget: "CDEBYTE_EoRa-S3", + architecture: "esp32-s3", + activelySupported: true, + displayName: "EBYTE EoRa-S3", + }, + { + hwModel: 64, + hwModelSlug: "RADIOMASTER_900_BANDIT_NANO", + platformioTarget: "radiomaster_900_bandit_nano", + architecture: "esp32", + activelySupported: true, + displayName: "RadioMaster 900 Bandit Nano", + }, + { + hwModel: 21, + hwModelSlug: "WIO_WM1110", + platformioTarget: "wio-tracker-wm1110", + architecture: "nrf52840", + activelySupported: true, + displayName: "Seeed Wio WM1110 Tracker", + }, + { + hwModel: 70, + hwModelSlug: "SENSECAP_INDICATOR", + platformioTarget: "seeed-sensecap-indicator", + architecture: "esp32-s3", + activelySupported: true, + displayName: "SenseCAP Indicator", + }, + { + hwModel: 71, + hwModelSlug: "TRACKER_T1000_E", + platformioTarget: "tracker-t1000-e", + architecture: "nrf52840", + activelySupported: true, + displayName: "Seeed Card Tracker T1000-E", + }, +]; -export const currentPrerelease = -{ - id: 'v2.5.4.8d288d5', - title: 'Meshtastic Firmware 2.5.4.8d288d5 Alpha', - zip_url: 'https://github.com/meshtastic/firmware/releases/download/v2.5.4.8d288d5/firmware-2.5.4.8d288d5.zip', - release_notes: `## Enhancements +export const timezones = { + "Africa/Abidjan": "GMT0", + "Africa/Accra": "GMT0", + "Africa/Addis_Ababa": "EAT-3", + "Africa/Algiers": "CET-1", + "Africa/Asmara": "EAT-3", + "Africa/Bamako": "GMT0", + "Africa/Bangui": "WAT-1", + "Africa/Banjul": "GMT0", + "Africa/Bissau": "GMT0", + "Africa/Blantyre": "CAT-2", + "Africa/Brazzaville": "WAT-1", + "Africa/Bujumbura": "CAT-2", + "Africa/Cairo": "EET-2EEST,M4.5.5/0,M10.5.4/24", + "Africa/Casablanca": "<+01>-1", + "Africa/Ceuta": "CET-1CEST,M3.5.0,M10.5.0/3", + "Africa/Conakry": "GMT0", + "Africa/Dakar": "GMT0", + "Africa/Dar_es_Salaam": "EAT-3", + "Africa/Djibouti": "EAT-3", + "Africa/Douala": "WAT-1", + "Africa/El_Aaiun": "<+01>-1", + "Africa/Freetown": "GMT0", + "Africa/Gaborone": "CAT-2", + "Africa/Harare": "CAT-2", + "Africa/Johannesburg": "SAST-2", + "Africa/Juba": "CAT-2", + "Africa/Kampala": "EAT-3", + "Africa/Khartoum": "CAT-2", + "Africa/Kigali": "CAT-2", + "Africa/Kinshasa": "WAT-1", + "Africa/Lagos": "WAT-1", + "Africa/Libreville": "WAT-1", + "Africa/Lome": "GMT0", + "Africa/Luanda": "WAT-1", + "Africa/Lubumbashi": "CAT-2", + "Africa/Lusaka": "CAT-2", + "Africa/Malabo": "WAT-1", + "Africa/Maputo": "CAT-2", + "Africa/Maseru": "SAST-2", + "Africa/Mbabane": "SAST-2", + "Africa/Mogadishu": "EAT-3", + "Africa/Monrovia": "GMT0", + "Africa/Nairobi": "EAT-3", + "Africa/Ndjamena": "WAT-1", + "Africa/Niamey": "WAT-1", + "Africa/Nouakchott": "GMT0", + "Africa/Ouagadougou": "GMT0", + "Africa/Porto-Novo": "WAT-1", + "Africa/Sao_Tome": "GMT0", + "Africa/Tripoli": "EET-2", + "Africa/Tunis": "CET-1", + "Africa/Windhoek": "CAT-2", + "America/Adak": "HST10HDT,M3.2.0,M11.1.0", + "America/Anchorage": "AKST9AKDT,M3.2.0,M11.1.0", + "America/Anguilla": "AST4", + "America/Antigua": "AST4", + "America/Araguaina": "<-03>3", + "America/Argentina/Buenos_Aires": "<-03>3", + "America/Argentina/Catamarca": "<-03>3", + "America/Argentina/Cordoba": "<-03>3", + "America/Argentina/Jujuy": "<-03>3", + "America/Argentina/La_Rioja": "<-03>3", + "America/Argentina/Mendoza": "<-03>3", + "America/Argentina/Rio_Gallegos": "<-03>3", + "America/Argentina/Salta": "<-03>3", + "America/Argentina/San_Juan": "<-03>3", + "America/Argentina/San_Luis": "<-03>3", + "America/Argentina/Tucuman": "<-03>3", + "America/Argentina/Ushuaia": "<-03>3", + "America/Aruba": "AST4", + "America/Asuncion": "<-04>4<-03>,M10.1.0/0,M3.4.0/0", + "America/Atikokan": "EST5", + "America/Bahia": "<-03>3", + "America/Bahia_Banderas": "CST6", + "America/Barbados": "AST4", + "America/Belem": "<-03>3", + "America/Belize": "CST6", + "America/Blanc-Sablon": "AST4", + "America/Boa_Vista": "<-04>4", + "America/Bogota": "<-05>5", + "America/Boise": "MST7MDT,M3.2.0,M11.1.0", + "America/Cambridge_Bay": "MST7MDT,M3.2.0,M11.1.0", + "America/Campo_Grande": "<-04>4", + "America/Cancun": "EST5", + "America/Caracas": "<-04>4", + "America/Cayenne": "<-03>3", + "America/Cayman": "EST5", + "America/Chicago": "CST6CDT,M3.2.0,M11.1.0", + "America/Chihuahua": "CST6", + "America/Costa_Rica": "CST6", + "America/Creston": "MST7", + "America/Cuiaba": "<-04>4", + "America/Curacao": "AST4", + "America/Danmarkshavn": "GMT0", + "America/Dawson": "MST7", + "America/Dawson_Creek": "MST7", + "America/Denver": "MST7MDT,M3.2.0,M11.1.0", + "America/Detroit": "EST5EDT,M3.2.0,M11.1.0", + "America/Dominica": "AST4", + "America/Edmonton": "MST7MDT,M3.2.0,M11.1.0", + "America/Eirunepe": "<-05>5", + "America/El_Salvador": "CST6", + "America/Fort_Nelson": "MST7", + "America/Fortaleza": "<-03>3", + "America/Glace_Bay": "AST4ADT,M3.2.0,M11.1.0", + "America/Godthab": "<-02>2<-01>,M3.5.0/-1,M10.5.0/0", + "America/Goose_Bay": "AST4ADT,M3.2.0,M11.1.0", + "America/Grand_Turk": "EST5EDT,M3.2.0,M11.1.0", + "America/Grenada": "AST4", + "America/Guadeloupe": "AST4", + "America/Guatemala": "CST6", + "America/Guayaquil": "<-05>5", + "America/Guyana": "<-04>4", + "America/Halifax": "AST4ADT,M3.2.0,M11.1.0", + "America/Havana": "CST5CDT,M3.2.0/0,M11.1.0/1", + "America/Hermosillo": "MST7", + "America/Indiana/Indianapolis": "EST5EDT,M3.2.0,M11.1.0", + "America/Indiana/Knox": "CST6CDT,M3.2.0,M11.1.0", + "America/Indiana/Marengo": "EST5EDT,M3.2.0,M11.1.0", + "America/Indiana/Petersburg": "EST5EDT,M3.2.0,M11.1.0", + "America/Indiana/Tell_City": "CST6CDT,M3.2.0,M11.1.0", + "America/Indiana/Vevay": "EST5EDT,M3.2.0,M11.1.0", + "America/Indiana/Vincennes": "EST5EDT,M3.2.0,M11.1.0", + "America/Indiana/Winamac": "EST5EDT,M3.2.0,M11.1.0", + "America/Inuvik": "MST7MDT,M3.2.0,M11.1.0", + "America/Iqaluit": "EST5EDT,M3.2.0,M11.1.0", + "America/Jamaica": "EST5", + "America/Juneau": "AKST9AKDT,M3.2.0,M11.1.0", + "America/Kentucky/Louisville": "EST5EDT,M3.2.0,M11.1.0", + "America/Kentucky/Monticello": "EST5EDT,M3.2.0,M11.1.0", + "America/Kralendijk": "AST4", + "America/La_Paz": "<-04>4", + "America/Lima": "<-05>5", + "America/Los_Angeles": "PST8PDT,M3.2.0,M11.1.0", + "America/Lower_Princes": "AST4", + "America/Maceio": "<-03>3", + "America/Managua": "CST6", + "America/Manaus": "<-04>4", + "America/Marigot": "AST4", + "America/Martinique": "AST4", + "America/Matamoros": "CST6CDT,M3.2.0,M11.1.0", + "America/Mazatlan": "MST7", + "America/Menominee": "CST6CDT,M3.2.0,M11.1.0", + "America/Merida": "CST6", + "America/Metlakatla": "AKST9AKDT,M3.2.0,M11.1.0", + "America/Mexico_City": "CST6", + "America/Miquelon": "<-03>3<-02>,M3.2.0,M11.1.0", + "America/Moncton": "AST4ADT,M3.2.0,M11.1.0", + "America/Monterrey": "CST6", + "America/Montevideo": "<-03>3", + "America/Montreal": "EST5EDT,M3.2.0,M11.1.0", + "America/Montserrat": "AST4", + "America/Nassau": "EST5EDT,M3.2.0,M11.1.0", + "America/New_York": "EST5EDT,M3.2.0,M11.1.0", + "America/Nipigon": "EST5EDT,M3.2.0,M11.1.0", + "America/Nome": "AKST9AKDT,M3.2.0,M11.1.0", + "America/Noronha": "<-02>2", + "America/North_Dakota/Beulah": "CST6CDT,M3.2.0,M11.1.0", + "America/North_Dakota/Center": "CST6CDT,M3.2.0,M11.1.0", + "America/North_Dakota/New_Salem": "CST6CDT,M3.2.0,M11.1.0", + "America/Nuuk": "<-02>2<-01>,M3.5.0/-1,M10.5.0/0", + "America/Ojinaga": "CST6CDT,M3.2.0,M11.1.0", + "America/Panama": "EST5", + "America/Pangnirtung": "EST5EDT,M3.2.0,M11.1.0", + "America/Paramaribo": "<-03>3", + "America/Phoenix": "MST7", + "America/Port-au-Prince": "EST5EDT,M3.2.0,M11.1.0", + "America/Port_of_Spain": "AST4", + "America/Porto_Velho": "<-04>4", + "America/Puerto_Rico": "AST4", + "America/Punta_Arenas": "<-03>3", + "America/Rainy_River": "CST6CDT,M3.2.0,M11.1.0", + "America/Rankin_Inlet": "CST6CDT,M3.2.0,M11.1.0", + "America/Recife": "<-03>3", + "America/Regina": "CST6", + "America/Resolute": "CST6CDT,M3.2.0,M11.1.0", + "America/Rio_Branco": "<-05>5", + "America/Santarem": "<-03>3", + "America/Santiago": "<-04>4<-03>,M9.1.6/24,M4.1.6/24", + "America/Santo_Domingo": "AST4", + "America/Sao_Paulo": "<-03>3", + "America/Scoresbysund": "<-02>2<-01>,M3.5.0/-1,M10.5.0/0", + "America/Sitka": "AKST9AKDT,M3.2.0,M11.1.0", + "America/St_Barthelemy": "AST4", + "America/St_Johns": "NST3:30NDT,M3.2.0,M11.1.0", + "America/St_Kitts": "AST4", + "America/St_Lucia": "AST4", + "America/St_Thomas": "AST4", + "America/St_Vincent": "AST4", + "America/Swift_Current": "CST6", + "America/Tegucigalpa": "CST6", + "America/Thule": "AST4ADT,M3.2.0,M11.1.0", + "America/Thunder_Bay": "EST5EDT,M3.2.0,M11.1.0", + "America/Tijuana": "PST8PDT,M3.2.0,M11.1.0", + "America/Toronto": "EST5EDT,M3.2.0,M11.1.0", + "America/Tortola": "AST4", + "America/Vancouver": "PST8PDT,M3.2.0,M11.1.0", + "America/Whitehorse": "MST7", + "America/Winnipeg": "CST6CDT,M3.2.0,M11.1.0", + "America/Yakutat": "AKST9AKDT,M3.2.0,M11.1.0", + "America/Yellowknife": "MST7MDT,M3.2.0,M11.1.0", + "Antarctica/Casey": "<+08>-8", + "Antarctica/Davis": "<+07>-7", + "Antarctica/DumontDUrville": "<+10>-10", + "Antarctica/Macquarie": "AEST-10AEDT,M10.1.0,M4.1.0/3", + "Antarctica/Mawson": "<+05>-5", + "Antarctica/McMurdo": "NZST-12NZDT,M9.5.0,M4.1.0/3", + "Antarctica/Palmer": "<-03>3", + "Antarctica/Rothera": "<-03>3", + "Antarctica/Syowa": "<+03>-3", + "Antarctica/Troll": "<+00>0<+02>-2,M3.5.0/1,M10.5.0/3", + "Antarctica/Vostok": "<+05>-5", + "Arctic/Longyearbyen": "CET-1CEST,M3.5.0,M10.5.0/3", + "Asia/Aden": "<+03>-3", + "Asia/Almaty": "<+05>-5", + "Asia/Amman": "<+03>-3", + "Asia/Anadyr": "<+12>-12", + "Asia/Aqtau": "<+05>-5", + "Asia/Aqtobe": "<+05>-5", + "Asia/Ashgabat": "<+05>-5", + "Asia/Atyrau": "<+05>-5", + "Asia/Baghdad": "<+03>-3", + "Asia/Bahrain": "<+03>-3", + "Asia/Baku": "<+04>-4", + "Asia/Bangkok": "<+07>-7", + "Asia/Barnaul": "<+07>-7", + "Asia/Beirut": "EET-2EEST,M3.5.0/0,M10.5.0/0", + "Asia/Bishkek": "<+06>-6", + "Asia/Brunei": "<+08>-8", + "Asia/Chita": "<+09>-9", + "Asia/Choibalsan": "<+08>-8", + "Asia/Colombo": "<+0530>-5:30", + "Asia/Damascus": "<+03>-3", + "Asia/Dhaka": "<+06>-6", + "Asia/Dili": "<+09>-9", + "Asia/Dubai": "<+04>-4", + "Asia/Dushanbe": "<+05>-5", + "Asia/Famagusta": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Asia/Gaza": "EET-2EEST,M3.4.4/50,M10.4.4/50", + "Asia/Hebron": "EET-2EEST,M3.4.4/50,M10.4.4/50", + "Asia/Ho_Chi_Minh": "<+07>-7", + "Asia/Hong_Kong": "HKT-8", + "Asia/Hovd": "<+07>-7", + "Asia/Irkutsk": "<+08>-8", + "Asia/Jakarta": "WIB-7", + "Asia/Jayapura": "WIT-9", + "Asia/Jerusalem": "IST-2IDT,M3.4.4/26,M10.5.0", + "Asia/Kabul": "<+0430>-4:30", + "Asia/Kamchatka": "<+12>-12", + "Asia/Karachi": "PKT-5", + "Asia/Kathmandu": "<+0545>-5:45", + "Asia/Khandyga": "<+09>-9", + "Asia/Kolkata": "IST-5:30", + "Asia/Krasnoyarsk": "<+07>-7", + "Asia/Kuala_Lumpur": "<+08>-8", + "Asia/Kuching": "<+08>-8", + "Asia/Kuwait": "<+03>-3", + "Asia/Macau": "CST-8", + "Asia/Magadan": "<+11>-11", + "Asia/Makassar": "WITA-8", + "Asia/Manila": "PST-8", + "Asia/Muscat": "<+04>-4", + "Asia/Nicosia": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Asia/Novokuznetsk": "<+07>-7", + "Asia/Novosibirsk": "<+07>-7", + "Asia/Omsk": "<+06>-6", + "Asia/Oral": "<+05>-5", + "Asia/Phnom_Penh": "<+07>-7", + "Asia/Pontianak": "WIB-7", + "Asia/Pyongyang": "KST-9", + "Asia/Qatar": "<+03>-3", + "Asia/Qyzylorda": "<+05>-5", + "Asia/Riyadh": "<+03>-3", + "Asia/Sakhalin": "<+11>-11", + "Asia/Samarkand": "<+05>-5", + "Asia/Seoul": "KST-9", + "Asia/Shanghai": "CST-8", + "Asia/Singapore": "<+08>-8", + "Asia/Srednekolymsk": "<+11>-11", + "Asia/Taipei": "CST-8", + "Asia/Tashkent": "<+05>-5", + "Asia/Tbilisi": "<+04>-4", + "Asia/Tehran": "<+0330>-3:30", + "Asia/Thimphu": "<+06>-6", + "Asia/Tokyo": "JST-9", + "Asia/Tomsk": "<+07>-7", + "Asia/Ulaanbaatar": "<+08>-8", + "Asia/Urumqi": "<+06>-6", + "Asia/Ust-Nera": "<+10>-10", + "Asia/Vientiane": "<+07>-7", + "Asia/Vladivostok": "<+10>-10", + "Asia/Yakutsk": "<+09>-9", + "Asia/Yangon": "<+0630>-6:30", + "Asia/Yekaterinburg": "<+05>-5", + "Asia/Yerevan": "<+04>-4", + "Atlantic/Azores": "<-01>1<+00>,M3.5.0/0,M10.5.0/1", + "Atlantic/Bermuda": "AST4ADT,M3.2.0,M11.1.0", + "Atlantic/Canary": "WET0WEST,M3.5.0/1,M10.5.0", + "Atlantic/Cape_Verde": "<-01>1", + "Atlantic/Faroe": "WET0WEST,M3.5.0/1,M10.5.0", + "Atlantic/Madeira": "WET0WEST,M3.5.0/1,M10.5.0", + "Atlantic/Reykjavik": "GMT0", + "Atlantic/South_Georgia": "<-02>2", + "Atlantic/St_Helena": "GMT0", + "Atlantic/Stanley": "<-03>3", + "Australia/Adelaide": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", + "Australia/Brisbane": "AEST-10", + "Australia/Broken_Hill": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", + "Australia/Currie": "AEST-10AEDT,M10.1.0,M4.1.0/3", + "Australia/Darwin": "ACST-9:30", + "Australia/Eucla": "<+0845>-8:45", + "Australia/Hobart": "AEST-10AEDT,M10.1.0,M4.1.0/3", + "Australia/Lindeman": "AEST-10", + "Australia/Lord_Howe": "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0", + "Australia/Melbourne": "AEST-10AEDT,M10.1.0,M4.1.0/3", + "Australia/Perth": "AWST-8", + "Australia/Sydney": "AEST-10AEDT,M10.1.0,M4.1.0/3", + "Etc/GMT": "GMT0", + "Etc/GMT+0": "GMT0", + "Etc/GMT+1": "<-01>1", + "Etc/GMT+10": "<-10>10", + "Etc/GMT+11": "<-11>11", + "Etc/GMT+12": "<-12>12", + "Etc/GMT+2": "<-02>2", + "Etc/GMT+3": "<-03>3", + "Etc/GMT+4": "<-04>4", + "Etc/GMT+5": "<-05>5", + "Etc/GMT+6": "<-06>6", + "Etc/GMT+7": "<-07>7", + "Etc/GMT+8": "<-08>8", + "Etc/GMT+9": "<-09>9", + "Etc/GMT-0": "GMT0", + "Etc/GMT-1": "<+01>-1", + "Etc/GMT-10": "<+10>-10", + "Etc/GMT-11": "<+11>-11", + "Etc/GMT-12": "<+12>-12", + "Etc/GMT-13": "<+13>-13", + "Etc/GMT-14": "<+14>-14", + "Etc/GMT-2": "<+02>-2", + "Etc/GMT-3": "<+03>-3", + "Etc/GMT-4": "<+04>-4", + "Etc/GMT-5": "<+05>-5", + "Etc/GMT-6": "<+06>-6", + "Etc/GMT-7": "<+07>-7", + "Etc/GMT-8": "<+08>-8", + "Etc/GMT-9": "<+09>-9", + "Etc/GMT0": "GMT0", + "Etc/Greenwich": "GMT0", + "Etc/UCT": "UTC0", + "Etc/UTC": "UTC0", + "Etc/Universal": "UTC0", + "Etc/Zulu": "UTC0", + "Europe/Amsterdam": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Andorra": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Astrakhan": "<+04>-4", + "Europe/Athens": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Belgrade": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Berlin": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Bratislava": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Brussels": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Bucharest": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Budapest": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Busingen": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Chisinau": "EET-2EEST,M3.5.0,M10.5.0/3", + "Europe/Copenhagen": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Dublin": "IST-1GMT0,M10.5.0,M3.5.0/1", + "Europe/Gibraltar": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Guernsey": "GMT0BST,M3.5.0/1,M10.5.0", + "Europe/Helsinki": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Isle_of_Man": "GMT0BST,M3.5.0/1,M10.5.0", + "Europe/Istanbul": "<+03>-3", + "Europe/Jersey": "GMT0BST,M3.5.0/1,M10.5.0", + "Europe/Kaliningrad": "EET-2", + "Europe/Kiev": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Kirov": "MSK-3", + "Europe/Lisbon": "WET0WEST,M3.5.0/1,M10.5.0", + "Europe/Ljubljana": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/London": "GMT0BST,M3.5.0/1,M10.5.0", + "Europe/Luxembourg": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Madrid": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Malta": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Mariehamn": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Minsk": "<+03>-3", + "Europe/Monaco": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Moscow": "MSK-3", + "Europe/Oslo": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Paris": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Podgorica": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Prague": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Riga": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Rome": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Samara": "<+04>-4", + "Europe/San_Marino": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Sarajevo": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Saratov": "<+04>-4", + "Europe/Simferopol": "MSK-3", + "Europe/Skopje": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Sofia": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Stockholm": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Tallinn": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Tirane": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Ulyanovsk": "<+04>-4", + "Europe/Uzhgorod": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Vaduz": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Vatican": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Vienna": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Vilnius": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Volgograd": "MSK-3", + "Europe/Warsaw": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Zagreb": "CET-1CEST,M3.5.0,M10.5.0/3", + "Europe/Zaporozhye": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "Europe/Zurich": "CET-1CEST,M3.5.0,M10.5.0/3", + "Indian/Antananarivo": "EAT-3", + "Indian/Chagos": "<+06>-6", + "Indian/Christmas": "<+07>-7", + "Indian/Cocos": "<+0630>-6:30", + "Indian/Comoro": "EAT-3", + "Indian/Kerguelen": "<+05>-5", + "Indian/Mahe": "<+04>-4", + "Indian/Maldives": "<+05>-5", + "Indian/Mauritius": "<+04>-4", + "Indian/Mayotte": "EAT-3", + "Indian/Reunion": "<+04>-4", + "Pacific/Apia": "<+13>-13", + "Pacific/Auckland": "NZST-12NZDT,M9.5.0,M4.1.0/3", + "Pacific/Bougainville": "<+11>-11", + "Pacific/Chatham": "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45", + "Pacific/Chuuk": "<+10>-10", + "Pacific/Easter": "<-06>6<-05>,M9.1.6/22,M4.1.6/22", + "Pacific/Efate": "<+11>-11", + "Pacific/Enderbury": "<+13>-13", + "Pacific/Fakaofo": "<+13>-13", + "Pacific/Fiji": "<+12>-12", + "Pacific/Funafuti": "<+12>-12", + "Pacific/Galapagos": "<-06>6", + "Pacific/Gambier": "<-09>9", + "Pacific/Guadalcanal": "<+11>-11", + "Pacific/Guam": "ChST-10", + "Pacific/Honolulu": "HST10", + "Pacific/Kiritimati": "<+14>-14", + "Pacific/Kosrae": "<+11>-11", + "Pacific/Kwajalein": "<+12>-12", + "Pacific/Majuro": "<+12>-12", + "Pacific/Marquesas": "<-0930>9:30", + "Pacific/Midway": "SST11", + "Pacific/Nauru": "<+12>-12", + "Pacific/Niue": "<-11>11", + "Pacific/Norfolk": "<+11>-11<+12>,M10.1.0,M4.1.0/3", + "Pacific/Noumea": "<+11>-11", + "Pacific/Pago_Pago": "SST11", + "Pacific/Palau": "<+09>-9", + "Pacific/Pitcairn": "<-08>8", + "Pacific/Pohnpei": "<+11>-11", + "Pacific/Port_Moresby": "<+10>-10", + "Pacific/Rarotonga": "<-10>10", + "Pacific/Saipan": "ChST-10", + "Pacific/Tahiti": "<-10>10", + "Pacific/Tarawa": "<+12>-12", + "Pacific/Tongatapu": "<+13>-13", + "Pacific/Wake": "<+12>-12", + "Pacific/Wallis": "<+12>-12", +}; + +export const currentPrerelease = { + id: "v2.5.4.8d288d5", + title: "Meshtastic Firmware 2.5.4.8d288d5 Alpha", + zip_url: + "https://github.com/meshtastic/firmware/releases/download/v2.5.4.8d288d5/firmware-2.5.4.8d288d5.zip", + release_notes: `## Enhancements * Comment on PR with build artifacts link by @scruplelesswizard in https://github.com/meshtastic/firmware/pull/4896 * Return queue status on rate limit throttling by @thebentern in https://github.com/meshtastic/firmware/pull/4901 * Get accelerometerThread running from AdminModule. by @gjelsoe in https://github.com/meshtastic/firmware/pull/4886 @@ -385,6 +849,5 @@ export const currentPrerelease = * Regenerate public key on boot, to avoid accidental mismatch. by @jp-bennett in https://github.com/meshtastic/firmware/pull/4916 * Fix #4911 : Partially rework some code to remove warnings about potential non-aligned memory accesses by @TheMalkavien in https://github.com/meshtastic/firmware/pull/4912 -**Full Changelog**: https://github.com/meshtastic/firmware/compare/v2.5.3.a70d5ee...v2.5.4.8d288d5` -} - +**Full Changelog**: https://github.com/meshtastic/firmware/compare/v2.5.3.a70d5ee...v2.5.4.8d288d5`, +}; From 2d31fd1de2ef974524cba13a01d1e41e86abbd2b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 5 Oct 2024 19:29:24 -0500 Subject: [PATCH 2/7] Padding --- stores/firmwareStore.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stores/firmwareStore.ts b/stores/firmwareStore.ts index 1caebdc..ef6c4ef 100644 --- a/stores/firmwareStore.ts +++ b/stores/firmwareStore.ts @@ -27,8 +27,8 @@ import { import { createUrl } from './store'; const previews = new Array()// new Array(currentPrerelease) - const firmwareApi = mande(createUrl('api/github/firmware/list')) +const TZP_PLACEHOLDER = 'tzplaceholder ' export const useFirmwareStore = defineStore('firmware', { state: () => { @@ -182,8 +182,10 @@ export const useFirmwareStore = defineStore('firmware', { let appContent = await this.fetchBinaryContent(fileName, true); const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; // get posix timezone string based on browser locale - const posixTz = timezones[tz as keyof typeof timezones] || 'CST6CDT,M3.2.0,M11.1.0'; - appContent.replace('tzplaceholder ', posixTz); + const posixTz = timezones[tz as keyof typeof timezones]; + if (posixTz) + appContent = appContent.replace(TZP_PLACEHOLDER, posixTz.padEnd(TZP_PLACEHOLDER.length, ' ')); + const sha256 = new Uint8Array(32); const sha256sum = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(appContent)); new Uint8Array(sha256sum).forEach((byte, index) => { From b53221ede220c78991b00a1db2290d7ec9344e08 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 5 Oct 2024 20:46:14 -0500 Subject: [PATCH 3/7] Progress? You be the judge --- stores/firmwareStore.ts | 14 ++++++++++---- utils/fileUtils.ts | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/stores/firmwareStore.ts b/stores/firmwareStore.ts index ef6c4ef..272c81a 100644 --- a/stores/firmwareStore.ts +++ b/stores/firmwareStore.ts @@ -186,6 +186,10 @@ export const useFirmwareStore = defineStore('firmware', { if (posixTz) appContent = appContent.replace(TZP_PLACEHOLDER, posixTz.padEnd(TZP_PLACEHOLDER.length, ' ')); + // The file is padded with zeros until its size is one byte less than a multiple of 16 bytes. A last byte (thus making the file size a multiple of 16) is the checksum of the data of all segments. The checksum is defined as the xor-sum of all bytes and the byte 0xEF. + const calculateChecksum = this.calculateChecksum(convertToUint8Array(appContent)); + appContent += convertToBinaryString(new Uint8Array(calculateChecksum)); + // esp32 checksum const sha256 = new Uint8Array(32); const sha256sum = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(appContent)); new Uint8Array(sha256sum).forEach((byte, index) => { @@ -215,15 +219,17 @@ export const useFirmwareStore = defineStore('firmware', { }; await this.startWrite(terminal, espLoader, transport, flashOptions); }, - async fetchBinaryContent(fileName: string, truncateLast32 = false): Promise { + calculateChecksum(data: Uint8Array) : number { + return data.reduce((acc, byte) => acc ^ byte, 0xEF); + }, + async fetchBinaryContent(fileName: string, truncateChecksum = false): Promise { if (this.selectedFirmware?.zip_url) { const baseUrl = getCorsFriendyReleaseUrl(this.selectedFirmware.zip_url); const response = await fetch(`${baseUrl}/${fileName}`); const blob = await response.blob(); let data = await blob.arrayBuffer(); - debugger - if (truncateLast32) - data = new Uint8Array(data).slice(0, -32); + if (truncateChecksum) + data = data.slice(0, -33); return convertToBinaryString(new Uint8Array(data)); } diff --git a/utils/fileUtils.ts b/utils/fileUtils.ts index 1dae613..f126288 100644 --- a/utils/fileUtils.ts +++ b/utils/fileUtils.ts @@ -9,6 +9,14 @@ export function downloadBlob(url: string, filename: string) { document.body.removeChild(link); } +export function convertToUint8Array(binaryString: string) { + let bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; +} + export function convertToBinaryString(bytes: Uint8Array) { let binaryString = ""; for (let i = 0; i < bytes.length; i++) { From 28c489f0e4c8d5661ba402f641b52cd315688071 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 6 Oct 2024 09:37:40 -0500 Subject: [PATCH 4/7] Still not working, but perhaps more correct --- stores/firmwareStore.ts | 28 ++++++++++++---------------- utils/fileUtils.ts | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/stores/firmwareStore.ts b/stores/firmwareStore.ts index 272c81a..9922ae5 100644 --- a/stores/firmwareStore.ts +++ b/stores/firmwareStore.ts @@ -28,7 +28,7 @@ import { createUrl } from './store'; const previews = new Array()// new Array(currentPrerelease) const firmwareApi = mande(createUrl('api/github/firmware/list')) -const TZP_PLACEHOLDER = 'tzplaceholder ' +const TZ_PLACEHOLDER = 'tzplaceholder ' export const useFirmwareStore = defineStore('firmware', { state: () => { @@ -180,23 +180,22 @@ export const useFirmwareStore = defineStore('firmware', { const transport = new Transport(this.port, true); const espLoader = await this.connectEsp32(transport, terminal); let appContent = await this.fetchBinaryContent(fileName, true); + const originalAppContent = await this.fetchBinaryContent(fileName); const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; // get posix timezone string based on browser locale const posixTz = timezones[tz as keyof typeof timezones]; if (posixTz) - appContent = appContent.replace(TZP_PLACEHOLDER, posixTz.padEnd(TZP_PLACEHOLDER.length, ' ')); + appContent = appContent.replace(TZ_PLACEHOLDER, posixTz.padEnd(TZ_PLACEHOLDER.length, ' ')); // The file is padded with zeros until its size is one byte less than a multiple of 16 bytes. A last byte (thus making the file size a multiple of 16) is the checksum of the data of all segments. The checksum is defined as the xor-sum of all bytes and the byte 0xEF. - const calculateChecksum = this.calculateChecksum(convertToUint8Array(appContent)); - appContent += convertToBinaryString(new Uint8Array(calculateChecksum)); + const calculateChecksum = espLoader.checksum(convertToUint8Array(appContent)); + const appDataWithChecksum = new Uint8Array([...convertToUint8Array(appContent), ...new Uint8Array(calculateChecksum)]); // esp32 checksum - const sha256 = new Uint8Array(32); - const sha256sum = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(appContent)); - new Uint8Array(sha256sum).forEach((byte, index) => { - sha256[index] = byte; - }); - appContent += convertToBinaryString(sha256); - + const sha256sum = await crypto.subtle.digest('SHA-256', appDataWithChecksum); + appContent = convertToBinaryString(new Uint8Array([...appDataWithChecksum, ...new Uint8Array(sha256sum)])); + console.log(originalAppContent === appContent); + console.log('Length:', appContent.length); + console.log('Original length:', originalAppContent.length); const otaContent = await this.fetchBinaryContent(otaFileName); const littleFsContent = await this.fetchBinaryContent(littleFsFileName); this.isFlashing = true; @@ -219,9 +218,6 @@ export const useFirmwareStore = defineStore('firmware', { }; await this.startWrite(terminal, espLoader, transport, flashOptions); }, - calculateChecksum(data: Uint8Array) : number { - return data.reduce((acc, byte) => acc ^ byte, 0xEF); - }, async fetchBinaryContent(fileName: string, truncateChecksum = false): Promise { if (this.selectedFirmware?.zip_url) { const baseUrl = getCorsFriendyReleaseUrl(this.selectedFirmware.zip_url); @@ -229,7 +225,7 @@ export const useFirmwareStore = defineStore('firmware', { const blob = await response.blob(); let data = await blob.arrayBuffer(); if (truncateChecksum) - data = data.slice(0, -33); + data = data.slice(0, data.byteLength - 33); return convertToBinaryString(new Uint8Array(data)); } @@ -293,7 +289,7 @@ export const useFirmwareStore = defineStore('firmware', { const reader = inputStream.getReader(); while (true) { - const{ value } = await reader.read(); + const { value } = await reader.read(); if (value) { terminal.write(value); } diff --git a/utils/fileUtils.ts b/utils/fileUtils.ts index f126288..f2e8d45 100644 --- a/utils/fileUtils.ts +++ b/utils/fileUtils.ts @@ -19,7 +19,7 @@ export function convertToUint8Array(binaryString: string) { export function convertToBinaryString(bytes: Uint8Array) { let binaryString = ""; - for (let i = 0; i < bytes.length; i++) { + for (let i = 0; i < bytes.byteLength; i++) { binaryString += String.fromCharCode(bytes[i]); } return binaryString; From 10c366480be482aa1912c4170fdb9cc0006775c6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 6 Oct 2024 16:35:09 -0500 Subject: [PATCH 5/7] Even closer, but still bogus checksum --- stores/firmwareStore.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stores/firmwareStore.ts b/stores/firmwareStore.ts index 9922ae5..74593ea 100644 --- a/stores/firmwareStore.ts +++ b/stores/firmwareStore.ts @@ -188,10 +188,10 @@ export const useFirmwareStore = defineStore('firmware', { appContent = appContent.replace(TZ_PLACEHOLDER, posixTz.padEnd(TZ_PLACEHOLDER.length, ' ')); // The file is padded with zeros until its size is one byte less than a multiple of 16 bytes. A last byte (thus making the file size a multiple of 16) is the checksum of the data of all segments. The checksum is defined as the xor-sum of all bytes and the byte 0xEF. - const calculateChecksum = espLoader.checksum(convertToUint8Array(appContent)); - const appDataWithChecksum = new Uint8Array([...convertToUint8Array(appContent), ...new Uint8Array(calculateChecksum)]); + const calculateChecksum = espLoader.checksum(convertToUint8Array(appContent).slice(65536)); + const appDataWithChecksum = new Uint8Array([...convertToUint8Array(appContent), ...new Uint8Array([calculateChecksum])]); // esp32 checksum - const sha256sum = await crypto.subtle.digest('SHA-256', appDataWithChecksum); + const sha256sum = await crypto.subtle.digest('SHA-256', appDataWithChecksum.slice(65536)); appContent = convertToBinaryString(new Uint8Array([...appDataWithChecksum, ...new Uint8Array(sha256sum)])); console.log(originalAppContent === appContent); console.log('Length:', appContent.length); @@ -297,4 +297,4 @@ export const useFirmwareStore = defineStore('firmware', { } }, }, -}) \ No newline at end of file +}) From 5fa09cf5dcaf98c80f0ebe05ccecaabced643668 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 6 Oct 2024 20:22:55 -0500 Subject: [PATCH 6/7] Finally working --- stores/firmwareStore.ts | 5 +++-- utils/fileUtils.ts | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/stores/firmwareStore.ts b/stores/firmwareStore.ts index 74593ea..8054fe6 100644 --- a/stores/firmwareStore.ts +++ b/stores/firmwareStore.ts @@ -183,12 +183,13 @@ export const useFirmwareStore = defineStore('firmware', { const originalAppContent = await this.fetchBinaryContent(fileName); const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; // get posix timezone string based on browser locale - const posixTz = timezones[tz as keyof typeof timezones]; + const posixTz = timezones[tz as keyof typeof timezones] + "\0"; if (posixTz) appContent = appContent.replace(TZ_PLACEHOLDER, posixTz.padEnd(TZ_PLACEHOLDER.length, ' ')); // The file is padded with zeros until its size is one byte less than a multiple of 16 bytes. A last byte (thus making the file size a multiple of 16) is the checksum of the data of all segments. The checksum is defined as the xor-sum of all bytes and the byte 0xEF. - const calculateChecksum = espLoader.checksum(convertToUint8Array(appContent).slice(65536)); + // Do all of our devices have 8 segments? Can get that from the 2nd byte of the header + const calculateChecksum = calcChecksum(convertToUint8Array(appContent).slice(65536), 8); const appDataWithChecksum = new Uint8Array([...convertToUint8Array(appContent), ...new Uint8Array([calculateChecksum])]); // esp32 checksum const sha256sum = await crypto.subtle.digest('SHA-256', appDataWithChecksum.slice(65536)); diff --git a/utils/fileUtils.ts b/utils/fileUtils.ts index f2e8d45..c5bff48 100644 --- a/utils/fileUtils.ts +++ b/utils/fileUtils.ts @@ -25,6 +25,28 @@ export function convertToBinaryString(bytes: Uint8Array) { return binaryString; } +export function convertBytesToInt(byteArray: Uint8Array) { + let value = 0; + for (let i = byteArray.length - 1; i >= 0; i--) { + value = (value * 256) + byteArray[i]; + } + return value; +} + +export function calcChecksum(bytes: Uint8Array, segments: number) { + let offset = 24; + let hashValue = 0xEF; + + for (let i = 0; i < segments; i++) { + let length = convertBytesToInt(bytes.slice(offset + 4, offset + 8)); + console.log(length); + let segment = bytes.slice(offset + 8, offset+length+8); + hashValue = segment.reduce((acc, byte) => acc ^ byte, hashValue); + offset += length + 8; + } + return hashValue; +} + export async function checkIfRemoteFileExists(url: string): Promise { console.log('Checking if remote file exists: ', url); try { From 450982fc6f66e9e21d899e546cc67c52e1570174 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 6 Oct 2024 23:59:09 -0500 Subject: [PATCH 7/7] Add slipstream to uploaded files and updates. --- stores/firmwareStore.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/stores/firmwareStore.ts b/stores/firmwareStore.ts index 8054fe6..425638d 100644 --- a/stores/firmwareStore.ts +++ b/stores/firmwareStore.ts @@ -125,7 +125,28 @@ export const useFirmwareStore = defineStore('firmware', { }; const transport = new Transport(this.port, true); const espLoader = await this.connectEsp32(transport, terminal); - const content = await this.fetchBinaryContent(fileName); + let content = await this.fetchBinaryContent(fileName, true); + let originalAppContent = await this.fetchBinaryContent(fileName); + + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + // get posix timezone string based on browser locale + const posixTz = timezones[tz as keyof typeof timezones] + "\0"; + if (posixTz) + content = content.replace(TZ_PLACEHOLDER, posixTz.padEnd(TZ_PLACEHOLDER.length, ' ')); + + // The file is padded with zeros until its size is one byte less than a multiple of 16 bytes. A last byte (thus making the file size a multiple of 16) is the checksum of the data of all segments. The checksum is defined as the xor-sum of all bytes and the byte 0xEF. + // Do all of our devices have 8 segments? Can get that from the 2nd byte of the header + const calculateChecksum = calcChecksum(convertToUint8Array(content), 8); + const appDataWithChecksum = new Uint8Array([...convertToUint8Array(content), ...new Uint8Array([calculateChecksum])]); + console.log(appDataWithChecksum.length) + // esp32 checksum + const sha256sum = await crypto.subtle.digest('SHA-256', appDataWithChecksum); + console.log(appDataWithChecksum.length) + content = convertToBinaryString(new Uint8Array([...appDataWithChecksum, ...new Uint8Array(sha256sum)])); + console.log(originalAppContent === content); + console.log('Length:', content.length); + console.log('Original length:', originalAppContent.length); + this.isFlashing = true; const flashOptions: FlashOptions = { fileArray: [{ data: content, address: 0x10000 }], @@ -246,13 +267,17 @@ export const useFirmwareStore = defineStore('firmware', { console.log('Found file:', file.filename); if (file?.getData) { const blob = await file.getData(new BlobWriter()); - const arrayBuffer = await blob.arrayBuffer(); + let arrayBuffer = await blob.arrayBuffer(); + if (truncateChecksum) + arrayBuffer = arrayBuffer.slice(0, arrayBuffer.byteLength - 33); return convertToBinaryString(new Uint8Array(arrayBuffer)); } throw new Error(`Could not find file with pattern ${fileName} in zip`); } } else if (this.selectedFile && !this.isZipFile) { - const buffer = await this.selectedFile.arrayBuffer(); + let buffer = await this.selectedFile.arrayBuffer(); + if (truncateChecksum) + buffer = buffer.slice(0, buffer.byteLength - 33); return convertToBinaryString(new Uint8Array(buffer)); } throw new Error('Cannot fetch binary content without a file or firmware selected');