Add Linux ARM64 preset, Raspberry Pi 5 docs and remote zip fetcher
- export_presets.cfg: new "Linux ARM64" preset (arch arm64, embedded PCK, ETC2/ASTC textures, S3TC/BPTC off since the Pi 5 V3D driver lacks it). - tools/zipfetch.py: pull a single member out of the remote export-templates tpz with HTTP Range requests instead of downloading the whole 1.28 GB. - docs/BUILD-RASPBERRY-PI.md: reproducible procedure, Pi 5 renderer expectations (GLES3 fallback, Forward+ downgraded to Mobile) and the pitfalls verified on the field. Verified: arm64 export produced (74.094.536 B, PCK embedded at the end of the official template); the x86_64 export from the same tree runs without missing resources.
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
# Build standalone per Raspberry Pi 5 (arm64)
|
||||||
|
|
||||||
|
Stato: **procedura verificata sulla generazione del binario** (export reale prodotto e
|
||||||
|
validato); la prova di esecuzione va fatta sul Pi 5, che non è questa macchina.
|
||||||
|
|
||||||
|
Artefatto prodotto il 2026-10-24 con Godot 4.7.1.stable (a13da4feb), commit `4af402f8`
|
||||||
|
(tag `v1.0.0-kiosk`).
|
||||||
|
|
||||||
|
## Esito
|
||||||
|
|
||||||
|
| Voce | Valore |
|
||||||
|
| --- | --- |
|
||||||
|
| File | `build/linux-arm64/work_death_balance.arm64` |
|
||||||
|
| Tipo | ELF 64-bit LSB, ARM aarch64, interpreter `/lib/ld-linux-aarch64.so.1` |
|
||||||
|
| Dimensione | 74.094.536 byte (≈70,7 MiB) = template 67.046.672 + PCK 7.047.864 |
|
||||||
|
| PCK | incorporato (`embed_pck=true`), magic `GDPC` a offset 67.046.672, pack_version 4 |
|
||||||
|
| Texture | `etc2_astc=true`, `s3tc_bptc=false` (v3d/V3D 7.1 non supporta S3TC/BPTC) |
|
||||||
|
| Addon nativo | non incluso: il preset esclude `addons/*`; nessun `.so` arm64 necessario |
|
||||||
|
|
||||||
|
## Perché serve poco lavoro
|
||||||
|
|
||||||
|
- I **template ufficiali Godot includono Linux arm64** (le architetture ufficiali sono
|
||||||
|
x86_32, x86_64, arm32, arm64). L'export è un **cross-export**: si esegue su x86_64,
|
||||||
|
non serve toolchain aarch64 né compilare il motore.
|
||||||
|
- La **GDExtension `goldsrc` non è nel gioco esportato**: `export_presets.cfg` ha
|
||||||
|
`exclude_filter="addons/*"` e l'unico riferimento nel codice è un commento in
|
||||||
|
`scripts/grenade_explosion.gd:3`. I modelli sono già `.scn` pre-estratti.
|
||||||
|
(Nel repository i binari dell'addon sono solo x86_64: irrilevante per l'export.)
|
||||||
|
|
||||||
|
## Procedura riproducibile
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Godot 4.7.1 editor (x86_64) in ~/.local/opt/godot-4.7.1/
|
||||||
|
# https://github.com/godotengine/godot-builds/releases/tag/4.7.1-stable
|
||||||
|
# (solo il template che serve: templates/linux_release.arm64 dal tpz)
|
||||||
|
|
||||||
|
# 2. template installati in:
|
||||||
|
# ~/.local/share/godot/export_templates/4.7.1.stable/{linux_release.arm64,version.txt}
|
||||||
|
# version.txt deve contenere esattamente: 4.7.1.stable
|
||||||
|
|
||||||
|
# 3. import del progetto (genera .godot/), una volta sola
|
||||||
|
godot --headless --path . --import
|
||||||
|
|
||||||
|
# 4. export
|
||||||
|
godot --headless --path . --export-release "Linux ARM64" \
|
||||||
|
build/linux-arm64/work_death_balance.arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
Il preset `Linux ARM64` è in `export_presets.cfg` (`[preset.2]`) ed è identico a
|
||||||
|
`Linux` tranne per: `binary_format/architecture="arm64"`,
|
||||||
|
`texture_format/etc2_astc=true`, `texture_format/s3tc_bptc=false`,
|
||||||
|
`export_path="build/linux-arm64/work_death_balance.arm64"`.
|
||||||
|
|
||||||
|
### Scaricare solo il template che serve (1,28 GB → 27 MB)
|
||||||
|
|
||||||
|
Il `tpz` dei template è uno zip: `tools/zipfetch.py` legge EOCD e central directory
|
||||||
|
via HTTP Range e scarica il solo membro richiesto. Comodo su connessioni lente.
|
||||||
|
|
||||||
|
## Sul Pi 5: aspettative e leve
|
||||||
|
|
||||||
|
GPU: Broadcom VideoCore VII (V3D 7.1). Mesa espone **OpenGL 3.1 desktop + OpenGL ES 3.1**
|
||||||
|
(v3d) e **Vulkan 1.2/1.3 conformante** (v3dv).
|
||||||
|
|
||||||
|
- Il progetto è `forward_plus`. Su Vulkan il Forward+ **viene declassato automaticamente
|
||||||
|
a Mobile** ("Platform supports less than 48 textures per stage … Defaulting to Mobile
|
||||||
|
renderer"): è il comportamento atteso, non un errore.
|
||||||
|
- Con `--rendering-driver opengl3` il driver desktop GL 3.3 non è disponibile su V3D e
|
||||||
|
Godot **ripiega su OpenGL ES 3.1**: per questo servono le texture ETC2 (già abilitate
|
||||||
|
nel preset arm64).
|
||||||
|
- Nessuna aspettativa da 1080p: il target realistico è 960x540 (o 640x360) con
|
||||||
|
`--max-fps 30`. Da misurare sul campo con `--print-fps` se compilato in debug.
|
||||||
|
|
||||||
|
Avvio consigliato (script fornito: `build/linux-arm64/run-arm64.sh`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./run-arm64.sh # GL/GLES3, 960x540, 30 fps, classifica attiva
|
||||||
|
WDB_MODE=vulkan ./run-arm64.sh # percorso Vulkan (Mobile)
|
||||||
|
WDB_RES=640x360 ./run-arm64.sh # se non regge
|
||||||
|
WDB_API= ./run-arm64.sh # senza classifica
|
||||||
|
```
|
||||||
|
|
||||||
|
## Insidie verificate
|
||||||
|
|
||||||
|
1. **`--api=` solo dopo `--`**: `leaderboard.gd` legge `OS.get_cmdline_user_args()`.
|
||||||
|
Senza separatore il gioco stampa "Classifica disattivata".
|
||||||
|
2. **`curl -C -` senza `-L`** su GitHub scarica il corpo del 302 (0 byte) e sembra
|
||||||
|
"riuscito": il download resta fermo per sempre. Serve `-L`.
|
||||||
|
3. **Resume fragile**: la CDN può restituire 206 anche dopo uno stallo; con
|
||||||
|
`--speed-time 30 --speed-limit 20000` si riprende senza perdere i byte già scaricati.
|
||||||
|
4. **L'addon `goldsrc` non si carica su host con glibc più vecchia del build**
|
||||||
|
(`GLIBC_2.43 not found`): errore cosmetico in import/export, non incide sul PCK
|
||||||
|
perché `addons/*` è escluso.
|
||||||
|
5. **Verifica del PCK**: la controprova x86_64 esportata dallo stesso clone si avvia e
|
||||||
|
gira senza risorse mancanti; per l'arm64 l'unica verifica possibile qui è strutturale
|
||||||
|
(`file`, offset/size del PCK). La prova reale richiede il Pi.
|
||||||
@@ -54,3 +54,31 @@ binary_format/architecture="x86_64"
|
|||||||
texture_format/s3tc_bptc=true
|
texture_format/s3tc_bptc=true
|
||||||
texture_format/etc2_astc=false
|
texture_format/etc2_astc=false
|
||||||
ssh_remote_deploy/enabled=false
|
ssh_remote_deploy/enabled=false
|
||||||
|
|
||||||
|
[preset.2]
|
||||||
|
|
||||||
|
name="Linux ARM64"
|
||||||
|
platform="Linux"
|
||||||
|
runnable=true
|
||||||
|
advanced_options=true
|
||||||
|
dedicated_server=false
|
||||||
|
custom_features=""
|
||||||
|
export_filter="all_resources"
|
||||||
|
include_filter=""
|
||||||
|
exclude_filter="addons/*,tools/*,docs/*,build/*,assets/**/*.mdl,assets/half_life_manifest.json"
|
||||||
|
export_path="build/linux-arm64/work_death_balance.arm64"
|
||||||
|
patches=PackedStringArray()
|
||||||
|
encrypt_pck=false
|
||||||
|
encrypt_directory=false
|
||||||
|
script_export_mode=2
|
||||||
|
|
||||||
|
[preset.2.options]
|
||||||
|
|
||||||
|
custom_template/release=""
|
||||||
|
custom_template/debug=""
|
||||||
|
debug/export_console_wrapper=0
|
||||||
|
binary_format/embed_pck=true
|
||||||
|
binary_format/architecture="arm64"
|
||||||
|
texture_format/s3tc_bptc=false
|
||||||
|
texture_format/etc2_astc=true
|
||||||
|
ssh_remote_deploy/enabled=false
|
||||||
|
|||||||
Executable
+102
@@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Estrae singoli file da uno ZIP remoto via HTTP Range (senza scaricare 1,28 GB).
|
||||||
|
|
||||||
|
Il tpz dei template Godot e' uno zip: serve solo templates/linux_release.arm64.
|
||||||
|
Legge EOCD + central directory con Range, poi scarica solo il membro richiesto.
|
||||||
|
"""
|
||||||
|
import binascii
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
URL = ("https://github.com/godotengine/godot-builds/releases/download/"
|
||||||
|
"4.7.1-stable/Godot_v4.7.1-stable_export_templates.tpz")
|
||||||
|
|
||||||
|
|
||||||
|
def get_range(url, start, end=None):
|
||||||
|
if end is None:
|
||||||
|
rng = f"bytes={start}-"
|
||||||
|
else:
|
||||||
|
rng = f"bytes={start}-{end}"
|
||||||
|
req = urllib.request.Request(url, headers={"Range": rng})
|
||||||
|
with urllib.request.urlopen(req, timeout=90) as r:
|
||||||
|
return r.read(), r.status
|
||||||
|
|
||||||
|
|
||||||
|
def main(wanted, dest_dir):
|
||||||
|
# 1. dimensione totale
|
||||||
|
req = urllib.request.Request(URL, method="HEAD")
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as r:
|
||||||
|
total = int(r.headers["Content-Length"])
|
||||||
|
print(f"zip remoto: {total} byte")
|
||||||
|
|
||||||
|
# 2. EOCD negli ultimi 64 KiB
|
||||||
|
tail_len = min(65536, total)
|
||||||
|
tail, _ = get_range(URL, total - tail_len)
|
||||||
|
pos = tail.rfind(b"PK\x05\x06")
|
||||||
|
if pos < 0:
|
||||||
|
sys.exit("EOCD non trovato (zip64?)")
|
||||||
|
(_, _, _, _, n_entries, cd_size, cd_off, _) = struct.unpack(
|
||||||
|
"<IHHHHIIH", tail[pos:pos + 22])
|
||||||
|
print(f"voci: {n_entries}, central directory: {cd_off}+{cd_size}")
|
||||||
|
|
||||||
|
# 3. central directory
|
||||||
|
cd, _ = get_range(URL, cd_off, cd_off + cd_size - 1)
|
||||||
|
|
||||||
|
entries = {}
|
||||||
|
i = 0
|
||||||
|
while i + 46 <= len(cd) and cd[i:i + 4] == b"PK\x01\x02":
|
||||||
|
(method, csize, usize, name_len, extra_len, comment_len,
|
||||||
|
local_off) = (struct.unpack("<H", cd[i + 10:i + 12])[0],
|
||||||
|
struct.unpack("<I", cd[i + 20:i + 24])[0],
|
||||||
|
struct.unpack("<I", cd[i + 24:i + 28])[0],
|
||||||
|
struct.unpack("<H", cd[i + 28:i + 30])[0],
|
||||||
|
struct.unpack("<H", cd[i + 30:i + 32])[0],
|
||||||
|
struct.unpack("<H", cd[i + 32:i + 34])[0],
|
||||||
|
struct.unpack("<I", cd[i + 42:i + 46])[0])
|
||||||
|
name = cd[i + 46:i + 46 + name_len].decode("utf-8", "replace")
|
||||||
|
entries[name] = (method, csize, usize, local_off)
|
||||||
|
i += 46 + name_len + extra_len + comment_len
|
||||||
|
|
||||||
|
print(f"membri indicizzati: {len(entries)}")
|
||||||
|
if wanted not in entries:
|
||||||
|
candidati = [n for n in entries if "arm64" in n]
|
||||||
|
sys.exit(f"'{wanted}' non presente. Candidati arm64: {candidati}")
|
||||||
|
|
||||||
|
method, csize, usize, local_off = entries[wanted]
|
||||||
|
print(f"{wanted}: metodo={method} compresso={csize} originale={usize}")
|
||||||
|
|
||||||
|
# 4. header locale per sapere dove iniziano davvero i dati
|
||||||
|
lh, _ = get_range(URL, local_off, local_off + 29)
|
||||||
|
if lh[:4] != b"PK\x03\x04":
|
||||||
|
sys.exit("header locale inatteso")
|
||||||
|
name_len = struct.unpack("<H", lh[26:28])[0]
|
||||||
|
extra_len = struct.unpack("<H", lh[28:30])[0]
|
||||||
|
data_start = local_off + 30 + name_len + extra_len
|
||||||
|
|
||||||
|
# 5. dati compressi del solo membro
|
||||||
|
print(f"scarico {csize} byte da offset {data_start} ...")
|
||||||
|
data, status = get_range(URL, data_start, data_start + csize - 1)
|
||||||
|
print(f"ricevuti {len(data)} byte (http {status})")
|
||||||
|
|
||||||
|
if method == 8:
|
||||||
|
raw = zlib.decompress(data, -15)
|
||||||
|
elif method == 0:
|
||||||
|
raw = data
|
||||||
|
else:
|
||||||
|
sys.exit(f"metodo di compressione non gestito: {method}")
|
||||||
|
|
||||||
|
print(f"decompresso: {len(raw)} byte (atteso {usize})")
|
||||||
|
if len(raw) != usize:
|
||||||
|
sys.exit("dimensione non corrispondente")
|
||||||
|
out = f"{dest_dir}/{wanted.split('/')[-1]}"
|
||||||
|
with open(out, "wb") as fh:
|
||||||
|
fh.write(raw)
|
||||||
|
print(f"scritto {out}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
target = sys.argv[1] if len(sys.argv) > 1 else "templates/linux_release.arm64"
|
||||||
|
dest = sys.argv[2] if len(sys.argv) > 2 else "/tmp/godot-dl"
|
||||||
|
main(target, dest)
|
||||||
Reference in New Issue
Block a user