gta4xl-common: Support updating firmware

Change-Id: Iff87746b6e3efd5ed0af207e689fd068ad965212
diff --git a/BoardConfigCommon.mk b/BoardConfigCommon.mk
index 4dabd01..eb05bdb 100644
--- a/BoardConfigCommon.mk
+++ b/BoardConfigCommon.mk
@@ -149,6 +149,7 @@
 BOARD_INCLUDE_RECOVERY_DTBO := true
 TARGET_RECOVERY_FSTAB := $(COMMON_PATH)/configs/init/fstab.exynos9611
 TARGET_RECOVERY_PIXEL_FORMAT := "ABGR_8888"
+TARGET_RECOVERY_UPDATER_LIBS := librecovery_updater_exynos9611
 
 ## Releasetools
 TARGET_RELEASETOOLS_EXTENSIONS := $(COMMON_PATH)/releasetools
diff --git a/recovery/Android.bp b/recovery/Android.bp
new file mode 100644
index 0000000..fb4bebe
--- /dev/null
+++ b/recovery/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2022 The LineageOS Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_static {
+    name: "librecovery_updater_exynos9611",
+    srcs: [
+        "recovery_updater.cpp",
+    ],
+    header_libs: ["libbase_headers"],
+    include_dirs: [
+        "bootable/recovery",
+        "bootable/recovery/edify/include",
+        "bootable/recovery/otautil/include",
+        "system/libziparchive/include"
+    ],
+}
diff --git a/recovery/recovery_updater.cpp b/recovery/recovery_updater.cpp
new file mode 100644
index 0000000..eff2c0f
--- /dev/null
+++ b/recovery/recovery_updater.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <libgen.h>
+#include <unistd.h>
+
+#include <android-base/properties.h>
+#include <edify/expr.h>
+#include <otautil/error_code.h>
+#include <ziparchive/zip_archive.h>
+
+Value *VerifyNoDowngradeFn(const char* name, State *state,
+                             const std::vector<std::unique_ptr<Expr>>& argv) {
+  int ret = 1;
+  std::vector<std::string> args;
+
+  if (argv.size() != 1 || !ReadArgs(state, argv, &args))
+    return ErrorAbort(state, kArgsParsingFailure,
+                      "%s() error parsing arguments", name);
+
+  std::string blModel = android::base::GetProperty("ro.boot.em.model", "");
+  std::string shortModel = blModel.substr(blModel.find("-") + 1, std::string::npos);
+  std::string blVer = android::base::GetProperty("ro.boot.bootloader", "");
+  if (blVer.length() >= shortModel.length() + 4
+      && args[0].length() >= shortModel.length() + 4) { // <model>XXU<binary>
+    std::string curBinary = blVer.substr(shortModel.length() + 3, 1);
+    std::string newBinary = args[0].substr(shortModel.length() + 3, 1);
+    if (newBinary.at(0) >= curBinary.at(0)) {
+      ret = 0;
+    }
+  }
+
+  return StringValue(std::to_string(ret));
+}
+
+Value *MarkHeaderBtFn(const char* name, State *state,
+                        const std::vector<std::unique_ptr<Expr>>& argv) {
+  int ret = 0;
+  std::vector<std::string> args;
+  const char* partition;
+  uint32_t magicOffset;
+  uint32_t numImages;
+  uint32_t magic1;
+
+  if(argv.size() < 4 || argv.size() > 4 || !ReadArgs(state, argv, &args))
+    return ErrorAbort(state, kArgsParsingFailure,
+                      "%s() error parsing arguments", name);
+
+  partition = args[0].c_str();
+  magicOffset = std::atoi(args[1].c_str());
+  numImages = std::atoi(args[2].c_str());
+  magic1 = std::atoi(args[3].c_str());
+
+  int fd = open(partition, O_RDWR);
+  if (fd < 0)
+    return ErrorAbort(state, kFileOpenFailure,
+                      "%s() failed to open %s", name, partition);
+
+  magic1 = magic1 << (magicOffset * 8);
+  numImages = numImages << (magicOffset * 8);
+  write(fd, (char*)&magic1, sizeof(uint32_t));
+  write(fd, (char*)&numImages, sizeof(uint32_t));
+  close(fd);
+
+  return StringValue(std::to_string(ret));
+}
+
+#define FILENAME_MAX_LEN 32
+
+Value *WriteDataBtFn(const char* name, State *state,
+                        const std::vector<std::unique_ptr<Expr>>& argv) {
+  int ret = 0;
+  std::vector<std::string> args;
+  const char* file;
+  const char* filename;
+  const char* partition;
+  uint32_t offset;
+  uint32_t filesize;
+
+  if(argv.size() < 4 || argv.size() > 4 || !ReadArgs(state, argv, &args))
+    return ErrorAbort(state, kArgsParsingFailure,
+                      "%s() error parsing arguments", name);
+
+  file = args[0].c_str();
+  filename = basename(file);
+  partition = args[1].c_str();
+  offset = std::atoi(args[2].c_str());
+  filesize = std::atoi(args[3].c_str());
+
+  int fd = open(partition, O_RDWR);
+  if (fd < 0)
+    return ErrorAbort(state, kFileOpenFailure,
+                      "%s() failed to open %s", name, partition);
+
+  char filename_padded[FILENAME_MAX_LEN] = { 0 };
+  strcpy(&filename_padded[0], filename);
+
+  lseek(fd, offset, SEEK_SET);
+  write(fd, &filename_padded[0], FILENAME_MAX_LEN);
+  write(fd, (char*)&filesize, sizeof(uint32_t));
+
+  // write data
+  ZipArchiveHandle za = state->updater->GetPackageHandle();
+  ZipEntry64 entry;
+  if (FindEntry(za, file, &entry) != 0) {
+    return ErrorAbort(state, kPackageExtractFileFailure,
+                      "%s() %s not found in package", name, file);
+  }
+
+  if (ExtractEntryToFile(za, &entry, fd))
+    return ErrorAbort(state, kPackageExtractFileFailure,
+                      "%s() failed to extract %s from package", name, file);
+
+  close(fd);
+
+  return StringValue(std::to_string(ret));
+}
+
+void Register_librecovery_updater_exynos9611() {
+  RegisterFunction("exynos9611.verify_no_downgrade", VerifyNoDowngradeFn);
+  RegisterFunction("exynos9611.mark_header_bt", MarkHeaderBtFn);
+  RegisterFunction("exynos9611.write_data_bt", WriteDataBtFn);
+}
diff --git a/releasetools/releasetools.py b/releasetools/releasetools.py
index 3e59bc6..584791b 100644
--- a/releasetools/releasetools.py
+++ b/releasetools/releasetools.py
@@ -1,6 +1,6 @@
 #!/bin/env python3
 #
