The .ggx format
What is inside a .ggx file, how it is built, how to open and read it with common tools, its size limits and what the format number guarantees.
DescribesFormat 1, API 1 For everyone
On this page
A Green Games game is shared as a single .ggx file. It is not an executable and not an installer: it is a JSON document compressed with gzip. You can open and read it with tools you already have.
What is inside#
A .ggx is an object called the package, written as JSON and compressed with gzip. The package has these fields (they are defined in packages/sdk/src/formato.ts of the Green Games repository). The field names are in Spanish:
| Field | Type | What it is |
|---|---|---|
gbx | 1 | Mark of the container. Always 1. |
manifiesto | object | The content of juego.json: name, difficulties, controls, tables, texts. |
escenas | object | The scenes, by id: each escenas/<id>.json. |
script | text | The joined script: all the game's code in a single text of classic JavaScript, which uses the global greengames object. |
recursos | object | The files of recursos/, by path. Each one with its tipo (MIME) and its datos in base64. |
bot | object, optional | The bot script: pruebas/bot.json. |
laminas | object, optional | The sprites, by id: each laminas/<id>.json. They are the pixel drawings of pieces, tiles and characters. |
guiones | object, optional | Additional bot scripts (pruebas/bot-<nombre>.json), by name. |
modelos | object, optional | The 3D models, by id: each modelos/<id>.glb in base64, with its collision from modelos/<id>.json. Since version 1.2. |
texturas | object, optional | The images of texturas/, by id, with their type and their data in base64. Since version 1.2. |
sonidos | object, optional | The recorded sounds of sonidos/, by id, in base64. Since version 1.2. |
creditos | list, optional | The content of creditos.json: author, license and origin of each model, texture and sound. Since version 1.2. |
That is all. Inside a .ggx there is nothing else: no programs, no libraries, no engine. The engine is provided by whoever opens the file. Models, images and sounds travel as data, in base64, and a model cannot carry code.
laminas and guiones were added after the first version of the contract, and modelos, texturas, sonidos and creditos with version 1.2. A package that does not use them does not carry them. The editor's workshop documents (taller/) never go in.
Two things change between the game's folder and the package:
- The script is joined and converted. If you wrote TypeScript in several files, inside the
.ggxthere is a single text of JavaScript. The lineimport { ... } from '@greengames/sdk'disappears: the services come from thegreengamesglobal. - The resources are encoded in base64, to fit inside the JSON. That is why a 25 KB cover takes up about 34 KB of text before compression.
How it is built#
| With | How |
|---|---|
| The editor | The Export game button. With validation errors, it does not export. See Game card and export. |
The greengames tool | greengames empaquetar mi-juego -o mi-juego.ggx. It validates first; with errors, it writes nothing. See The greengames tool. |
In both cases the path is the same: read the folder, join the script, validate, convert the package to JSON and compress it with gzip.
How to inspect it#
Since it is gzip with JSON inside, gunzip is enough and, to read it comfortably, Node. The commands in this section were run on the .ggx that greengames empaquetar builds for the example game patio-de-monedas, the one from the editor tutorial. The file weighs 24,783 bytes. The output shown is real; the game's own names and texts are in Spanish.
See that it is a .ggx#
A .ggx starts with the two gzip bytes, 1f 8b:
head -c 2 patio-de-monedas.ggx | od -An -tx1 1f 8bSee how much it takes up decompressed#
gunzip -c patio-de-monedas.ggx | wc -c4080324,783 bytes compressed, 40,803 decompressed. Most of it is the cover.
See the beginning#
gunzip -c patio-de-monedas.ggx | head -c 300{"gbx":1,"manifiesto":{"$schema":"../../packages/sdk/esquemas/juego.schema.json","formato":1,"id":"patio-de-monedas","version":"0.1.0","nombre":{"es":"Patio de monedas","en":"Coin Yard"},"descripcion":{"es":"Junta las cinco monedas del patio y llega a la meta antes de que se acabe el tiempo. CuidadoSee the fields#
With Node, one line summarizes each field of the package:
gunzip -c patio-de-monedas.ggx | node -e 'const p = JSON.parse(require("fs").readFileSync(0, "utf8")); for (const [k, v] of Object.entries(p)) console.log(k.padEnd(11), typeof v === "string" ? v.length + " caracteres" : typeof v === "object" ? Object.keys(v).join(", ") : v);'gbx 1
manifiesto $schema, formato, id, version, nombre, descripcion, autor, portada, jugadores, controles, etiquetas, dificultades, dificultadInicial, puntajes, escenaInicial, script, textos
escenas principal
script 2231 caracteres
recursos recursos/portada.jpg
bot $schema, formato, tiempoMaximo, esperado, pasosRead the script#
The thing most worth looking at before opening someone else's game is its script. It is in plain text, not obfuscated:
gunzip -c patio-de-monedas.ggx | node -e 'const p = JSON.parse(require("fs").readFileSync(0, "utf8")); console.log(p.script);'"use strict";
(() => {
// greengames:sdk
var g = greengames;
var juego = g.juego;
var mundo = g.mundo;
var jugadores = g.jugadores;
var interfaz = g.interfaz;
var sonido = g.sonido;
var progreso = g.progreso;
var puntajes = g.puntajes;
var azar = g.azar;
var reloj = g.reloj;
var actores = g.actores;
var camara = g.camara;
// games/patio-de-monedas/scripts/principal.ts
var SEGUNDOS = [90, 60, 45][Math.max(0, juego.nivelDificultad)];
var cronometro;
juego.alIniciar(() => {
cronometro = interfaz.cronometro("tiempo", { cuentaRegresiva: SEGUNDOS, alTerminar: () => terminar("derrota") });
cronometro.iniciar();The first lines are the preamble added by the tool that joined the script: it takes the services from the greengames global. This package names all eleven services. A package exported by editor 0.1.0 named nine: it had no actores or camara. Packages made up to version 0.2.0 name that global object by its previous name, which the room still provides: they run unchanged.
See the resources#
gunzip -c patio-de-monedas.ggx | node -e 'const p = JSON.parse(require("fs").readFileSync(0, "utf8")); for (const [ruta, r] of Object.entries(p.recursos)) console.log(ruta, r.tipo, Math.round(r.datos.length * 3 / 4) + " bytes");'recursos/portada.jpg image/jpeg 25692 bytesWhat happens when a .ggx is opened#
Whoever opens the file (the portal, the launcher or the editor) always does the same thing, in this order:
- It checks that the file does not weigh more than 5 MB.
- It checks that it starts with the gzip bytes. If not: "El archivo no es un .ggx" (the file is not a .ggx).
- It decompresses it, cutting off if it goes over 20 MB. A small file that inflates without end never gets to take up memory.
- It reads the JSON and checks that
gbxis 1. - It validates the whole package with the same rules that
greengames validaruses. An invalid package is not played: it is rejected with its reasons.
The launcher validates twice before saving an imported file, and saves the SHA-256 hash of the file to check it every time it opens it.
Size limits#
| What | Maximum |
|---|---|
The .ggx file | 5 MB |
| The decompressed package | 20 MB |
| The joined script | 512 KB |
| One resource | 2 MB |
| Scenes | 16 |
| Models, textures and sounds, added up | 12 MB |
The games in the catalog are far from those caps: packaged with the tool, Circuito Luz Verde weighs 9.0 KB and Semáforo 21.5 KB. The example on this page weighs 24 KB because its cover is a 25 KB JPG image.
All the other caps: Limits.
Compatibility#
Two numbers identify the contract: formato: 1 in the manifest, the scenes and the bot script, and greengames.version === 1 in the scripting API.
- Changes within format 1 are additive: new properties and functions, always optional. A game made earlier keeps validating and running the same.
- A change that breaks existing games requires
formato: 2, and the game player will keep opening format 1 packages.
Being additive has a practical consequence: a format 1 .ggx can use functions that an older game player does not have yet. It happens with launcher 0.1.0, which ships the first version of the contract: a script can call a service that launcher does not know. And it happens with launcher 0.2.0, which ships the eleven services but not what version 1.1 added: the cube terrain, the pointer and the hotbar. And it happens with launcher 0.3.0, which does not ship the models, textures and recorded sounds of version 1.2. The portal, launcher 0.4.0 and editor 0.4.0 ship the full contract. What was added: Availability.
Unknown properties are not accepted: a key the format does not define is a validator error, not something that gets ignored.
Next#
- Import a .ggx: open in the launcher a game someone gave you.
- Package structure: the folder the file comes from.
- Isolation: why opening someone else's
.ggxdoes not put your computer at risk.