UpdateService class

Пакет: com.hypixel.hytale.server.core.update

Файл: com/hypixel/hytale/server/core/update/UpdateService.java

Поля (70)

МодификаторыТипИмя
public static final BuilderCodec<UpdateService.GetPatchlinesResponse> CODEC
public static final BuilderCodec<UpdateService.PatchlineSummary> CODEC
public static final BuilderCodec<UpdateService.SignedUrlResponse> CODEC
public static final BuilderCodec<UpdateService.VersionManifest> CODEC
final HytaleLogger LOGGER
public String downloadUrl
public long expiresAt
public String name
public UpdateService.PatchlineSummary[] patchlines
return
return
public String sha256
public String url
UpdateConfig var0
Path var0
Path var0
CodeSource var1
String var1
var1
long var10
Path var10
MessageDigest var12
var12
InputStream var13
OutputStream var14
byte[] var15
long var16
int var18
int var19
ServerAuthManager var2
Path var2
Path var2
Path var2
Manifest var2
String var2
String var3
ServerAuthManager var3
CompletableFuture var3
Path var3
String var3
Path var3
Attributes var3
HttpRequest var3
String var33
String var4
CompletableFuture var4
Path var4
String var4
Thread var4
Path var4
HttpRequest var4
String var4
HttpResponse var4
String var5
Thread var5
String var5
Path var5
HttpResponse var5
UpdateService.GetPatchlinesResponse var5
HttpRequest var6
HttpRequest var6
Path var6
UpdateService.SignedUrlResponse var6
HttpResponse var7
Path var7
UpdateService.VersionManifest var8
boolean var8
var8
HttpResponse var9
public String version

Методы (37)