-# Copyright (C) 2020 The LineageOS Project
+# Copyright (C) 2020-2022 The LineageOS Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -34,9 +34,53 @@
 def PrintInfo(info, dest):
   info.script.Print("Patching {} image unconditionally...".format(dest.split('/')[-1]))
 
+def AddFirmwareImage(info, model, basename, dest, simple=False, offset=8):
+  if ("RADIO/%s_%s" % (basename, model)) in info.input_zip.namelist():
+    data = info.input_zip.read("RADIO/%s_%s" % (basename, model))
+    common.ZipWriteStr(info.output_zip, "firmware/%s/%s" % (model, basename), data);
+    info.script.Print("Patching {} image unconditionally...".format(basename.split('.')[0]));
+    if simple:
+      info.script.AppendExtra('package_extract_file("firmware/%s/%s", "%s");' % (model, basename, dest))
+    else:
+      size = info.input_zip.getinfo("RADIO/%s_%s" % (basename, model)).file_size
+      info.script.AppendExtra('assert(exynos9611.write_data_bt("firmware/%s/%s", "%s", %d, %d));' % (model, basename, dest, offset, size))
+      return size
+    return 0
+
 def OTA_InstallEnd(info):
   PrintInfo(info, "/dev/block/by-name/dtbo")
   AddImage(info, "dtbo.img", "/dev/block/by-name/dtbo")
   PrintInfo(info, "/dev/block/by-name/vbmeta")
   AddImage(info, "vbmeta.img", "/dev/block/by-name/vbmeta")
+
+  if "RADIO/models" in info.input_zip.namelist():
+    modelsIncluded = []
+    for model in info.input_zip.read("RADIO/models").decode('utf-8').splitlines():
+      if "RADIO/version_%s" % model in info.input_zip.namelist():
+        modelsIncluded.append(model)
+        version = info.input_zip.read("RADIO/version_%s" % model).decode('utf-8').splitlines()[0]
+        offset = 8
+        numImages = 0
+        info.script.AppendExtra('# Firmware update to %s for %s' % (version, model))
+        info.script.AppendExtra('ifelse (getprop("ro.boot.em.model") == "%s" &&' % model)
+        info.script.AppendExtra('exynos9611.verify_no_downgrade("%s") == "0" &&' % version)
+        info.script.AppendExtra('getprop("ro.boot.bootloader") != "%s",' % version)
+        info.script.AppendExtra('assert(exynos9611.mark_header_bt("/dev/block/by-name/bota", 0, 0, 0));')
+        for image in 'cm.bin', 'keystorage.bin', 'sboot.bin', 'uh.bin', 'up_param.bin':
+          size = AddFirmwareImage(info, model, image, "/dev/block/by-name/bota", False, offset)
+          if size > 0:
+            numImages += 1
+            offset += size + 36 # header size
+        info.script.AppendExtra('assert(exynos9611.mark_header_bt("/dev/block/by-name/bota", 0, %d, 3142939818));' % numImages)
+        AddFirmwareImage(info, model, "modem.bin", "/dev/block/by-name/radio", True)
+        AddFirmwareImage(info, model, "modem_debug.bin", "/dev/block/by-name/cp_debug", True)
+        info.script.AppendExtra(',"");')
+
+    modelCheck = ""
+    for model in modelsIncluded:
+      if len(modelCheck) > 0:
+        modelCheck += ' || '
+      modelCheck += 'getprop("ro.boot.em.model") == "%s"' % model
+    if len(modelCheck) > 0:
+      info.script.AppendExtra('%s || abort("Unsupported model, not updating firmware!");' % modelCheck)
   return