> ## Documentation Index
> Fetch the complete documentation index at: https://chart-spec.enchor.us/llms.txt
> Use this file to discover all available pages before exploring further.

# .sng files

> A song folder packed into one file: its layout, its metadata, and the files it holds.

A `.sng` file holds a whole song folder in one file. The values of the folder's `song.ini` become the `.sng` file's metadata, and its other files are stored inside it:

```text theme={null}
Song Name.sng
  metadata
    name = Song Name
    artist = Artist
    album = Album
    genre = Rock
    year = 2002
    charter = Charter
    song_length = 215000
    diff_guitar = 4
    preview_start_time = 45500
  files
    notes.chart
    song.opus
    guitar.opus
    album.png
```

Clone Hero and YARG read `.sng` files, and play the audio straight from them, without unpacking them. scan-chart doesn't read or write them.

Matthew Sitton and Geo designed the format. Its [specification](https://github.com/mdsitton/SngFileFormat) comes with SngCli, a tool that packs song folders into `.sng` files and unpacks them.

## Where a .sng file goes

* **Required:** a `.sng` file goes where a song folder would, among the song folders, not inside one.
* **Required:** the extension `.sng`, in lowercase. SngCli names the file after the song folder.

## Layout

A `.sng` file is a header, then three parts: the metadata, the file index and the files' data. Every number is little-endian. `M` is the metadata's length, and `L` the file index's:

| Offset | Bytes | Field | Holds |
| - | - | - | - |
| 0 | 6 | Identifier | `53 4E 47 50 4B 47`, the text `SNGPKG`. **Required** |
| 6 | 4 | Version | `1`, as an unsigned 32-bit number. **Required** |
| 10 | 16 | Mask | 16 bytes that mask the files' data |
| 26 | 8 | Metadata length, `M` | The number of bytes after this field up to the file index |
| 34 | 8 | Metadata count | The number of key and value pairs |
| 42 | `M` - 8 | Metadata pairs | Each pair: the key's length as a signed 32-bit number, the key, the value's length the same way, and the value |
| 34 + `M` | 8 | File index length, `L` | The number of bytes after this field up to the file data length |
| 42 + `M` | 8 | File count | The number of files |
| 50 + `M` | `L` - 8 | File entries | Each file: its name's length as one byte, its name, its length, and the offset of its data from the start of the `.sng` file |
| 42 + `M` + `L` | 8 | File data length | The total length of the files' data |
| 50 + `M` + `L` | | File data | Each file's bytes, masked |

Lengths, counts and offsets of 8 bytes are unsigned 64-bit numbers. Keys, values and names are UTF-8, without NUL characters.

* **Required:** the metadata length, the file index length and the file data length each equal the number of bytes their part holds.
* **Required:** the file data starts right after the file data length, holds the files in the order of the file index with nothing between them, and ends the `.sng` file.

### Masking

Each file's data is masked with the 16 bytes of the mask. For byte `i` of a file, counted from 0 at that file's first byte:

```text theme={null}
key[i]  = mask[i % 16] XOR (i AND 0xFF)
byte[i] = masked[i] XOR key[i]
```

* The same operation masks and unmasks a file. The key repeats every 256 bytes, so a scanner can build it as a 256-byte table.
* Any 16 bytes are a valid mask. **Recommended:** random bytes for a new file, and the file's own mask when it's saved again.

The mask keeps programs that don't know the format from reading the files inside as ordinary files.

## Metadata

The metadata is the `[song]` section of a `song.ini` file, as key and value pairs. Its keys are those of [Metadata](/ini/metadata) and [Settings](/ini/settings), and each value is one that `song.ini` can hold, so that the metadata can become a `song.ini` file again.

* `name` is **Required**.
* **Required:** keys in lowercase. Both games match keys exactly.
* **Required:** each key once.
* **Required:** a value of at least one character in every pair. To leave a value unset, leave its key out.
* **Required:** a boolean written as `True` or `False`.
* **Required:** a number written as digits, with an optional `-` before them, and no spaces.
* **Required:** no `;` and no line break in keys and values, and no `=` in keys.
* **Required:** none of the older names of `song.ini` keys, `frets`, `track`, `pro_drum` and `star_power_note`. Write `charter`, `album_track`, `pro_drums` and `multiplier_note`.

**Edge case: the chart's `[Song]` section.** Clone Hero takes nothing from a `.chart` file's `[Song]` section but `Offset`, `PreviewStart` and `PreviewEnd`, and those only when the metadata doesn't set `delay`, `preview_start_time` or `preview_end_time`. So the metadata holds every value the song needs.

## Files

Each file in the index is a file of the song folder. [Files in a song folder](/song-folders) gives their names and rules: one chart, at least one audio file, and one file for each audio name.

* **Required:** names in lowercase. Clone Hero matches names exactly.
* **Required:** each name once, and no two names that differ only in letter case.
* A name is at most 255 bytes of UTF-8, folders included.
* **Required:** no `song.ini` stored: the metadata takes its place.
* **Recommended:** no folders in the names. The format allows a name such as `extras/photo.png`, but games don't read files in folders.

**Edge case: background images in folders.** Clone Hero shows any image whose name contains `background`, folders included, and YARG any whose name starts with `background`. So an image named `backgrounds/1.png` can still appear behind the highway.

**Edge case: long names.** parse-sng, a TypeScript library that reads `.sng` files, reads a name's length as a signed byte, so it fails on a name longer than 127 bytes.

**Edge case: the song's identity.** Clone Hero and YARG tell songs apart by a hash of the chart file alone, in a `.sng` file as in a folder. So packing a folder into a `.sng` file, or unpacking it, doesn't change the song's identity, as long as the chart file doesn't change.

## Making a .sng file

`SngCli encode` packs each song folder in a folder into a `.sng` file, and `SngCli decode` unpacks them. As it packs a folder, SngCli:

* copies every `song.ini` key and value as written, so `pro_drums = 1` stays `1`, and `Name` stays `Name`;
* renames `frets`, `track`, `star_power_note` and `pro_drum` to `charter`, `album_track`, `multiplier_note` and `pro_drums`, and leaves out known keys whose values it can't read;
* writes the names of the files games read in lowercase, and gives each audio file the extension of its content;
* stores files games don't read, unless it's given `--skipUnknown`, and stores the files of a subfolder with the subfolder's name before theirs.

It can also convert the audio to `.opus`. Before packing a folder, write its booleans as `True` or `False` and its keys in lowercase, and remove the files games don't read.

## Deprecated

* **`preview` and `crowd` audio,** as in a song folder: see [Deprecated](/song-folders#deprecated). Clone Hero doesn't play a `preview` file inside a `.sng` file at all.

## Ignored

* **Metadata keys a scanner doesn't read.** An exporter keeps them.
* **Files games don't read,** and files in folders, except background images as above.

## Invalid forms

<Accordion title="A .sng file inside a song folder">
  Clone Hero loads it as a song of its own. YARG doesn't look for songs inside a song folder, so it skips it. A scanner loads it as a song of its own, as Clone Hero does.
</Accordion>

<Accordion title="An extension other than .sng, such as .SNG">
  Neither game finds the file.
</Accordion>

<Accordion title="An identifier other than SNGPKG, or a version other than 1">
  Neither game loads a file with another identifier or another version, and Clone Hero lists a file of another version in its `errors.txt`. A scanner doesn't load the file.
</Accordion>

<Accordion title="Lengths that don't match the bytes used">
  A scanner refuses the file. Readers trust different fields: SngCli and parse-sng read the pairs and entries by their counts, and Clone Hero and YARG read each part by its length, then the pairs or entries inside it by their count. So such a file reads differently in each, or not at all.
</Accordion>

<Accordion title="File data out of order, with gaps, or with bytes after it">
  A scanner finds each file by its offset, as Clone Hero, YARG and SngCli do, and refuses a `.sng` file with a file that runs past its end, as Clone Hero does. parse-sng ignores the offsets, and reads the files one after another from the end of the file data length, so it reads the wrong bytes. YARG fails when it reads a file that runs past the end.
</Accordion>

<Accordion title="One name used twice, or two names that differ only in letter case">
  Clone Hero and YARG don't load a `.sng` file that holds one name twice. YARG makes names lowercase first, so it also doesn't load one that holds `Song.opus` and `song.opus`. Clone Hero reads those as two files. A scanner reads them as Clone Hero does. When SngCli packs two files with one name, it keeps the first.
</Accordion>

<Accordion title="A file name not in lowercase, such as Notes.chart">
  A scanner matches names exactly, so it doesn't find the file, as Clone Hero does. YARG does.
</Accordion>

<Accordion title="A metadata key not in lowercase, such as Name">
  Neither game finds the key, so the song reads as if it weren't there. Without `name`, Clone Hero doesn't list the song.
</Accordion>

<Accordion title="A boolean written as 1 or 0">
  A scanner reads only `True` and `False`, in any letter case, and anything else as not set, as Clone Hero does. So a `.sng` file with `pro_drums = 1` plays without pro drums, while a song folder with the same `song.ini` plays with them. YARG reads `1` as true.
</Accordion>

<Accordion title="A number in another form, such as one with spaces">
  A scanner reads it as Clone Hero does: an optional sign, then digits, within the 32-bit range, with spaces allowed around them, and the key's default for anything else. YARG reads a number from the value's first character, and reads 0 when it isn't a sign or a digit.
</Accordion>

<Accordion title="No name">
  Clone Hero doesn't list the song, even when the `.chart` file's `[Song]` section has a `Name`. YARG uses that `Name`. A scanner takes the name from the metadata alone, never from `[Song]`, as Clone Hero does: see [SongHash](/hashes/songhash#metadata).
</Accordion>

<Accordion title="An empty value">
  Clone Hero reads empty text, or a number's default. YARG reads past the end of an empty value, into the next pair. A scanner reads the key as not set. SngCli never writes one.
</Accordion>

<Accordion title="A key written more than once">
  Both games use the last.
</Accordion>

<Accordion title="A semicolon, a line break or = where the metadata forbids it">
  No game checks for them. The specification forbids them so that the metadata can become a `song.ini` file again.
</Accordion>

<Accordion title="A stored song.ini">
  Games ignore it: the metadata is the only source of the song's values.
</Accordion>

<Accordion title="frets, track, pro_drum or star_power_note in the metadata">
  Clone Hero ignores them. YARG reads them as it does in `song.ini`. A scanner ignores them.
</Accordion>

<Accordion title="Two audio files with one name, such as song.ogg and song.opus">
  Unlike in a song folder, both games play the first of `.opus`, `.ogg`, `.mp3` and `.wav`. As in a song folder, a `.sng` file holds one file for each audio name.
</Accordion>

<Accordion title="No audio file">
  As in a song folder, Clone Hero loads the song without sound, and YARG doesn't load it unless it holds `crowd` audio or a stem in `.aiff`.
</Accordion>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.