fix(client): synchronize persistent Addressables catalog
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
clientlayout "bd2server/internal/client/layout"
|
clientlayout "bd2server/internal/client/layout"
|
||||||
@@ -27,6 +28,7 @@ func installCurrentLocalCatalog(gameDir, localRoot, bundleVersion string) error
|
|||||||
}
|
}
|
||||||
release := filepath.Join(filepath.Clean(localRoot), "ServerData", "StandaloneWindows64", "HD", bundleVersion)
|
release := filepath.Join(filepath.Clean(localRoot), "ServerData", "StandaloneWindows64", "HD", bundleVersion)
|
||||||
source := filepath.Join(release, "catalog_alpha.json")
|
source := filepath.Join(release, "catalog_alpha.json")
|
||||||
|
sourceHash := filepath.Join(release, "catalog_alpha.hash")
|
||||||
raw, err := readCatalog(source)
|
raw, err := readCatalog(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -41,9 +43,7 @@ func installCurrentLocalCatalog(gameDir, localRoot, bundleVersion string) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read built-in Addressables catalog: %w", err)
|
return fmt.Errorf("read built-in Addressables catalog: %w", err)
|
||||||
}
|
}
|
||||||
if bytes.Equal(current, raw) {
|
if !bytes.Equal(current, raw) {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
backup := filepath.Join(aa, localCatalogBackup)
|
backup := filepath.Join(aa, localCatalogBackup)
|
||||||
if _, err := os.Stat(backup); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(backup); errors.Is(err, os.ErrNotExist) {
|
||||||
if err := writeExclusiveFile(backup, current, 0o600); err != nil {
|
if err := writeExclusiveFile(backup, current, 0o600); err != nil {
|
||||||
@@ -55,6 +55,69 @@ func installCurrentLocalCatalog(gameDir, localRoot, bundleVersion string) error
|
|||||||
if err := replaceCatalog(target, raw); err != nil {
|
if err := replaceCatalog(target, raw); err != nil {
|
||||||
return fmt.Errorf("install current local Addressables catalog: %w", err)
|
return fmt.Errorf("install current local Addressables catalog: %w", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
hash, err := os.ReadFile(sourceHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read local ServerData catalog hash: %w", err)
|
||||||
|
}
|
||||||
|
if len(hash) == 0 || len(hash) > 1024 {
|
||||||
|
return errors.New("local ServerData catalog hash has an invalid size")
|
||||||
|
}
|
||||||
|
cache, err := addressablesCacheDirectory()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return synchronizePersistentCatalog(cache, raw, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addressablesCacheDirectory() (string, error) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("locate user home for Addressables cache: %w", err)
|
||||||
|
}
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
return filepath.Join(home, "AppData", "LocalLow", "Gamfs", "BrownDust II", "com.unity.addressables"), nil
|
||||||
|
case "darwin":
|
||||||
|
return filepath.Join(home, "Library", "Application Support", "Gamfs", "BrownDust II", "com.unity.addressables"), nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("local Brown Dust II resources are unsupported on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func synchronizePersistentCatalog(cache string, catalog, hash []byte) error {
|
||||||
|
if err := os.MkdirAll(cache, 0o700); err != nil {
|
||||||
|
return fmt.Errorf("create persistent Addressables cache: %w", err)
|
||||||
|
}
|
||||||
|
for _, file := range []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
}{
|
||||||
|
{name: "catalog_alpha.json", data: catalog},
|
||||||
|
{name: "catalog_alpha.hash", data: hash},
|
||||||
|
} {
|
||||||
|
target := filepath.Join(cache, file.name)
|
||||||
|
current, err := os.ReadFile(target)
|
||||||
|
if err == nil && bytes.Equal(current, file.data) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("read persistent Addressables %s: %w", file.name, err)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
backup := target + ".bd2-before-local-sync"
|
||||||
|
if _, backupErr := os.Stat(backup); errors.Is(backupErr, os.ErrNotExist) {
|
||||||
|
if backupErr := writeExclusiveFile(backup, current, 0o600); backupErr != nil {
|
||||||
|
return fmt.Errorf("back up persistent Addressables %s: %w", file.name, backupErr)
|
||||||
|
}
|
||||||
|
} else if backupErr != nil {
|
||||||
|
return fmt.Errorf("inspect persistent Addressables %s backup: %w", file.name, backupErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := replaceCatalog(target, file.data); err != nil {
|
||||||
|
return fmt.Errorf("install persistent Addressables %s: %w", file.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,3 +97,37 @@ func TestLocalizeCatalogPreservesMetadataAndCreatesHardLink(t *testing.T) {
|
|||||||
t.Fatal("localized bundle is not a hard link to the selected release")
|
t.Fatal("localized bundle is not a hard link to the selected release")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSynchronizePersistentCatalogBacksUpAndIsIdempotent(t *testing.T) {
|
||||||
|
cache := t.TempDir()
|
||||||
|
oldCatalog := []byte("old catalog")
|
||||||
|
oldHash := []byte("old hash")
|
||||||
|
if err := os.WriteFile(filepath.Join(cache, "catalog_alpha.json"), oldCatalog, 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(cache, "catalog_alpha.hash"), oldHash, 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
newCatalog := []byte("new catalog")
|
||||||
|
newHash := []byte("new hash")
|
||||||
|
if err := synchronizePersistentCatalog(cache, newCatalog, newHash); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := synchronizePersistentCatalog(cache, newCatalog, newHash); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for name, want := range map[string][]byte{
|
||||||
|
"catalog_alpha.json": newCatalog,
|
||||||
|
"catalog_alpha.hash": newHash,
|
||||||
|
"catalog_alpha.json.bd2-before-local-sync": oldCatalog,
|
||||||
|
"catalog_alpha.hash.bd2-before-local-sync": oldHash,
|
||||||
|
} {
|
||||||
|
got, err := os.ReadFile(filepath.Join(cache, name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Fatalf("%s=%q, want %q", name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user