yarnballs v0.0.0
yarnballs
Parser and writer for the "A Hat in Time" Nintendo Switch savefile archive.
Installation and Usage
CLI
$ npm install -g yarnballs
$ yarnballs -h
For playing with AHiT saves, the most useful commands are:
## create a new savefile from template
$ yarnballs -C "A Hat in Time GameState"
## update first save slot using a PC game save
$ yarnballs -u "A Hat in Time GameState" /w/DataChanges/HatinTimeGame/SaveData/slot1 save.hat
NodeJS
Install the module with NPM, then require:
const YARNBALLS = require("yarnballs");
Browser
Add a reference to the crc-32 library before referencing the script. The UNPKG cdn hosts scripts:
<script src="https://unpkg.com/crc-32/crc32.js"></script>
<script src="https://unpkg.com/yarnballs/yarnballs.js"></script>
See index.html, live at https://switchjs.com/yarnballs.html
API
YARNBALLS.parse(data)
parses a Uint8Array
, returning an array of entries
YARNBALLS.write(entries)
generates a Uint8Array from the entries.
File Structure
People have explored the format. An example file generated by @leo60228 included a few questionable sizes. Since we were unable to reproduce the issues, they are not documented here, but the code includes logic to handle those cases.
Frame Structure
The file starts with the magic number 0x13 0x22 0xb2 0xe5 0xa8 0x04 0x10 0xdc
After that, a series of entry objects appear in the data. There is no padding between the objects.
The last 4 bytes of the file is a CRC-32 cksum of the magic and entries.
Offset | Length | Desc |
---|---|---|
0 | 8 | Magic Number 0x13 0x22 0xb2 0xe5 0xa8 0x04 0x10 0xdc |
8 | size - 12 | Series of entry objects (defined below) |
-4 | 4 | CRC-32 checksum of Magic + Entries |
Entry Structure
There are two entry types, "files" and "folders", with magic numbers:
Entry Type | Magic Number |
---|---|
File | 0xb1 0xca 0x20 0x97 0xb4 0x81 0x00 0x00 |
Folder | 0x01 0xa4 0x20 0xb2 0xfd 0x41 0x00 0x00 |
Creation / Access / Modify times are stored in the Windows FILETIME
format.
Paths are specified in the UNIX backslash style. Folder names do not include the trailing slash.
Offset | Length | Desc |
---|---|---|
0 | 8 | Entry Magic Number (see table above) |
8 | 8 | Creation Time |
16 | 8 | Access Time |
24 | 8 | Modify Time |
32 | 2 | Length of the Path in bytes (not including null) |
34 | N+1 | Null-terminated Path (ASCII characters) |
Files include additional data after the path:
Offset | Length | Desc |
---|---|---|
0 | 8 | Upper bound on data size (see "Game Details") |
8 | 4 | Magic 0xe9 0xb7 0x12 0x3a |
12 | 12+(n) | Data (n is less than or equal to the upper bound) |
Game Details
The actual game presumes the folder /w/DataChanges/HatinTimeGame
exists.
Subfolders like /w/DataChanges/HatinTimeGame/Config
are explicitly specified.
In test files that we generated, the individual entry data sizes were correct. However, there are test files where this is not the case. No information regarding version numbers are available, so in accordance with Postel's law we just do a linear scan for the next entry.
Credits
- @leo60228 for motivation and exploration
- @sheetjs for CRC-32 library
- @substack for mkdir library
- @tjholowaychuk for CLI flags library
5 years ago