МодификаторыВозвратСигнатура
abstract throw new CancellationExceptionthrow new CancellationException("Update download cancelled")
abstract throw new CancellationExceptionthrow new CancellationException("Bootstrap install cancelled")
public record DownloadTaskrecord DownloadTask(CompletableFuture<Boolean> future, Thread thread)
public GetPatchlinesResponsepublic GetPatchlinesResponse()
public PatchlineSummarypublic PatchlineSummary()
private SignedUrlResponseprivate SignedUrlResponse()
public VersionManifestpublic VersionManifest()
static boolean clearStagingDirboolean clearStagingDir(@Nonnull Path var0)
static boolean deleteBackupDirboolean deleteBackupDir()
static void deleteRunningJarIfPossiblevoid deleteRunningJarIfPossible(@Nonnull Path var0)
abstract deleteRunningJarIfPossible deleteRunningJarIfPossible(var4)
static boolean deleteStagedUpdateboolean deleteStagedUpdate()
abstract return deleteStagedUpdatereturn deleteStagedUpdate()
public UpdateService.DownloadTask downloadUpdateUpdateService.DownloadTask downloadUpdate(@Nonnull UpdateService.VersionManifest var1, @Nonnull Path var2, @Nullable UpdateService.ProgressCallback var3)
static <T> KeyedCodec<T> externalKey<T> KeyedCodec<T> externalKey(String var0, Codec<T> var1)
if if(var3 == null)
if if(var5 == null)
if if(var8 != null && var8.version != null)
if if(var4 == null)
if if(var4 == null)
if if(var5 == null)
if if(var2 != null && var10 > 0L)
if if(!var8)
if if(var3 == null)
if if(var1 == null)
if if(var2 == null)
static boolean isValidUpdateLayoutboolean isValidUpdateLayout()
static void moveIntoServerDirvoid moveIntoServerDir(@Nonnull Path var0, @Nonnull Path var1)
abstract moveIntoServerDir moveIntoServerDir(var6, var10)
abstract void onProgressvoid onProgress(int var1, long var2, long var4)
public UpdateService.DownloadTask performBootstrapInstallUpdateService.DownloadTask performBootstrapInstall(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2)
private boolean performBootstrapInstallSyncboolean performBootstrapInstallSync(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2)
private boolean performDownloadboolean performDownload(@Nonnull UpdateService.VersionManifest var1, @Nonnull Path var2, @Nullable UpdateService.ProgressCallback var3)
static boolean safeDeleteUpdaterDirboolean safeDeleteUpdaterDir(Path var0, String var1)
abstract return safeDeleteUpdaterDirreturn safeDeleteUpdaterDir(STAGING_DIR, "staging")
abstract return safeDeleteUpdaterDirreturn safeDeleteUpdaterDir(BACKUP_DIR, "backup")
abstract try try(InputStream var13 = var9.body()

Исходный код

Показать/скрыть
class="kw">package com.hypixel.hytale.server.core.update;

class="kw">import com.hypixel.hytale.codec.Codec;
class="kw">import com.hypixel.hytale.codec.EmptyExtraInfo;
class="kw">import com.hypixel.hytale.codec.KeyedCodec;
class="kw">import com.hypixel.hytale.codec.builder.BuilderCodec;
class="kw">import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
class="kw">import com.hypixel.hytale.codec.util.RawJsonReader;
class="kw">import com.hypixel.hytale.common.util.java.ManifestUtil;
class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.server.core.HytaleServer;
class="kw">import com.hypixel.hytale.server.core.HytaleServerConfig;
class="kw">import com.hypixel.hytale.server.core.auth.AuthConfig;
class="kw">import com.hypixel.hytale.server.core.auth.EncryptedAuthCredentialStore;
class="kw">import com.hypixel.hytale.server.core.auth.ServerAuthManager;
class="kw">import com.hypixel.hytale.server.core.config.UpdateConfig;
class="kw">import com.hypixel.hytale.server.core.util.ServiceHttpClientFactory;
class="kw">import com.hypixel.hytale.server.core.util.io.FileUtil;
class="kw">import java.io.IOException;
class="kw">import java.io.InputStream;
class="kw">import java.io.OutputStream;
class="kw">import java.net.URI;
class="kw">import java.net.http.HttpClient;
class="kw">import java.net.http.HttpRequest;
class="kw">import java.net.http.HttpResponse;
class="kw">import java.net.http.HttpClient.Redirect;
class="kw">import java.net.http.HttpResponse.BodyHandlers;
class="kw">import java.nio.file.AccessDeniedException;
class="kw">import java.nio.file.Files;
class="kw">import java.nio.file.Path;
class="kw">import java.nio.file.StandardCopyOption;
class="kw">import java.security.CodeSource;
class="kw">import java.security.MessageDigest;
class="kw">import java.security.NoSuchAlgorithmException;
class="kw">import java.time.Duration;
class="kw">import java.util.HexFormat;
class="kw">import java.util.Locale;
class="kw">import java.util.concurrent.CancellationException;
class="kw">import java.util.concurrent.CompletableFuture;
class="kw">import java.util.jar.Attributes;
class="kw">import java.util.jar.JarFile;
class="kw">import java.util.jar.Manifest;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;

class="kw">public class UpdateService {
   class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
   class="kw">private class="kw">static class="kw">final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30L);
   class="kw">private class="kw">static class="kw">final Duration DOWNLOAD_TIMEOUT = Duration.ofMinutes(30L);
   class="kw">private class="kw">static class="kw">final Path STAGING_DIR = Path.of("..").resolve("updater").resolve("staging");
   class="kw">private class="kw">static class="kw">final Path BACKUP_DIR = Path.of("..").resolve("updater").resolve("backup");
   class="kw">private class="kw">final HttpClient httpClient;
   class="kw">private class="kw">final String accountDataUrl = "https://account-data.hytale.com";

   class="kw">public UpdateService() {
      this.httpClient = ServiceHttpClientFactory.newBuilder(REQUEST_TIMEOUT).followRedirects(Redirect.NORMAL).build();
   }

   @Nullable
   class="kw">public CompletableFuture<UpdateService.VersionManifest> checkForUpdate(@Nonnull String var1) {
      class="kw">return CompletableFuture.supplyAsync(
         () -> {
            try {
               ServerAuthManager var2 = ServerAuthManager.getInstance();
               String var3 = var2.getOAuthAccessToken();
               if (var3 == null) {
                  LOGGER.at(Level.WARNING).log("Cannot check for updates - not authenticated");
                  class="kw">return null;
               } else {
                  String var4 = String.format("version/%s.json", var1);
                  String var5 = this.getSignedUrl(var3, var4);
                  if (var5 == null) {
                     LOGGER.at(Level.WARNING).log("Failed to get signed URL for version manifest");
                     class="kw">return null;
                  } else {
                     HttpRequest var6 = HttpRequest.newBuilder()
                        .uri(URI.create(var5))
                        .header("Accept", "application/json")
                        .timeout(REQUEST_TIMEOUT)
                        .GET()
                        .build();
                     HttpResponse var7 = this.httpClient.send(var6, BodyHandlers.ofString());
                     if (var7.statusCode() != 200) {
                        LOGGER.at(Level.WARNING).log("Failed to fetch version manifest: HTTP %d", var7.statusCode());
                        class="kw">return null;
                     } else {
                        UpdateService.VersionManifest var8 = UpdateService.VersionManifest.CODEC
                           .decodeJson(new RawJsonReader(var7.body().toCharArray()), EmptyExtraInfo.EMPTY);
                        if (var8 != null && var8.version != null) {
                           LOGGER.at(Level.INFO).log("Found version: %s", var8.version);
                           class="kw">return var8;
                        } else {
                           LOGGER.at(Level.WARNING).log("Invalid version manifest response");
                           class="kw">return null;
                        }
                     }
                  }
               }
            } catch (IOException var9) {
               LOGGER.at(Level.WARNING).log("IO error checking for updates: %s", var9.getMessage());
               class="kw">return null;
            } catch (InterruptedException var10) {
               Thread.currentThread().interrupt();
               LOGGER.at(Level.WARNING).log("Update check interrupted");
               class="kw">return null;
            } catch (Exception var11) {
               ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var11)).log("Error checking for updates");
               class="kw">return null;
            }
         }
      );
   }

   class="kw">public UpdateService.DownloadTask downloadUpdate(
      @Nonnull UpdateService.VersionManifest var1, @Nonnull Path var2, @Nullable UpdateService.ProgressCallback var3
   ) {
      CompletableFuture var4 = new CompletableFuture<>();
      Thread var5 = new Thread(() -> {
         try {
            boolean var5 = this.performDownload(var1, var2, var3);
            var4.complete(var5);
         } catch (CancellationException var6) {
            var4.completeExceptionally(var6);
         } catch (InterruptedException var7) {
            Thread.currentThread().interrupt();
            var4.completeExceptionally(new CancellationException("Update download interrupted"));
         } catch (Exception var8) {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var8)).log("Error downloading update");
            var4.complete(false);
         }
      }, "UpdateDownload");
      var5.setDaemon(true);
      var5.start();
      class="kw">return new UpdateService.DownloadTask(var4, var5);
   }

   class="kw">private boolean performDownload(@Nonnull UpdateService.VersionManifest var1, @Nonnull Path var2, @Nullable UpdateService.ProgressCallback var3) class="kw">throws IOException, InterruptedException {
      Path var4 = this.downloadAndVerifyZip(var1, var3);
      if (var4 == null) {
         class="kw">return false;
      }

      try {
         if (!clearStagingDir(var2)) {
            LOGGER.at(Level.WARNING).log("Failed to clear staging directory before extraction");
            class="kw">return false;
         }

         Files.createDirectories(var2);
         if (Thread.currentThread().isInterrupted()) {
            throw new CancellationException("Update download cancelled");
         }

         FileUtil.extractZip(var4, var2);
         LOGGER.at(Level.INFO).log("Update %s downloaded and extracted to staging", var1.version);
         class="kw">return true;
      } class="kw">finally {
         Files.deleteIfExists(var4);
      }
   }

   @Nullable
   class="kw">private Path downloadAndVerifyZip(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2) class="kw">throws IOException, InterruptedException {
      ServerAuthManager var3 = ServerAuthManager.getInstance();
      String var4 = var3.getOAuthAccessToken();
      if (var4 == null) {
         LOGGER.at(Level.WARNING).log("Cannot download update - not authenticated");
         class="kw">return null;
      }

      String var5 = this.getSignedUrl(var4, var1.downloadUrl);
      if (var5 == null) {
         LOGGER.at(Level.WARNING).log("Failed to get signed URL for download");
         class="kw">return null;
      }

      HttpRequest var6 = HttpRequest.newBuilder().uri(URI.create(var5)).timeout(DOWNLOAD_TIMEOUT).GET().build();
      Path var7 = Files.createTempFile("hytale-update-", ".zip");
      boolean var8 = false;

      try {
         HttpResponse var9 = this.httpClient.send(var6, BodyHandlers.ofInputStream());
         if (var9.statusCode() != 200) {
            LOGGER.at(Level.WARNING).log("Failed to download update: HTTP %d", var9.statusCode());
            class="kw">return null;
         }

         long var10 = var9.headers().firstValueAsLong("Content-Length").orElse(-1L);

         MessageDigest var12;
         try {
            var12 = MessageDigest.getInstance("SHA-256");
         } catch (NoSuchAlgorithmException var29) {
            LOGGER.at(Level.SEVERE).log("SHA-256 not available - this should never happen");
            class="kw">return null;
         }

         try (
            InputStream var13 = var9.body();
            OutputStream var14 = Files.newOutputStream(var7);
         ) {
            byte[] var15 = new byte[8192];
            long var16 = 0L;

            int var18;
            while ((var18 = var13.read(var15)) != -1) {
               if (Thread.currentThread().isInterrupted()) {
                  throw new CancellationException("Update download cancelled");
               }

               var14.write(var15, 0, var18);
               var12.update(var15, 0, var18);
               var16 += var18;
               if (var2 != null && var10 > 0L) {
                  int var19 = (int)(var16 * 100L / var10);
                  var2.onProgress(var19, var16, var10);
               }
            }
         }

         String var33 = HexFormat.of().formatHex(var12.digest());
         if (var1.sha256 != null && !var1.sha256.equalsIgnoreCase(var33)) {
            LOGGER.at(Level.WARNING).log("Checksum mismatch! Expected: %s, Got: %s", var1.sha256, var33);
            class="kw">return null;
         } else {
            var8 = true;
            class="kw">return var7;
         }
      } class="kw">finally {
         if (!var8) {
            Files.deleteIfExists(var7);
         }
      }
   }

   class="kw">public UpdateService.DownloadTask performBootstrapInstall(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2) {
      CompletableFuture var3 = new CompletableFuture<>();
      Thread var4 = new Thread(() -> {
         try {
            var3.complete(this.performBootstrapInstallSync(var1, var2));
         } catch (CancellationException var5) {
            var3.completeExceptionally(var5);
         } catch (InterruptedException var6) {
            Thread.currentThread().interrupt();
            var3.completeExceptionally(new CancellationException("Bootstrap install interrupted"));
         } catch (Exception var7) {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var7)).log("Error performing bootstrap install");
            var3.complete(false);
         }
      }, "BootstrapInstall");
      var4.setDaemon(true);
      var4.start();
      class="kw">return new UpdateService.DownloadTask(var3, var4);
   }

   class="kw">private boolean performBootstrapInstallSync(@Nonnull UpdateService.VersionManifest var1, @Nullable UpdateService.ProgressCallback var2) class="kw">throws IOException, InterruptedException {
      Path var3 = this.downloadAndVerifyZip(var1, var2);
      if (var3 == null) {
         class="kw">return false;
      }

      try {
         Path var4 = Path.of(".").toAbsolutePath().normalize();
         if (Thread.currentThread().isInterrupted()) {
            throw new CancellationException("Bootstrap install cancelled");
         }

         FileUtil.extractZip(var3, var4);
         if (!System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win")) {
            Path var5 = var4.resolve("start.sh");
            if (Files.exists(var5) && !var5.toFile().setExecutable(true, false)) {
               LOGGER.at(Level.WARNING).log("Failed to set executable bit on %s", var5);
            }
         }

         Path var10 = var4.resolve("Server");
         if (Files.isDirectory(var10)) {
            Path var6 = var4.resolve("auth.enc");
            moveIntoServerDir(var6, var10);
            moveIntoServerDir(EncryptedAuthCredentialStore.keyFilePath(var6), var10);
            moveIntoServerDir(var4.resolve(HytaleServerConfig.PATH), var10);
         }

         deleteRunningJarIfPossible(var4);
         LOGGER.at(Level.INFO).log("Bootstrap install %s extracted to %s", var1.version, var4);
         class="kw">return true;
      } class="kw">finally {
         Files.deleteIfExists(var3);
      }
   }

   class="kw">private class="kw">static void moveIntoServerDir(@Nonnull Path var0, @Nonnull Path var1) {
      if (Files.exists(var0)) {
         Path var2 = var1.resolve(var0.getFileName());

         try {
            Files.move(var0, var2, StandardCopyOption.REPLACE_EXISTING);
            LOGGER.at(Level.INFO).log("Moved %s to %s", var0.getFileName(), var2);
         } catch (IOException var4) {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to move %s into Server/", var0.getFileName());
         }
      }
   }

   class="kw">private class="kw">static void deleteRunningJarIfPossible(@Nonnull Path var0) {
      if (ManifestUtil.isJar()) {
         try {
            CodeSource var1 = UpdateService.class.getProtectionDomain().getCodeSource();
            if (var1 == null) {
               class="kw">return;
            }

            Path var2 = Path.of(var1.getLocation().toURI());
            if (!var2.getParent().toAbsolutePath().normalize().equals(var0)) {
               LOGGER.at(Level.INFO).log("Installer JAR %s is outside cwd %s - skipping cleanup", var2, var0);
               class="kw">return;
            }

            Files.delete(var2);
            LOGGER.at(Level.INFO).log("Deleted installer JAR %s", var2);
         } catch (AccessDeniedException var3) {
            LOGGER.at(Level.INFO).log("Could not delete installer JAR (Windows locks the running JAR) - delete it manually after shutdown");
         } catch (Exception var4) {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to delete installer JAR");
         }
      }
   }

   @Nullable
   class="kw">private String getSignedUrl(String var1, String var2) class="kw">throws IOException, InterruptedException {
      String var3 = this.accountDataUrl + "/game-assets/" + var2;
      HttpRequest var4 = HttpRequest.newBuilder()
         .uri(URI.create(var3))
         .header("Accept", "application/json")
         .header("Authorization", "Bearer " + var1)
         .header("User-Agent", AuthConfig.USER_AGENT)
         .timeout(REQUEST_TIMEOUT)
         .GET()
         .build();
      HttpResponse var5 = this.httpClient.send(var4, BodyHandlers.ofString());
      if (var5.statusCode() != 200) {
         LOGGER.at(Level.WARNING).log("Failed to get signed URL: HTTP %d - %s", var5.statusCode(), var5.body());
         class="kw">return null;
      } else {
         UpdateService.SignedUrlResponse var6 = UpdateService.SignedUrlResponse.CODEC
            .decodeJson(new RawJsonReader(var5.body().toCharArray()), EmptyExtraInfo.EMPTY);
         class="kw">return var6 != null ? var6.url : null;
      }
   }

   @Nonnull
   class="kw">public class="kw">static String getEffectivePatchline() {
      UpdateConfig var0 = HytaleServer.get().getConfig().getUpdateConfig();
      String var1 = var0.getPatchline();
      if (var1 != null && !var1.isEmpty()) {
         class="kw">return var1;
      }

      var1 = ManifestUtil.getPatchline();
      class="kw">return var1 != null ? var1 : "release";
   }

   class="kw">public class="kw">static boolean isValidUpdateLayout() {
      Path var0 = Path.of("..").toAbsolutePath();
      class="kw">return Files.exists(var0.resolve("Assets.zip")) && (Files.exists(var0.resolve("start.sh")) || Files.exists(var0.resolve("start.bat")));
   }

   @Nonnull
   class="kw">public class="kw">static Path getStagingDir() {
      class="kw">return STAGING_DIR;
   }

   @Nonnull
   class="kw">public class="kw">static Path getBackupDir() {
      class="kw">return BACKUP_DIR;
   }

   @Nullable
   class="kw">public class="kw">static String getStagedVersion() {
      Path var0 = STAGING_DIR.resolve("Server").resolve("HytaleServer.jar");
      class="kw">return !Files.exists(var0) ? null : readVersionFromJar(var0);
   }

   class="kw">public class="kw">static boolean deleteStagedUpdate() {
      class="kw">return safeDeleteUpdaterDir(STAGING_DIR, "staging");
   }

   class="kw">public class="kw">static boolean deleteBackupDir() {
      class="kw">return safeDeleteUpdaterDir(BACKUP_DIR, "backup");
   }

   class="kw">private class="kw">static boolean clearStagingDir(@Nonnull Path var0) {
      if (!Files.exists(var0)) {
         class="kw">return true;
      }

      if (var0.toAbsolutePath().normalize().equals(STAGING_DIR.toAbsolutePath().normalize())) {
         class="kw">return deleteStagedUpdate();
      }

      try {
         FileUtil.deleteDirectory(var0);
         class="kw">return true;
      } catch (IOException var2) {
         LOGGER.at(Level.WARNING).log("Failed to delete staging dir %s: %s", var0, var2.getMessage());
         class="kw">return false;
      }
   }

   class="kw">private class="kw">static boolean safeDeleteUpdaterDir(Path var0, String var1) {
      try {
         if (!Files.exists(var0)) {
            class="kw">return true;
         } else {
            Path var2 = var0.toAbsolutePath().normalize();
            Path var3 = var2.getParent();
            if (var3 == null || !var3.getFileName().toString().equals("updater")) {
               LOGGER.at(Level.SEVERE).log("Refusing to delete %s - not within updater/ directory", var2);
               class="kw">return false;
            } else if (!var2.getFileName().toString().equals(var1)) {
               LOGGER.at(Level.SEVERE).log("Refusing to delete %s - unexpected directory name", var2);
               class="kw">return false;
            } else {
               FileUtil.deleteDirectory(var0);
               class="kw">return true;
            }
         }
      } catch (IOException var4) {
         LOGGER.at(Level.WARNING).log("Failed to delete %s: %s", var0, var4.getMessage());
         class="kw">return false;
      }
   }

   @Nullable
   class="kw">public class="kw">static String readVersionFromJar(@Nonnull Path var0) {
      try (JarFile var1 = new JarFile(var0.toFile())) {
         Manifest var2 = var1.getManifest();
         if (var2 == null) {
            class="kw">return null;
         }

         Attributes var3 = var2.getMainAttributes();
         String var4 = var3.getValue("Implementation-Vendor-Id");
         class="kw">return !"com.hypixel.hytale".equals(var4) ? null : var3.getValue("Implementation-Version");
      } catch (IOException var8) {
         LOGGER.at(Level.WARNING).log("Failed to read version from JAR: %s", var8.getMessage());
         class="kw">return null;
      }
   }

   @Nullable
   class="kw">public UpdateService.PatchlineSummary[] fetchPatchlines(@Nonnull String var1) class="kw">throws IOException, InterruptedException {
      String var2 = this.accountDataUrl + "/my-account/get-patchlines";
      HttpRequest var3 = HttpRequest.newBuilder()
         .uri(URI.create(var2))
         .header("Accept", "application/json")
         .header("Authorization", "Bearer " + var1)
         .header("User-Agent", AuthConfig.USER_AGENT)
         .timeout(REQUEST_TIMEOUT)
         .GET()
         .build();
      HttpResponse var4 = this.httpClient.send(var3, BodyHandlers.ofString());
      if (var4.statusCode() != 200) {
         LOGGER.at(Level.WARNING).log("Failed to fetch patchlines: HTTP %d", var4.statusCode());
         class="kw">return null;
      } else {
         UpdateService.GetPatchlinesResponse var5 = UpdateService.GetPatchlinesResponse.CODEC
            .decodeJson(new RawJsonReader(var4.body().toCharArray()), EmptyExtraInfo.EMPTY);
         class="kw">return var5 != null ? var5.patchlines : null;
      }
   }

   class="kw">private class="kw">static <T> KeyedCodec<T> externalKey(String var0, Codec<T> var1) {
      class="kw">return new KeyedCodec<>(var0, var1, false, true);
   }

   class="kw">public record DownloadTask(CompletableFuture<Boolean> future, Thread thread) {
      class="kw">public DownloadTask {
      }
   }

   class="kw">public class="kw">static class GetPatchlinesResponse {
      class="kw">public UpdateService.PatchlineSummary[] patchlines;
      class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.GetPatchlinesResponse> CODEC = BuilderCodec.builder(
            UpdateService.GetPatchlinesResponse.class, UpdateService.GetPatchlinesResponse::new
         )
         .append(
            UpdateService.externalKey("patchlines", new ArrayCodec<>(UpdateService.PatchlineSummary.CODEC, UpdateService.PatchlineSummary[]::new)),
            (var0, var1) -> var0.patchlines = var1,
            var0 -> var0.patchlines
         )
         .add()
         .build();

      class="kw">public GetPatchlinesResponse() {
      }
   }

   class="kw">public class="kw">static class PatchlineSummary {
      class="kw">public String name;
      class="kw">public long expiresAt;
      class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.PatchlineSummary> CODEC = BuilderCodec.builder(
            UpdateService.PatchlineSummary.class, UpdateService.PatchlineSummary::new
         )
         .append(UpdateService.externalKey("name", Codec.STRING), (var0, var1) -> var0.name = var1, var0 -> var0.name)
         .add()
         .append(UpdateService.externalKey("expiresAt", Codec.LONG), (var0, var1) -> var0.expiresAt = var1, var0 -> var0.expiresAt)
         .add()
         .build();

      class="kw">public PatchlineSummary() {
      }
   }

   @FunctionalInterface
   class="kw">public class="kw">interface ProgressCallback {
      void onProgress(int var1, long var2, long var4);
   }

   class="kw">private class="kw">static class SignedUrlResponse {
      class="kw">public String url;
      class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.SignedUrlResponse> CODEC = BuilderCodec.builder(
            UpdateService.SignedUrlResponse.class, UpdateService.SignedUrlResponse::new
         )
         .append(UpdateService.externalKey("url", Codec.STRING), (var0, var1) -> var0.url = var1, var0 -> var0.url)
         .add()
         .build();

      class="kw">private SignedUrlResponse() {
      }
   }

   class="kw">public class="kw">static class VersionManifest {
      class="kw">public String version;
      class="kw">public String downloadUrl;
      class="kw">public String sha256;
      class="kw">public class="kw">static class="kw">final BuilderCodec<UpdateService.VersionManifest> CODEC = BuilderCodec.builder(
            UpdateService.VersionManifest.class, UpdateService.VersionManifest::new
         )
         .append(UpdateService.externalKey("version", Codec.STRING), (var0, var1) -> var0.version = var1, var0 -> var0.version)
         .add()
         .append(UpdateService.externalKey("download_url", Codec.STRING), (var0, var1) -> var0.downloadUrl = var1, var0 -> var0.downloadUrl)
         .add()
         .append(UpdateService.externalKey("sha256", Codec.STRING), (var0, var1) -> var0.sha256 = var1, var0 -> var0.sha256)
         .add()
         .build();

      class="kw">public VersionManifest() {
      }
   }
}