This is the full documentation for Kula Quest.
# Start of Kula Quest documentation
---
url: /index.md
---
# Introduction
Welcome! This website contains documentation derived from countless hours of reverse engineering and research into the PlayStation 1 game **Kula Quest**.

Throughout this wiki, the game will be referred to as **Kula Quest** as it was the original name and was intended to be known as such all over the world [^1], despite most people knowing it as Kula World.
The other titles will be used in cases of version differences as needed.
::: warning Warning
This wiki is still under constant development, so expect a lot of changes!
:::
## Gameplay
Kula Quest is a 3D puzzle-platformer video game developed by [Game Design Sweden AB](https://en.wikipedia.org/wiki/Netbabyworld) and released for the original PlayStation in 1998.
Published by [Sony Computer Entertainment](https://en.wikipedia.org/wiki/Sony_Interactive_Entertainment) in Europe (_Kula World_) and Japan (_Kula Quest_), and by [Psygnosis](https://en.wikipedia.org/wiki/Psygnosis) in North America (_Roll Away_), it casts players as a colorful beach ball navigating through a series of suspended structures filled with collectables and hazards.
The game's core mechanic revolves around **direction gravity**, which changes based on the ball's position, challenging players to think spatially.
## Resources
Several resources are available here, including:
- Tutorials for various tools for modding the game.
- Details for [every known release](/content/releases) and version differences.
- Technical documentation for most of Kula Quest's custom binary [formats](/formats/).
## Credits
This website was created and maintained by the original creators of [Kula Workshop](https://www.kulaworkshop.net/).
[^1]: [Interview with J. Söderqvist](https://kulaquest.pinkgothic.com/storybehind.html#q02), Neike Taika-Tessaro (2003)
---
---
url: /content/main-releases.md
---
---
description: 'Details regarding the main releases of Kula Quest.'
---
# Main Releases
{{ $frontmatter.description }}
This page is still under construction!
**Table of Contents:**
[[toc]]
## Kula World
Kula World was released on **July 10th, 1998** and was published by [Sony Computer Entertainment](https://en.wikipedia.org/wiki/Sony_Interactive_Entertainment).
It remains the most well known version of the game.
The most well known piece of cut content is the **OBJ LEVEL**, a testing level containing every object in the game including unused objects.
This level is present at the end of every theme's main [.PAK](/formats/pak) file:

## Roll Away
Roll Away was released sometime between **November 27th to December 1st, 1998**.
It remains the only known version to have been released in North America, and was published by [Psygnosis](https://en.wikipedia.org/wiki/Psygnosis).
The changes in this version branch off of the changes from the [Kula World Prototype](/content/releases#kula-world-prototype-europe), as the prototype is slightly newer than Kula World.
The OBJ LEVEL inside Hiro's PAK file was replaced with a new **LESSON** level, slightly modified to show the player most of the objects and blocks, though still inaccessible:

A more obscure unused level is one embedded inside the game's executable, which was later used in [Kula Quest](#kula-quest) for the Time Trial ending:

Unused code for the ending sequence does exist inside the game, and can be activated using a debugger:
Additional changes include:
- The ending FMV was changed to a golden ball bouncing on a block.
- The Hidden 10 exit is correctly placed, with the Hidden 9 exit being placed but in an unreachable location.
- For an unknown reason, the "ACID!" death message is displayed when The Final is completed.
- Several [.TGI](/formats/tgi) files are changed.
Notably, the dark side in Hiro's theme is a little brighter in this version.
## Kula Quest
Kula Quest was released on **May 27th, 1999** and was published by [Sony Computer Entertainment](https://en.wikipedia.org/wiki/Sony_Interactive_Entertainment).
With it being the most recent official release, it contains a multitude of new features and adjustments than the previous versions.
The Time Trial ending level was slightly changed:
- Additional blocks are added.
- All of the objects in the center were removed.
- The exit and a single key was moved to the same platform as the player spawn.

Additional changes include:
- The The Final completion message is fixed with the same message appearing when the player touches acid.
- The Hidden 9 exit is correctly placed in a different level and the fruit is removed from the hidden level itself.
- Several levels were changed to accommodate for the new level completion percentage feature.
## References
Cover sources:
- [Kula World Cover](https://en.wikipedia.org/wiki/Kula_World#/media/File:Kula_World_Coverart.png), Wikipedia
- [Roll Away Cover](https://www.mobygames.com/game/9070/roll-away/cover/group-26080/cover-67016/), MobyGames
- [Kula Quest Cover](https://archive.org/details/kulaquestjapan), Internet Archive
---
---
url: /content/releases.md
---
---
description: 'Every known release and version of Kula Quest.'
---
# Releases
{{ $frontmatter.description }}
::: tip Note
Information regarding the main releases is available [here](/content/main-releases).
:::
**Table of Contents:**
[[toc]]
## Overview
Releases marked in **blue** are unique to that disc, totaling to **10** known versions of the game that exist.
| Product Code | Name | Build Version | Region |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------- |
| `SCED-00678` | [Euro Demo 13 (Germany)](#euro-demo-13-germany) | | PAL |
| `SCED-00936` | Registered Users Demo 02 (Europe) | | PAL |
| `SCED-03473` | [Euro Demo 10-01 (Germany)](#euro-demo-10-01-germany) | | PAL |
| `SCED-03789` | Euro Demo 86 (Europe) | | PAL |
| `SCED-04084` | Euro Demo (Future) 105 (Europe) | | PAL |
| `SCED-00782` | Euro Demo 20 (France) | | PAL |
| `SCED-00829` | Euro Demo 33 (Europe) | | PAL |
| `SCED-01140` | Essential PlayStation 9 (Europe, Australia) | | PAL |
| `SCED-01385` | Registered Users Demo (Europe) | | PAL |
| `SCED-01441` | Winter Releases '98 (Europe) | | PAL |
| `SCED-01441` | Winter Releases '98 (Rev 1) (Europe) | | PAL |
| `SCED-02204` | Sony Double Impact Games (Europe) | | PAL |
| `PBPX-95007` | [Demo One (Version 6) (Europe)](#demo-one-version-6-europe) | | PAL |
| `PBPX-95008` | Demo One (Version 7) (Europe) | | PAL |
| `SCED-01123` | Euro Demo 06/98 (Germany) | | PAL |
| `SCED-01189` | MIP Club Demo 01 (Germany) | | PAL |
| `SCES-01000` | [Kula World (Europe)](/content/main-releases#kula-world) | | PAL |
| `SCES-01000` | [Kula World Prototype (Europe)](#kula-world-prototype-europe) | | PAL |
| `SLUS-00724` | [Roll Away (North America)](/content/main-releases#roll-away) | | NTSC-U/C |
| `SCPS-10064` | [Kula Quest (Japan)](/content/main-releases#kula-quest) | | NTSC-J |
| `PCPX-96142` | [Play-Pre Vol. 16 (Japan) (Disc 1)](#play-pre-vol-16-japan-disc-1) | | NTSC-J |
| `PCPX-96121` | [Kula Quest Taikenban (Japan)](#kula-quest-taikenban-japan) | | NTSC-J |
| `SLPM-80423` | [Famitsu Wave 6gatsu-gou Vol. 11 (Japan)](#famitsu-wave-6gatsu-gou-vol-11-japan) | | NTSC-J |
| `SCED-01850` | [Euro Demo 30 (France)](#euro-demo-30-france) | | PAL |
| `SLPM-80425` | [Dengeki PlayStation D19 (Japan)](#dengeki-playstation-d19-japan) | | NTSC-J |
| `SLPM-80424` | [Hyper PlayStation Re-mix 1999 No. 6](#hyper-playstation-re-mix-1999-no-6) | | NTSC-J |
| `PCPX-96170` | [Hyper PlayStation Re-mix 1999 No. 9 (2)](#hyper-playstation-re-mix-1999-no-9-japan-disc-2-sony-computer-entertainment-special) | | NTSC-J |
Here is a separate table for all the known demos containing the official [Kula World Trailer](https://youtu.be/XPKme7NelHQ):
| Product Code | Name | Region |
| ------------ | --------------------------------- | ------ |
| `SCED-00781` | Euro Demo 18 (France) | PAL |
| `SCED-00828` | Euro Demo 32 (Europe) | PAL |
| `SCED-01122` | Euro Demo 17 (Germany) | PAL |
| `SCED-01230` | Registered Users Demo 03 (Europe) | PAL |
For an unknown reason, multiple PlayStation game lists contain a "Kula World Beta" entry with the product code `BABD-3775`.
It is unknown if this version even exists or its origin, as `BABD` is not a standard product code convention for PlayStation games.
- https://github.com/julianxhokaxhiu/iPoPS/blob/master/PSXListOFGames.txt
- https://m.blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=toses3&logNo=40035465262
## Common Build Versions
Build version dates were determined by using the **latest date** inside the game files for the main release or demo.
The executable date and PVD timestamps are usually inaccurate, so this method was chosen.
### Alpha

| World | Levels |
| ----- | ---------------- |
| Hiro | 4 (1 unlockable) |
This is the earliest known build of the game released, when the game was originally intended to be called **Kula Quest** worldwide.
It contains 4 levels, with the last one being unlocked when all 3 fruits are collected.
There are 2 fruits that unused in this demo:
As well as 4 unused balls:
Several unused pieces of text go unused inside the game's executable, including references to world names not found in official releases of the game:
```
KULA, SUNSET, MARS, HIRO, FIELD, ICE, WOOD
```
What likely used to be the main menu options at some point:
```
1P GAME
2P GAME
LOAD GAME
OPTIONS
COMBAT
COOPERATIVE
BACK
```
Various level ending texts, including a somewhat ominous message telling the players that they "cheated":
```
YOU WERE\nFRIED!\n
\nGOT YOU!\n
\nACID DEATH!\n
\n\nYOU CHEATED!\n
```
A reference to a TIM image inside an EARTH directory, likely a startup warning screen:
```
\\EARTH\\WARNING.TIM;1
```
### Beta

| World | Levels | Bonus Levels |
| ----- | ------ | ------------ |
| Hiro | 3 | 1 |
This is the second known build of the game released, where it was renamed to **Kula World**.
It contains 3 regular levels and 1 bonus level. Several balls go unused, and can be seen [here](https://www.youtube.com/watch?v=NybEGYafcK8).
An unused fruit bowl is included in this demo, setting all of the fruit to collected when touched by the player.
This item is actually [inside the base games](https://x.com/SaturnKai1/status/1297644867288367104) as well, though the model is set to an hourglass instead.

There are 5 unused present collectables, which have no sound effect or particles and just disappear when touched by the player.
The unused "hedgehog" enemy in the main versions have an entirely different model in this demo, and some other models have slight differences as well.
A full video with a more in depth look at some of these model changes is available [here](https://www.youtube.com/watch?v=YokLTYeL65E).
There are a few unused cheat references:
```
"OBJ CHEAT\n"
"BONUS CHEAT\n"
```
There are also several unused .XA files referenced as well:
```
\\SAMPLE.XA;1
\\XA\\NIGHT.XA;1
\\XA\\EARTH.XA;1
\\XA\\SUNSET.XA;1
\\XA\\MUSIC_1.XA;1
\\XA\\MUSIC_3.XA;1
\\XA\\MUSIC_2.XA;1
```
### Beta (Inca Variant)

| World | Levels | Bonus Levels |
| ----- | ------ | ------------ |
| Inca | 3 | 1 |
This build is mostly identical to the regular beta build.
It instead takes place in the **Inca** environment, has a different bonus level, and a slightly different executable file.
An interesting oddity is that the developers forget to change the sun glare to reflect on the new Inca skybox, which has the sun on the right side instead of behind.
Thus, this demo does not contain sun glare:

## Unique Build Versions
### Kula World Prototype (Europe)

This build is pretty obscure, with not much information known about its origin.
The build appears to have been made after the official release of Kula World, and is likely a copy sent to Psygnosis for review before publishing Roll Away.
It's pretty much identical to Kula World, with the following differences:
- Freecam is available without needing a code by pressing the **O** button.
- The introduction image was changed from `WARNING.TIM` to `SCEI.TIM` that displays "Sony Computer Entertainment Inc. Presents".
- The fruit placed by mistake inside the first Hills final level was removed.
- The executable file is slightly different from Kula World.
The `HIRO.GGI` file is slightly different, with the ball jump animation being updated to the version used in Roll Away, where the player can now jump and hit their head on certain blocks:
Some of the HUD sprites are slightly squished in height for an unknown reason as well:
- The fruit collection indicators
- Hourglass
- Key
- Kula World logo
### Play-Pre Vol. 16 (Japan) (Disc 1)

| World | Levels | Bonus Levels |
| ----- | ------ | ------------ |
| Hiro | 5 | 1 |
### Kula Quest Taikenban (Japan)

| World | Levels | Bonus Levels | Hidden Levels |
| ------- | ------ | ------------ | ------------- |
| Hiro | 5 | 1 | - |
| Inca | 5 | 1 | - |
| Haze | 5 | 1 | 1 |
| CopyCat | 3 | - | - |
The level filenames inside the [PAK](/formats/pak) files in this version are all over the place in their order:
```
HIRO.PAK:
LEVEL 1, LEVEL 2, LEVEL 5, LEVEL 4, LEVEL 3, BONUS 1
INCA.PAK:
LEVEL 6, LEVEL 8, LEVEL 11, LEVEL 9, LEVEL 12, BONUS 2
HAZE.PAK:
LEVEL 7, LEVEL 10, LEVEL 13, LEVEL 14, LEVEL 15, BONUS 3, HIDDEN
```
### Famitsu Wave 6gatsu-gou Vol. 11 (Japan)

| World | Levels | Bonus Levels |
| ----- | ------ | ------------ |
| Inca | 10 | 2 |
For an unknown reason, this disc contains an unused `KULA.SFX` file that is identical to the `TAIKEN1.SFX` file, and an unused `KULA.TGI` file that is identical to other demo **Haze** theme files (SCED-01850).
### Euro Demo 30 (France)

| World | Levels | Bonus Levels |
| ----- | ------ | ------------ |
| Haze | 5 | 1 |
| Hills | 5 | 1 |
This is the only demo with the **Kula World** title that contains any reworked themes.
This disc contains a reworked Haze theme, while also containing a second pre-worked Hills theme with the original bonus titles, named `LULA.TGI`.
It also contains an unused `WARNING.TIM` file identical to official releases of Kula World.
Allows for turn delay to be toggled.
### Dengeki PlayStation D19 (Japan)

| World | Levels | Bonus Levels |
| ----- | ------ | ------------ |
| Haze | 10 | 2 |
Level 7 contains a [level flag property](/formats/level#level-flag-property) that the game is not programmed to ignore in the final completion percentage, despite the executable being dated after the final Kula Quest release.
Subsequently, this causes the level to never show a **100%** completion, as the game thinks there's an additional item to be collected.
Additionally, for some reason the Haze soundtrack used in this demo is a version shorted to **2 minutes and 40 seconds**, as oppose to being **5 minutes and 9 seconds**.
### Hyper PlayStation Re-mix 1999 No. 6

| World | Levels | Bonus Levels |
| ----- | ------ | ------------ |
| Haze | 10 | 2 |
### Hyper PlayStation Re-mix 1999 No. 9 (Japan) (Disc 2) (Sony Computer Entertainment Special)

| World | Levels | Bonus Levels | Hidden Levels |
| ----- | ------ | ------------ | ------------- |
| Mars | 5 | 1 | 1 |
The only known version of the game to contain the unused "**hedgehog**" enemy in a level.
This demo also includes the infamous **Sunset** level seen in the official trailer, with slight modifications to increase difficulty.
## Additional Oddities
The following oddities are from a [common build version](#common-build-versions) but are specific to the demo disc itself.
### Euro Demo 13 (Germany)
This demo contains a `TEST.CTI` file, which is a **CD Track Information** file used to define the structure of a PlayStation CD image.
This file is **not** supposed to be included inside a published game and was added by mistake.
```
Echo The cti file you are using has been generated by the GenCTI utility
Echo The aim of this program is to provide a simple method for generatng cti file
Echo templates. If you have not already done so it would be advisable to review
Echo the cti file created, to correct positions of files.
ShowDefines
Disc CDROMXA_PSX template.img
CatalogNumber 000000000000
LeadIn XA
Empty 1000
PostGap 150
EndTrack
Track XA
Pause 150
Volume ISO9660 CD.ISO
PrimaryVolume
SystemIdentifier "PLAYSTATION"
VolumeIdentifier "TEMPLATE"
LogicalBlockSize 2048
VolumeSetIdentifier "VOLUME1"
PublisherIdentifier "SONY"
DataPreparerIdentifier "EXAMPLE"
ApplicationIdentifier "SONY"
Lpath
OptionalLpath
Mpath
OptionalMpath
;Please look carefully thorugh the hierarchy created below.
;Ensure that the boot file is located first.
;It is a simple matter to cut and paste files into correct postions.
;Refer to the CDEmulator manual for further details
Hierarchy
File KULA.EXE
XAFileAttributes Form1 Data
Source C:\TESTING\DEMOS\KULA\KULA.EXE
EndFile
File PSX.EXE
XAFileAttributes Form1 Data
Source C:\TESTING\DEMOS\KULA\BS.EXE
EndFile
File KULA.GGI
XAFileAttributes Form1 Data
Source C:\TESTING\DEMOS\KULA\KULA.GGI
EndFile
...
EndDisc
```
::: info Note
The file has been truncated for size.
:::
### Euro Demo 10-01 (Germany)
The music is bugged in this demo and is completely silent, despite the game files being identical to other discs containing the same version.
Additionally, the demo itself titles the game as "Kula World" instead of Kula Quest like it should.
### Demo One (Version 6) (Europe)
For an unknown reason, the second level is incorrectly displayed on the title screen, despite the game files being identical to other discs containing the same version.
---
---
url: /content/soundtracks.md
---
---
description: 'Details regarding all known soundtracks from Kula Quest.'
---
# Soundtracks
{{ $frontmatter.description }}
## Overview
There are **13** official soundtracks in the official releases of Kula Quest, with the first 10 being used for each world in the game, and an additional 3 tracks for bonus and hidden levels.
However, there are multiple soundtracks that aren't included within the main series of the game.
## Kula Quest Demo
The most well known removed soundtrack is from the earliest alpha version of the game.
It can be easily found on various sites such as YouTube.
## Track 14
A mysterious soundtrack has surfaced around the internet with unknown origins, and seems to have came directly from **Twice a Man**.
The track doesn't officially have a name, and is usually called "Hidden Track" or "Unknown Track".
The track number inside the metadata of the MP3 conflicts as well, as 2 versions refer to this track to be #14, while another suggests it is #16.
For an **unknown reason**, one of the versions of the soundtrack (originally sent by Steve from the Discord server) contains the text, **"Ripped By Rimo"**, suggesting that it was ripped directly from the game instead.
Rimo is a very prominent person in video game music (VGM) ripping, who's website can be found [here](https://www.geocities.ws/rimo_vgm/gamerips.htm).
They have ripped many soundtracks from Kula World, with each one containing the very same text in their metadata.
Attempts to contact Rimo have been unsuccessful, as they have appeared to have left the VGM ripping scene altogether.
There is no evidence that suggests this track is hidden inside the game's files, unless it came from an unknown build of the game.
## Misc
### Soundtrack Playlist
A popular playlist containing the game's soundtracks used to exist on YouTube, with a thumbnail suggesting multiple unreleased soundtracks:

One version of the [Track 14](#track-14) contains the track number #16 as previously stated, so it could be a beta version of Arctic as noted in the graphic.
### Email Response
The following is a response from an email sent directly to Twice a Man from **Murphy** regarding [Track 14](#track-14):
> I made originally 14 tracks to Kula World and when I delivered them the programmers used them as they liked, i.e. I did not have control where which piece was to which "world".
> The piece you refer to has been in at least 2 different versions and was delivered to the game among the others tracks. There are also other pieces which have different versions.
> I sent some of the music to friends, included the piece you refer to and now you can hear all these tracks on Youtube etc.. It is out of my control.
> I hope some day be able to release the original music as an official audio release. I don´t know if you got wiser by this, but I hope so.
> Best Wishes
>
> Dan Söderqvist
>
> Twice a Man
This response suggests that there are multiple versions of various soundtracks that have been created and put into the game, with the hidden track included in at least 2 different versions of the game.
It is possible that this track could've been included in an unknown earlier build of the game at some point, and was ripped by **Rimo**.
### Official Trailer
Inside the game's [official trailer](https://www.youtube.com/watch?v=XPKme7NelHQ), the background music is a slight variation of the Hills theme.
It seems to be just a compressed and spliced version, and is unknown if it is actually different from the official version.
---
---
url: /formats/ggi.md
---
---
description: 'A custom binary format for storing model and sprite data.'
---
# GGI Format
{{ $frontmatter.description }}
This format is still under heavy research!
## Overview
```c
struct GGIData_t {
GGIHeader_t header;
ModelTable_t model_table;
};
```
## Header
```c
struct GGIHeader_t {
int32_t sprite_group_index_1;
int32_t sprite_group_index_2_enc;
int32_t sprite_group_index_3_enc; // fancy numbers / text
int32_t sprite_group_index_4_enc; // hidden level particles (do not seem affected though)
int32_t sprite_group_index_5_enc; // bonus meter
int32_t sprite_group_index_6_enc; // hourglass sprite to the end
int32_t hourglass_anim_offset_enc; // hourglass spin animation
int32_t unk_offset1_enc; // block lighting related
int32_t unk_offset2_enc; // possibly ignored in game, doesn't seem to be read anywhere
int32_t dummy_offset_enc; // offset to "dummy!!!" text
int32_t ball_anim_enc; // ball bounce animation
int32_t sprites_offset_enc;
int32_t file_size_enc;
int32_t entity_table_offset_enc;
int32_t object_table_offset_enc;
};
```
The first part of the header contain indexes to different groups of sprites:
1. Sprites relating to object collection and teleportation affects.
2. Sprites relating to the sun glare and scores / loading screen blue background boxes.
3. Sprites relating to menu text elements.
4. Sprites relating to the hidden level sky particles, although **does not seem to affect them**.
5. Sprites relating to the bonus level HUD.
6. Sprites relating to the hourglass hud and various other text elements; the rest of the sprites.
```c
// decoding the sprite indexes
sprite_group_index_1 = sprite_group_index_1;
sprite_group_index_2 = sprite_group_index_1 + sprite_group_index_2_enc;
sprite_group_index_3 = sprite_group_index_2 + sprite_group_index_3_enc;
sprite_group_index_4 = sprite_group_index_3 + sprite_group_index_4_enc;
sprite_group_index_5 = sprite_group_index_4 + sprite_group_index_5_enc;
sprite_group_index_6 = sprite_group_index_5 + sprite_group_index_6_enc;
```
Every offset contained in the header is encoded in the following manner for an unknown reason:
```c
// decoding the first six offsets
hourglass_anim_offset = hourglass_anim_offset_enc * 2 + 0x34;
unk_offset1 = unk_offset1_enc * 2 + hourglass_anim_offset;
unk_offset2 = unk_offset2_enc * 2 + unk_offset1;
dummy_offset = dummy_offset_enc * 2 + unk_offset2;
ball_anim = ball_anim_enc * 2 + dummy_offset;
sprites_offset = sprites_offset_enc * 2 + ball_anim;
file_size = file_size_enc * 2 + sprites_offset;
// decoding the two table offsets
entity_table_offset = ((entity_table_offset_enc >> 2) + 0xD) * 4;
object_table_offset = ((object_table_offset_enc >> 2) + 0xD) * 4;
```
## Model Table
```c
struct ModelTable_t {
// entities
ModelEntry_Entity_t world_balls[10];
ModelEntry_Entity_t bonus_balls[3];
ModelEntry_Entity_t hidden_ball;
ModelEntry_Entity_t padding[6];
ModelEntry_Entity_t slow_star;
ModelEntry_Entity_t tire;
ModelEntry_Entity_t fast;
ModelEntry_Entity_t capture_pod;
ModelEntry_Entity_t captivator;
// objects
ModelEntry_Variants_t padding[5];
ModelEntry_Variants_t transporters;
ModelEntry_Variants_t padding;
ModelEntry_Variants_t exits;
ModelEntry_Variants_t padding[2];
ModelEntry_Variants_t buttons;
ModelEntry_Variants_t bounce_pad;
ModelEntry_Variants_t moving_spike;
ModelEntry_Variants_t spike;
ModelEntry_Variants_t padding[13];
ModelEntry_Variants_t hidden_exit;
ModelEntry_Variants_t fruit_bowl;
ModelEntry_Variants_t arrow;
ModelEntry_Variants_t padding[2];
ModelEntry_Variants_t key;
ModelEntry_Variants_t lethargy_pill;
ModelEntry_Variants_t bounce_pill;
ModelEntry_Variants_t invincibility_pill;
ModelEntry_Variants_t hourglass;
ModelEntry_Variants_t gems;
ModelEntry_Variants_t coins;
ModelEntry_Variants_t sunglasses;
ModelEntry_Variants_t present_1;
ModelEntry_Variants_t present_2;
ModelEntry_Variants_t present_3;
ModelEntry_Variants_t hedgehog;
ModelEntry_Variants_t apple;
ModelEntry_Variants_t watermelon;
ModelEntry_Variants_t pumpkin;
ModelEntry_Variants_t banana;
ModelEntry_Variants_t strawberry;
ModelEntry_Variants_t unknown;
ModelEntry_Variants_t unknown;
};
struct ModelEntry_Entity_t {
int32_t lod_offset_1;
int32_t lod_offset_2;
int32_t lod_offset_3;
int32_t padding; // a 4th LOD offset is available in demos
}; // 0x10 bytes
struct ModelEntry_Variants_t {
typedef struct {
int32_t variant1_Loffset;
int32_t variant2_Loffset;
int32_t variant3_Loffset;
int32_t variant4_Loffset;
} Variant_LOD_offset_t;
Variant_LOD_offset_t L1;
Variant_LOD_offset_t L2;
Variant_LOD_offset_t L3;
int32_t padding[4];
}; // 0x40 bytes
```
The model offset table begins at offset **$3C** in the file and is where the game references each object's model. Every model has **three** different levels of detail (LOD), with the first being the **highest quality** when the player is closest to the model, and the third being the **lowest quality** when the player is further from the model. Every entry in the table contains offsets to these different quality models, with each offset being denoted with an "L" in the structure above.
Padding between entries in the table are always set to **-1**.
The last two entries in the table most likely refer to the last two unused presents in the demo.
Each offset is **relative** to a specific offset:
- Entity offsets are relative from **0x3C**.
- Object offsets are relative from **0x1CC**.
## Model Data
```c
struct ModelData_t {
short unknown;
short unknown;
short unknown;
short unknown;
short stp_blend_operator;
short unknown;
int32_t unknown; // set to 24 for objects, 28 for balls
IndexBuffer_t index_buffer;
VertexBuffer_t vertex_buffer;
};
struct IndexBuffer_t {
int32_t size;
int32_t unknown;
char indices[size - 32];
int32_t unknown = 1;
int32_t unknown = 0;
};
typedef struct {
int32_t vertexBufferCount = 1; // vertex buffer count (32 for moving spike)
int32_t size; // size of vertices section
struct IndexBuffer_t {
short x1;
short y1;
short x2;
short y2;
short x3;
short y3;
short z1;
short z2;
short z3;
short padding = 0;
} vertices[][];
} VertexBuffer_t;
typedef struct {
int32_t unknown = 1;
int32_t size; // size of vertex colors section
struct VertexColor_t {
char r;
char g;
char b;
char vFlags;
} vertex_colors[size / 4]; // 4 bytes
} VertexColors_t;
```
Not every model in the game has a vertex count in multiples of three. Since the vertex position buffer of a model is grouped into **threes**, the leftover vertex positions are set to **0**. For example, the _arrow model_.
### Vertex Flags
Each vertex color attribute contains an extra byte with flags that specifies additional information about the vertex (vFlags):
| Bit | Type |
| --- | ------------------ |
| 1 | Unused (0) |
| 2 | Unused (0) |
| 3 | Unused (1) |
| 4 | Unknown |
| 5 | Tri (0) / Quad (1) |
| 6 | Unused (0) |
| 7 | Transparent |
| 8 | Unused (0) |
## Hourglass Sprite Animation
There is a section dedicated to the hourglass flip animation that is **1920** bytes in size. This animation data is the exact same across all known GGI files.
## Unknown Section 1
This section is currently unknown, and seems to affect block lighting. It is **8192** bytes in size, and is also the exact same across all known GGI files.
## Unknown Section 2
This section is also unknown, and does not seem to be referenced in later releases of the game. It is also **8192** bytes in size, and is also the exact same across all known GGI files.
## Dummy Section
This section contains the text "dummy!!!\r\n\r\n".
## Ball Animation Section
```c
struct RelativePositionOffset_t {
short x;
short y;
short z;
};
struct BallAnim_t {
short anim_frames;
short padding[3];
short y_start;
short z_start;
RelativePositionOffset_t position_offsets[anim_frames - 1];
};
struct BallAnim_t {
short anim_frames;
short padding[3];
short y_start;
short z_start;
RelativePositionOffset_t position_offsets[];
short x_end;
};
```
This section contains data relating to the ball jumping, including jumping forward. Each short position value offsets the ball in that relative direction. Here's an example of the first and last 4 frames of animation from Kula World:
```
0 0 0 (y_start and z_start)
-48 61 0
-96 117 0
-142 168 0
...
-941 158 0
-969 110 0
-997 56 0
-1024 0 0
```
The Y and Z position start of at 0, and each frame the relative X position is decremented when jumping forward and the Y is incremented for height. At the end of the animation, the Y starts decrementing again until it reaches 0, back to its original position and thus landing the ball.
This section was updated for the Roll Away release, with the animation lasting longer and going slightly into the floor upon landing. This is what causes the [floor clip glitch](https://youtu.be/G6RH7ERGCtI?t=105) in Roll Away.
## Sprites
The first value in the sprite section is the amount of sprites (int32_t). Immediately following this value are all the sprites, which use the following structures:
```c
struct Sprite_t {
short bpp;
short blend_op;
CLUT_t clut;
Texture_t texture;
};
struct CLUT_t {
short vram_x;
short vram_y;
short use_prev;
short padding = 0;
if (bpp != 16) {
short data[bpp == 8 ? 256 : 16];
}
};
struct Texture_t {
short vram_x;
short vram_y;
short width; // actual width, not in framebuffer pixels
short height;
if (bpp != 16) {
char data[bpp == 8 ? (width * height) : (width * height) / 2];
} else {
short data[width * height];
}
};
```
After each sprite's texture, there **may** be extra bytes (which some contain garbage data) to align to the 4 byte boundary.
When a sprite uses **16 bits per pixel**, each pixel is represented directly using 16 bit color instead of a color lookup table (CLUT), and therefore the values for the associated CLUT are set to **-1** in this bit depth (different in the first demo, see below). The only sprite that uses this bit depth is a 2x1 completely white sprite, and its current use in game is **unknown**.
Unique to only the first demo of the game (Kula Quest beta), the 4 CLUT values for a 16 bit sprite are not included, thus after the **bpp and blend operator** values starts the **Texture_t** structure.
---
---
url: /formats/index.md
---
# Formats
Technical specifications are available for most of Kula Quest's custom binary formats.
## Overview
---
---
url: /formats/kub.md
---
---
description: 'A custom archive format for storing multiple files with compression in the oldest demo.'
---
# Kub Format
{{ $frontmatter.description }}
This document contains information about the format structure of Kub files.
If you are interested in using tools to create your own, please visit [here](../tools/quilt.md).
## Overview
The first demo of the game uses a custom archive format for storing multiple compressed files into one file.
This format was eventually superseded by the [**.PAK**](./pak) format used in later versions of the game, with the only differences being that the Kub **doesn't** store filenames and uses a different compression algorithm.
It is worth noting that the **.PIC** file is in the exact same format.
All values are in [**little endian**](https://en.wikipedia.org/wiki/Endianness), and the following data types will be used:
| Encoding | Description |
| -------- | ----------------------- |
| u32 | Unsigned 32-bit integer |
### Structure
The format is comprised of the following structure:
- File count
- Offset and compressed size of each file
- Compressed file data
## Header
The first 4 bytes in the file (**note** there is no magic header) specify how many compressed files are inside the archive:
| Offset(h) | Size | Type Description |
| --------- | ---- | ---------------------------------- |
| 0x00 | 4 | u32 Number of files in the archive |
## File Table
Starting at offset 0x04, each file entry is 8 bytes:
| Offset(h) | Size | Type Description |
| --------- | ---- | ------------------------------------------- |
| +0x00 | 4 | u32 Absolute offset to compressed file data |
| +0x04 | 4 | u32 Size of compressed file in bytes |
## File Data
The file data starts immediately after the table, with each buffer starting with a null 4-byte value.
Each file is compressed using [**lzrw3a**](http://www.ross.net/compression/lzrw3a.html), an old and somewhat obscure compression algorithm.
---
---
url: /formats/level.md
---
---
description: 'A custom binary format for storing level information.'
---
# Level Format
{{ $frontmatter.description }}
This document contains information about the format structure of level files.
If you are interested in using tools to create your own, please visit [here](https://example.com).
**Table of Contents:**
[[toc]]
## Overview
Kula Quest uses a custom binary format for storing level data.
This format does not have its own file extension, and is mostly the same across all versions of the games with slight differences.
All values are in [**little endian**](https://en.wikipedia.org/wiki/Endianness), and the following data types will be used:
| Encoding | Description |
| -------- | --------------------- |
| i16 | Signed 16-bit integer |
### Structure
The format is comprised of the following structure:
- Block identifiers
- The number of blocks and block property count
- Block properties
- Optional level properties
| Offset(h) | Size | Type | Description |
| --------- | ----- | ---------------- | ---------------------------------------- |
| 0x00 | 78608 | i16[34^3] | Identifiers for every block in the level |
| 0x13310 | 2 | i16 | Number of blocks in the level |
| 0x13312 | 2 | i16 | Unused (sometimes -1) |
| 0x13314 | 2 | i16 | Number of block properties in the level |
| 0x13316 | - | property[] | Block properties |
| - | 256 | level_properties | Level properties (optional) |
---
Every level in the game is essentially a **34x34x34** grid of blocks, with some blocks containing extra properties.
The first block starts on the top-left front corner of the level, which is at coordinate **0, 0, 0**.
The last block ends on the bottom-right back corner of the level, which is at coordinate **33, 33, 33**.

Based on the illustration above:
- Positive X and negative X indicate right and left respectively.
- Positive Y and negative Y indicate down and up respectively.
- Positive Z and negative Z indicate backward and forward respectively.
::: tip Note
Level editors invert the Y axis for simplicity as it is more common for positive Y to indicate up instead of down.
:::
## Position Types
There are 2 types of positioning used in this format — **block coordinates** and **entity coordinates**.
Here's an example of the different coordinates:
- `11 00 10 00 11 00` - Block Coordinate
- `00 22 00 20 9C 20` - Entity Coordinate
The block coordinate system is mainly used for positioning block properties and block positioning in general.
Objects and entities in game that need to use finer position values use the entity coordinate system, such as the ball and the current moving block position.
An entity coordinate value is **double** the amount of a block coordinate value, and the bytes are swapped, with the first byte as fine tune.
In the example above, we have a block with an item using the block coordinate and a ball on top of the same block using the entity coordinate.
The X and Z values are the same, while the Y coordinate is slightly above the block.
Here are the two different structures used throughout this document to define the type of positioning used:
### Block Position
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | ----------- |
| +0x00 | 2 | i16 | X position |
| +0x02 | 2 | i16 | Z position |
| +0x04 | 2 | i16 | Y position |
> Also referenced as **Position_block_t** in structures.
### Entity Position
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | --------------------------- |
| +0x00 | 2 | i16 | X position (more precision) |
| +0x02 | 2 | i16 | Z position (more precision) |
| +0x04 | 2 | i16 | Y position (more precision) |
> Also referenced as **Position_entity_t** in structures.
## Block Identifiers
The first **78608** bytes of the file contain 2 byte values that correspond to each block in the grid.
As referenced above, the first value in this section signifies the first block in the level, which is at the top-left front corner of the level.
The last value in this section signifies the last block in the level, which is at the bottom-right back corner of the level.
Every 2 bytes indicate a single block, each time incrementing the Y position.
This continues until the Y position exceeds 33, where it's reset back to 0 and the Z position is incremented.
Once the Z position exceeds 33, the X position is incremented and Y and Z are reset back to 0.
These blocks are read until the position of the file reaches the end of the **block identifier section**, which is at offset **0x13310** (which as stated previously is the size of this section).
Here is a table defining what the 2 byte identifier represents:
| ID | Type |
| ---------- | ------------------------------------------------------------------------------------------------------------------ |
| -1 | An air block — nothing at all is placed here, and is completely ignored. |
| 0 | A block with no special properties at all; i.e. a block that doesn't contain any objects or is of a specific type. |
| 1 | A fire block, which is just a normal block but with fire patches on all sides with no objects or properties. |
| 2 | Same as above, but as an ice block. |
| 3 | Same as above, but as an invisible block. |
| 4 | Same as above, but as an acid block. |
| 5 or above | A block that contains special properties. See below. |
| -2 | Reserved for laser segments, in memory usage only. |
Any value that is **5** or greater indicates a block with special properties and has corresponding [property data](#property-list), such as a crumbling or laser block, or a block that contains items and/or objects.
The first block of this type must start at **5**, and is incremented for every next special block.
## Block and Property Count
Immediately after the block identifier section begins these 3 values:
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | --------------------------------------- |
| 0x13310 | 2 | i16 | Number of blocks in the level |
| 0x13312 | 2 | i16 | Unused (sometimes -1) |
| 0x13314 | 2 | i16 | Number of block properties in the level |
The first value does not have to be set as it's not read in game, while the third value must be set properly in order for the properties to work.
For an unknown reason, the unused value is sometimes set to -1 in certain levels, and the first value is sometimes set to a negative number.
## Property List
There are a lot of different types of special blocks used in the game, and each one needs additional information defined below at the end of the file, right after the block and property count section (**0x13316**).
Each property is a chunk of **256 bytes**.
## Object Block
The following structure below applies to blocks that contain **objects**, i.e. anything that is assigned to a specific side of a block like items and traps. [Moving](#moving-block), [crumbling](#crumbling-block), [flashing](#flashing-block), and [laser](#laser-block) blocks follow a different structure, and have dedicated sections respectively below.
| Offset(h) | Size | Type | Description |
| --------- | ---- | -------------------------------------------- | -------------------------------------- |
| +0x00 | 2 | i16 | Type of block (0-4) |
| +0x02 | 32 | object | Object on the top of the block (-y) |
| +0x22 | 32 | object | Object on the right of the block (+x) |
| +0x42 | 32 | object | Object on the front of the block (+z) |
| +0x62 | 32 | object | Object on the back of the block (-z) |
| +0x82 | 32 | object | Object on the left of the block (-x) |
| +0xA2 | 32 | object | Object on the bottom of the block (+y) |
| +0xC2 | 56 | - | Padding (set to -1) |
| +0xFA | 6 | position_block | The block's position in the level |
### Object Property
Each object contains **32 bytes** of information:
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | -------------------------------- |
| +0x00 | 2 | i16 | Object identifier |
| +0x02 | 2 | i16 | Object direction |
| +0x04 | 2 | i16 | Object variant |
| ... | | | _See struct for complete layout_ |
```c
struct BlockObject_t {
i16_t id;
i16_t direction;
i16_t variant;
i16_t state;
i16_t collectableIndex; // automatically set in memory
i16_t toggleObject1; // buttons and transporters only
i16_t toggleObject2; // buttons and transporters only
i16_t animationModelIndex = 0; // memory only (related to vertex buffer index)
i16_t groundOffset; // only required in demos
i16_t rotationType; // only required in demos
i16_t animationValue1 = -1; // memory only
i16_t animationValue2 = -1; // memory only
i16_t animationValue3 = -1; // memory only
i16_t rotationSpeed; // only required in demos
i16_t animationCounter = -1; // memory only
i16_t animationState = -1; // memory only
};
```
Here is an interactive example of the block that contains the level exit from the first level of HIRO, starting at the file offset `0x13310`:
::: tip Note
A table documenting every single object and their properties is available [here](/formats/objects).
:::
Every object has a completely unique **ID** that indicates what item to put there, with some having different **variants** as well.
For example, a coin has the ID of `0x25` with the following variants: **Bronze, Gold, and Blue**.
An object can have several **states** as well, such as a transporter or button being turned on or off.
Most collectables use the state field to determine whether an object has been collected by the player or not, with the state being set to **1** in the file and becoming **0** in memory when obtained.
Finally, the **direction** field is utilized by directional objects, such as arrows for the direction they face.
If a side of a block **does not** contain an object, the object ID and state are set to 0, and all other values are set to -1.
---
The following fields are automatically set in memory and are not required:
- **Collectable index**: Incremented from 0 after every collectable object.
- **Animation values**: Values used to keep track of different aspect of an object's animation.
- **Animation state**: Determines the state of the object's animation.
- **Animation counter**: A value to keep track of a counter for the object's animation.
In the alpha and beta versions of the game, the following values are required to be set, but are not read in later versions:
- **Ground offset**: Determines how far an object is above the block.
- **Rotation type**: Determines the type of rotation an object uses.
- **Rotation speed**: Determines how fast an object rotates.
---
### Toggle Object
Lastly are the two **toggle object** fields, which are only used by transporters and buttons to specify what object(s) to toggle when the object is toggled.
They both act as positions, and follow this principle:
```c
short propertyIndex;
short side;
ushort position = (propertyIndex * 16) + side;
```
For example, the value `D0 01` corresponds to the block with a property at index **29**, as the maximum value for the first byte is `F0` (16), and the second byte is incremented due to 29 exceeding 16, which leaves 13. `0xD0`
Since only the first digit in the byte represents the index, it would be `D0 01`.
Lastly, the second digit in the first byte is 0, which targets the object on top of the block.
This value will target the top side of the block with the 29th property.
Another example is the value `72 01`, which corresponds to index **23**.
Since the target side is 2, the second digit in the first byte is set as such, leaving `72 01` as a result.
This value will target the right side of the block with the 23rd property.
In earlier versions, buttons power themselves when pressed and only contain the first slot.
In all other versions of the games, buttons have two slots and **do not** power themselves when pressed.
This means buttons themselves need power, so often times one of the slots are used to power itself, though only **toggle object 2** can be used to power itself.
When targeting a laser block, ensure the side is set to **6**.
## Moving Block
| Offset(h) | Size | Type | Description |
| --------- | ---- | --------------- | --------------------------- |
| +0x00 | 2 | i16 | Property Type (5) |
| +0x02 | 2 | i16 | Direction |
| +0x04 | 2 | i16 | Axis |
| +0x06 | 2 | i16 | Unknown |
| +0x08 | 6 | position_block | Position 1 |
| +0x0E | 6 | position_block | Position 2 |
| +0x14 | 12 | - | Padding |
| +0x20 | 2 | i16 | Unknown |
| +0x22 | 2 | i16 | Length |
| +0x24 | 2 | i16 | Speed |
| +0x26 | 2 | i16 | Padding |
| +0x28 | 2 | i16 | Block ID |
| +0x2A | 196 | - | Padding |
| +0xEE | 6 | position_entity | Current Position |
| +0xF4 | 6 | unknown | Unknown (00 01 00 01 00 01) |
| +0xFA | 6 | position_block | Position |
The moving block contains 3 position values:
- **Position 1** - Specifies one of two points that the block will move between during the level. This position must be **before** position 2 along an axis.
- **Position 2** - Specifies the other one of two points that the block will move between, and must be positioned **after** position 1.
- **Starting Position / Current Position** - Specifies what position the moving block will start at when the level is started. This means that the moving block can actually start at a different point along the axis than the two positions specified, though in most cases (and in all cases from the game) this position is usually the same as one of the two points above.

Based on the example level seen above, position 1 (green) is always first along the axis than position 2 (blue), regardless of what direction the block is set to.
The direction specifies what direction the block will initially move towards on the axis:
- `00 00` indicates Negative Y.
- `01 00` indicates Positive X.
- `02 00` indicates Positive Z.
- `03 00` indicates Negative Z.
- `04 00` indicates Negative X.
- `05 00` indicates Positive Y.
- Any other value causes the moving block to not move at all.
The **axis** of the moving block indicates what axis it is on, and how the texture is wrapped onto the block. If this value is not set correctly, the block's collision will not work properly and will often crash the game.
- `00 00` indicates Y axis.
- `01 00` indicates X axis.
- `02 00` indicates Z axis.
- Any other value defaults to Y axis.
The **speed** indicates how many times the current position is incremented or decremented per frame depending on if it's moving in a positive or negative direction, respectively. Lastly, the **block ID** is set to the block ID that represents this property in the block data section.
---
A single block is placed at the starting position, which is known as the **origin**. Based on the length, that many blocks including the origin will be placed on the **positive** axis, e.g. if the direction is set to Negative X, the additional blocks will still be placed in the Positive X direction.
For example, if the block length is **1**, no other blocks will be placed as the origin block is apart of the length. If the length is **3** and the direction is set to **1**, **2 blocks** will be placed in the **Positive X** direction. The maximum length a moving block can be is **4**, as the texture data the block stores in memory inside its property overwrites other information about the moving block, as well as the next property.
## Crumbling Block
| Offset(h) | Size | Type | Description |
| --------- | ---- | --------------- | ----------------- |
| +0x00 | 2 | i16 | Property Type (6) |
| +0x02 | 2 | i16 | State (1) |
| +0x04 | 6 | position_entity | Position (entity) |
| +0x0A | 240 | - | Padding |
| +0xFA | 6 | position_block | Position (block) |
Crumbling blocks have the following state values, although they should be set to **1** in file:
- **0** indicates the crumble block is gone.
- **1** indicates the crumble block is active.
- **2** indicates the crumble block is crumbling, but is not used.
- **3** indicates the crumble block is crumbling.
- Any other value causes the crumble block to still be active, but will not make a sound when touched.
The crumble block contains entity positioning, and are set as the exact block coordinate of the crumble block, even though it seems to not have an effect in game when changed to a different value. The **240 bytes** of padding are usually set to **-1**, but strangely enough some crumble blocks have object block data and other weird structures. Although they have no effect, it's still interesting to see that maybe some crumble blocks were intended to contain objects as well.
## Flashing Block
| Offset(h) | Size | Type | Description |
| --------- | ---- | -------------- | ------------------------- |
| +0x00 | 2 | i16 | Property Type (7) |
| +0x02 | 2 | i16 | Index (-1, memory only) |
| +0x04 | 2 | i16 | Sync |
| +0x06 | 2 | i16 | State (-1, memory only) |
| +0x08 | 2 | i16 | Counter (-1, memory only) |
| +0x0A | 240 | - | Padding |
| +0xFA | 6 | position_block | Position |
The **sync** value is used to specify in what order of timing the flashing block will appear, similar to moving spikes.
Occasionally, just like crumble blocks, the **240 bytes** of padding may contain weird structures, but have no affect at all.
## Laser Block
| Offset(h) | Size | Type | Description |
| --------- | ---- | -------------- | ----------------- |
| +0x00 | 2 | i16 | Property Type (8) |
| +0x02 | 2 | i16 | Direction |
| +0x04 | 2 | i16 | Axis (unused) |
| +0x06 | 2 | i16 | Enabled |
| +0x08 | 6 | position_block | Position 1 |
| +0x0E | 6 | position_block | Position 2 |
| +0x14 | 14 | - | Padding |
| +0x22 | 2 | i16 | Unknown (1) |
| +0x24 | 4 | - | Padding |
| +0x28 | 2 | i16 | Block ID |
| +0x2A | 2 | - | Padding |
| +0x2C | 2 | i16 | Color |
| +0x2E | 2 | i16 | Toggle Object |
| +0x30 | 202 | - | Padding |
| +0xFA | 6 | position_block | Position |
Position 1 and 2 specify the two points of the laser, where position 1 must always come before on the axis than position 2, similar to the [moving block](#moving-block).
If one of the positions are set to a block that isn't actually present in the level, the game will automatically create a normal block in its place.
The direction specifies the direction of the laser:
- `00 00` indicates Negative Y.
- `01 00` indicates Positive X.
- `02 00` indicates Positive Z.
- `03 00` indicates Negative Z.
- `04 00` indicates Negative X.
- `05 00` indicates Positive Y.
The **color** property follows the same structure that transporters and buttons do, and can be viewed [here](/formats/objects#variant).
Changing the color actually indexes into the laser's different textures, as each color is its own texture, so setting the value other than 4 changes to textures beyond the boundary, and the game will crash upon turning it on.
**Enabled** specifies whether the laser block is enabled by default when the level is started.
Same as moving blocks, laser's also contain a **block ID** field that should be set to the block ID that represents this property in the block data section.
Lastly, laser blocks contain a **toggle object** field for specifying which block and face to toggle when the laser's power is toggled, similar to transporters and buttons.
## Level Flag Property
The flag property is optional and if set is always behind the level information property. This property is used for setting flags for the level and does not tie to any block at all:
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | --------------------------- |
| +0x00 | 2 | i16 | Property Type (9) |
| +0x02 | 2 | i16 | Hidden Level |
| +0x04 | 2 | i16 | Farsighted Invisible Blocks |
| +0x06 | 250 | - | Padding |
This property is very simple and only has two flags, which are set to true or false depending if they're set to 1 or 0 respectively.
The first flag specifies if the current level is a **hidden level**, and the second flag specifies if the current level uses **farsighted invisible blocks**.
Similar to crumble and flashing blocks, this property usually contains **a lot** of weird structures in its padding, and sometimes this property is added to a level that doesn't use any of its flags.
```
014010h 11 00 11 00 11 00 09 00 00 00 00 00 00 00 00 00
014020h 00 00 00 00 FF FF 00 00 FF FF FF FF FF FF FF FF
014030h FF FF FF FF FF FF FF FF 00 00 FF FF FF FF 00 00
014040h FF FF FF FF FF FF 00 00 FF FF FF FF FF FF FF FF
014050h FF FF FF FF FF FF FF FF 00 00 FF FF FF FF 00 00
014060h FF FF FF FF FF FF 00 00 FF FF FF FF FF FF FF FF
014070h FF FF FF FF FF FF FF FF 00 00 FF FF FF FF 00 00
014080h FF FF FF FF FF FF 00 00 FF FF FF FF FF FF FF FF
014090h FF FF FF FF FF FF FF FF 00 00 FF FF FF FF 00 00
0140A0h FF FF FF FF FF FF 00 00 FF FF FF FF FF FF FF FF
0140B0h FF FF FF FF FF FF FF FF 0A 00 00 00 00 00 01 00
0140C0h FF FF FF FF FF FF 00 00 00 01 03 00 FF FF FF FF
0140D0h FF FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF
0140E0h FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0140F0h FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
014100h FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
014110h 11 00 13 00 11 00 ..
```
Take this property from **LEVEL 31** as an example.
We can see that it's definitely a flag property, but doesn't have any of its flags set.
Even stranger however, we can see that it contains data for a bounce pad on the underside of the block, and even has a block position set for it in the next property.
It is unknown why this occurs, but it does not have an affect on the level at all.
## Level Info Property
At the end of the level binary, there is an extra property that contains basic information about the level itself. Most of these values are unknown and do not seem to have an affect on the level at all.
This property has the type **666**, but is not tied to any block.
| Offset(h) | Size | Type | Description |
| --------- | ---- | -------------- | ------------------- |
| +0x00 | 2 | i16 | Property Type (666) |
| +0x02 | 6 | position_block | Last Block Modified |
| +0x08 | 4 | i16[2] | Unknown |
| +0x0C | 2 | i16 | Start Time |
| +0x0E | 242 | - | Padding |
::: info
Some levels do not contain this property, notably the [first alpha Kula Quest demo](#version-differences).
This property is not required, so the game will default to specific values
if this property is not present.
:::
The only confirmed value in this property is the **start time**, which specifies the amount of time the level starts with in seconds.
The game calculates the amount of time by multiplying this value by **50**, which is the PAL's game framerate.
For reference, the game decrements `currentTime` every frame of unpaused gameplay, and ends the level if it reaches 0.
Based on my research, there is evidence to suggest that the position value is leftover metadata based on the **last modified block** in the original level editor used by the developers:
1. A fruit was accidentally placed in Kula World's FINAL 3 level.
When this fruit was removed in the next version of the game, the position value just so happened to update to the removed fruit's block position.
2. A change was made for LEVEL 133 on the Kula Quest release to the green gem where it was moved from the fire block to in front of the key.
The unknown position value also updated to the block that the gem was moved to.
3. In the first level of the game, this position value points to the block that contains the farther right bronze coin, which happened to be moved forward and changed from a gold to a bronze coin from earlier versions of the game.
This is just some examples, but one could reasonably conclude that this position value was likely metadata as it is **never** referenced from within the game's programming.
It's also worth noting that this position value can be set to a block that isn't contained in the level, likely referencing a deleted block.
The two **unknown short** values before the start time value are also ignored in game, but seem to be always in multiples of 5 and sometimes negative.
## Version Differences
The level format remains mostly the same across all versions of the game with slight differences:
- In the alpha release, there is no [level info property](#level-info-property).
- In the beta release, the mere existence of the [level flag property](#level-flag-property) causes the level to be hidden, so no flags are required to be set.
### Level Start Time
In Kula World and Roll Away, the starting level time is calculated by multiplying the field by **50**, which is the framerate of the PAL version:
```c
// SLUS_007.24: 0x35F5C
// currentTime: 0xA573C
// checks if the property type is 666, meaning the level info property exists;
// if so, set currentTime to the property's 6th index (startingTime) * 50.
if (*propertyData == 0x29a) {
currentTime = propertyData[6] * 0x32;
}
// if the level info property doesn't exist, default to 4950.
else {
currentTime = 0x1356;
}
```
_Kula World and Roll Away function the same here, so Roll Away is used as an example._
In Kula Quest, the time is calculated by multiplying this value by **60**, which is the framerate of the NTSC version.
An additional check was added if the value is **5940**, which is the maximum time value (99) and is set to **7140** if so to allow additional level time:
```c
// SCPS_100.64: 0x362C4
// currentTime: 0xA12EC
// checks if the block's type is 666, meaning the level info property exists;
// if so, set currentTime to the property's 6th index (startingTime) * 50.
if (*propertyData == 0x29a) {
currentTime = propertyData[6] * 0x3c;
}
// if the level info property doesn't exist, default to 4950.
else {
currentTime = 0x1be4;
}
// if the currentTime was set to 5940, default to 7140 to allow more time.
if (currentTime == 0x1734) {
currentTime = 0x1be4;
}
```
## Oddities
Most levels in the game have the fruit set to a **Banana** (`0x2E`), as the fruit is automatically set based on how many you have collected, **except** for the first alpha release where the order was set manually.
However, some levels in later versions still have other fruit set, perhaps because they were originally early levels or on accident:
| Release | Level | Fruit |
| -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ---------- |
| Main Releases | `INCA/LEVEL 42` | Watermelon |
| Main Releases | `HILLS/LEVEL 18` | Pumpkin |
| Main Releases | `HILLS/LEVEL 19` | Strawberry |
| Main Releases | `ARCTIC/LEVEL 53` | Pumpkin |
| Main Releases | `FIELD/LEVEL 83` | Apple |
| Main Releases | `HAZE/LEVEL 116` | Watermelon |
| Main Releases | `MARS/LEVEL 122` | Watermelon |
| Kula World and Roll Away | `MARS/HIDDEN 9` | Pumpkin |
| Main Releases | `HELL/LEVEL 137` | Watermelon |
| Main Releases | `HELL/LEVEL 146` | Watermelon |
| Kula World | `HELL/BONUS 30` | Pumpkin |
| [Beta (1998-01-30)](/content/releases#beta) and [Inca Variant](/content/releases#beta-inca-variant) | `LEVEL 1` | Watermelon |
| [Beta (1998-01-30)](/content/releases#beta) and [Inca Variant](/content/releases#beta-inca-variant) | `LEVEL 2` | Strawberry |
| [Beta (1998-01-30)](/content/releases#beta) and [Inca Variant](/content/releases#beta-inca-variant) | `LEVEL 3` | Watermelon |
| [Hyper PlayStation Re-mix 1999 No. 9](/content/releases#hyper-playstation-re-mix-1999-no-9-japan-disc-2-sony-computer-entertainment-special) | `LEVEL 2` | Strawberry |
**LEVEL 125** from Mars is the only level in all releases that contain inaccessible objects hidden by another block, consisting of a couple of fire patches hidden inside a few blocks:
Some levels contain slow moving stars that face the wrong direction, causing them to initially move in the air when the level loads:


In the last bonus level in Kula World, every star is incorrectly set to the **Positive X** direction.
This issue can be observed in the playthrough video _PS1 Kula World 1998 - No Commentary_ by [GameGamer](https://www.youtube.com/@GameGamer), at [2:56:27](https://youtu.be/jBsIFDERgsE?t=10587).

This is also the only level in any release after the alpha that does not contain the [level info property](#level-info-property).
---
In Atlantis, LEVEL 94 is incorrectly spelled "LECEL 94".
For an unknown reason, the captivator and time pause patches have their direction value set to **4** in the **OBJ LEVEL**.
Additionally, no objects have their direction set to 0 except for capture pods, who's direction value [behaves unpredictably](/formats/objects#capture-pod-direction).
---
---
url: /formats/objects.md
---
---
description: 'A database containing every known object and their properties.'
---
# Object Table
{{ $frontmatter.description }}
## Overview
There are many different types of objects in Kula Quest, each with their own set of properties.
Here is a complete table of every known object to exist:
| ID(h) | Name | Direction | Variant | State | Appearance |
| ----- | --------------------- | ----------------------------------- | ---------------------- | --------------------- | ------------ |
| 01 | Fire Patch | 0 | 0 | 0 | - |
| 02 | Ice Patch | 0 | 0 | 0 | - |
| 03 | Invisible Patch | 0 | 0 | 0 | - |
| 04 | Acid Patch | 0 | 0 | 0 | - |
| 05 | Transporter | [Direction](#direction) | [Color](#variant) | [Enabled](#state) | 256, 1, 30 |
| 06 | Unknown | - | - | - | - |
| 07 | Exit | 0 | [Exit Color](#variant) | [Locked](#state) | 500, 1, 30 |
| 08 | Timer Pause | 0 | 0 | 0 | - |
| 09 | Button | 0 | [Color](#variant) | [Enabled](#state) | 256, 0, 0 |
| 0A | Bouncepad | 0 | 0 | [Collectable](#state) | 256, 3, 0 |
| 0B | Moving Spike | 0 | [Sync](#sync) | [Collectable](#state) | 256, 0, 0 |
| 0C | Spike | 0 | 0 | [Collectable](#state) | 256, 0, 0 |
| 0D-19 | Unknown | - | - | - | - |
| 1A | Hidden Exit | 0 | 0 | [Locked](#state) | 500, 1, 30 |
| 1B | Fruit Bowl | 0 | 0 | [Collectable](#state) | - |
| 1C | Arrow | [Direction](#direction) | 0 | [Collectable](#state) | 256, 0, 0 |
| 1D | Player Spawn (Paused) | [Direction](#direction) | 0 | 0 | 256, 0, 0 |
| 1E | Player Spawn | [Direction](#direction) | 0 | 0 | 256, 0, 0 |
| 1F | Key | 0 | 0 | [Collectable](#state) | 386, 1, -100 |
| 20 | Lethargy Pill | 0 | 0 | [Collectable](#state) | 386, 2, 100 |
| 21 | Bouncy Pill | 0 | 0 | [Collectable](#state) | 386, 2, 100 |
| 22 | Invincibility Pill | 0 | 0 | [Collectable](#state) | 386, 2, 100 |
| 23 | Hourglass | 0 | 0 | [Collectable](#state) | 386, 2, 100 |
| 24 | Gem | 0 | [Variant](#variant) | [Collectable](#state) | 386, 1, -17 |
| 25 | Coin | 0 | [Variant](#variant) | [Collectable](#state) | 386, 1, -100 |
| 26 | Sunglasses | 0 | 0 | [Collectable](#state) | 386, 1, -100 |
| 27 | Purple Present | 0 | 0 | [Collectable](#state) | - |
| 28 | Red Present | 0 | 0 | [Collectable](#state) | - |
| 29 | Yellow Present | 0 | 0 | [Collectable](#state) | - |
| 2A | Unused Enemy | 0 | 0 | [Collectable](#state) | 416, 0, 100 |
| 2B | Apple | 0 | 0 | [Collectable](#state) | 386, 1, 100 |
| 2C | Watermelon | 0 | 0 | [Collectable](#state) | 386, 1, 100 |
| 2D | Pumpkin | 0 | 0 | [Collectable](#state) | 386, 1, 100 |
| 2E | Banana | 0 | 0 | [Collectable](#state) | 386, 1, 100 |
| 2F | Strawberry | 0 | 0 | [Collectable](#state) | 386, 1, 100 |
| 30 | Blue Present | 0 | 0 | [Collectable](#state) | - |
| 31 | Green Present | 0 | 0 | [Collectable](#state) | - |
| 32 | Slow Star | [Direction](#direction) | 0 | 0 | - |
| 33 | Tire | [Direction](#direction) | 0 | 0 | - |
| 34 | Fast Star | [Direction](#direction) | 0 | 0 | - |
| 35 | Capture Pod | [Direction](#capture-pod-direction) | 0 | 0 | - |
| 36-37 | Unknown | - | - | - | - |
| 38 | Captivator | 0 | [Sync](#sync) | 0 | - |
## Variant
Several variants for different objects exist, and are actually used under the hood for indexing into a different sub-model for a given object.
Transporters and buttons allow you to change their color using the this field.
Lasers have a dedicated property for their color, but it follows the same structure.
| Object | Value 0 | Value 1 | Value 2 | Value 3 |
| ------------ | ---------------- | ----------------- | ----------------- | ------- |
| Coin | Gold (750 pts.) | Silver (500 pts.) | Bronze (250 pts.) | - |
| Gem | Blue (2975 pts.) | Green (2975 pts.) | Red (2975 pts.) | - |
| Exit | Green (Unlocked) | Red (Locked) | - | - |
| Colors | Yellow | Blue | Green | Red |
| Colors (old) | Blue | Red | Green | Yellow |
For the **first two demos specifically**, they follow a slightly different color structure on the bottom.
### Sync
Some objects and blocks have an option that allows you to specify when their animation occurs in sync with others.
For example, in some levels there are moving spikes that are placed right next to each other that poke out and retract at different times, allowing the player to time their jumps, instead of those animations occurring at the same time.
For flashing blocks, they have a dedicated **sync** value to control this, while moving spikes and captivators use the **variant** field to specify this value.
The sync value ranges from **0 to 5**, as any value after **5** stays in sync together, so it is effectively pointless. However, it's worth noting that with moving spikes specifically, setting its sync value to **5** causes the spikes to become invisible and may cause other unexpected behavior, so it is recommended to keep the sync value in a range from **0 to 4** for **moving spikes specifically!** For captivators, the sync value is also in range from **0 to 4**, though nothing larger will cause any unexpected behaviors.
## State
Most objects use this value in memory to determine whether it has been collected or not if it's a collectable object:
| Object | Value 0 | Value 1 | Value 2 |
| ------------------------ | --------- | ------------------------- | -------------------- |
| Transporters and Buttons | - | Enabled | Disabled |
| Exits | - | Unlocked | Locked (set in file) |
| Collectables | Collected | Uncollected (set in file) | - |
::: tip Important
Exits and collectables must have their states set to 2 and 1 in the file respectively as shown in the table for them to work properly.
:::
Transporters and buttons use this value to determine whether they're enabled. Any other value defaults to disabled, but the state will be set to 1 when pressed again.
For exits, any other value defaults to locked.
## Direction
Many objects that allow you to control what direction they face.
For example, enemies allow you to control what direction they start moving in when the level is played based on this value, and other objects as the player spawn allow you to specify what direction the player spawns in.
Depending on the side of the block the object is placed on, you can specify the direction based on this table:
| Block Face | Value 1 | Value 2 | Value 3 | Value 4 |
| -------------- | ---------- | ---------- | ---------- | ---------- |
| **Positive X** | Positive Z | Positive Y | Negative Z | Negative Y |
| **Negative X** | Positive Z | Negative Y | Negative Z | Positive Y |
| **Positive Y** | Positive Z | Negative X | Negative Z | Positive X |
| **Negative Y** | Positive Z | Positive X | Negative Z | Negative X |
| **Positive Z** | Positive Y | Positive X | Negative Y | Negative X |
| **Negative Z** | Negative Y | Positive X | Positive Y | Negative X |
::: info
Any other value defaults to the value **1**, and the direction for transporters specify what direction the player will face when exited out of that transporter.
:::
### Capture Pod Direction
For an unknown reason, **capture pods** still move in a _random_ direction even if multiple in a level are set to the same direction.
It is not recommended to set its direction, and in every level they are actually set to either **0** or **4** in all releases.
## Appearance
In the alpha and beta demos for the game, objects contain 3 additional values used for setting how the object spins and how far it is off the ground.
Here is what the 3 values refer to under the **Appearance** column:
- Ground offset
- Rotation type
- `0`: None
- `1`: Relative Y axis
- `2`: Relative Z axis
- `3`: Relative X axis
- Rotation speed
- The direction it spins on the axis depends on whether the value is positive or negative.
---
---
url: /formats/pak.md
---
---
description: 'A custom archive format for storing multiple files with compression.'
---
# Pak Format
{{ $frontmatter.description }}
This document contains information about the format structure of Pak files.
If you are interested in using tools to create your own, please visit [here](../tools/quilt.md).
## Overview
Kula Quest uses a custom archive format for storing multiple compressed files into one file, similar to [**.ZIP**]() files.
This file format is known as **.PAK**, and is primarily used for storing levels in a world, though it is used for other purposes such as storing HUD textures and demo completion screenshots.
All values are in [**little endian**](https://en.wikipedia.org/wiki/Endianness), and the following data types will be used:
| Encoding | Description |
| -------- | ----------------------- |
| u32 | Unsigned 32-bit integer |
### Structure
The format is comprised of the following structure:
- File count
- Offset and compressed size of each file
- Offset to each filename
- Filenames
- Compressed file data
## Header
The first 4 bytes in the file (**note** there is no magic header) specify how many compressed files are inside the archive:
| Offset(h) | Size | Type | Field | Field | Description |
| --------- | ---- | ---- | ---------- | ---------- | ------------------------------ |
| 0x00 | 4 | u32 | file_count | file_count | Number of files in the archive |
## File Table
Starting at offset 0x04, each file entry is 8 bytes:
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | --------------------------------------- |
| +0x00 | 4 | u32 | Absolute offset to compressed file data |
| +0x04 | 4 | u32 | Size of compressed file in bytes |
## File Names
Offsets for each filename immediately follow after the file table:
| Offset(h) | Size | Type | Description |
| ------------------------ | --------------- | ----- | --------------------------------------------- |
| 0x04 + (file_count \* 8) | 4 \* file_count | u32[] | Array of absolute offsets to filename strings |
Each filename is a null-terminated string with a newline character:
| Component | Size | Description |
| --------------- | ---- | -------------------------------- |
| Filename | - | ASCII filename (e.g., "LEVEL 1") |
| Line Feed | 1 | 0x0A (newline character) |
| Null Terminator | 1 | 0x00 (end of string) |
## File Data
The files are compressed with [**zlib**](https://zlib.net/), an old and commonly used compression algorithm.
Each buffer starts with the zlib header `78 9C`.
## Oddities
Some Pak files contain residual data after the filenames:
| File | Garbage Data | ASCII |
| ----------------------- | ------------ | ------ |
| FIELDFI.PAK, HAZEFI.PAK | 4D 4F 4E 20 | "MON " |
| HILLSFI.PAK | 53 49 | "SI" |
These fragments appear to be remnants from the **SIMON** naming convention found in `COPYCAT.PAK`.
They serve no functional purpose and appear to be padding artifacts that align data to 4-byte boundaries.
---
---
url: /formats/sfx.md
---
---
description: 'A custom binary format for storing sound information.'
---
# SFX Format
{{ $frontmatter.description }}
This document contains information about the format structure of SFX files.
If you are interested in using tools to create your own, please visit [here](../tools/mksfx.md).
## Overview
A custom binary format is used for storing every single sound effect used in the game.
The format is identical across all versions of the game, with each sound consisting of raw **PSX ADPCM** audio data and an associated pitch value that determines playback frequency.
All values are in [**little endian**](https://en.wikipedia.org/wiki/Endianness), and the following data types will be used:
| Encoding | Description |
| -------- | ----------------------- |
| u32 | Unsigned 32-bit integer |
### Structure
The format is comprised of the following structure:
- Sound count
- Offset and pitch value for each sound
- Sound data
## Header
The first 4 bytes in the file (**note** there is no magic header) specify how many compressed files are inside the archive:
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | ----------------------- |
| 0x00 | 4 | u32 | Number of sound entries |
## Sound Table
Starting at offset 0x04, each sound entry is 8 bytes:
| Offset(h) | Size | Type | Description |
| --------- | ---- | ---- | ------------------------------------------- |
| +0x00 | 4 | u32 | Absolute offset to audio data for the sound |
| +0x04 | 4 | u32 | Pitch value |
## Audio Data
Raw PSX ADPCM compressed audio data begins at the specified offsets.
Each sound's data continues until the next sound's offset (or end of file for the last sound).
Every sound uses **1 channel** (Mono).
## Pitch Value
The 4-byte pitch value encodes musical note information used by the PlayStation SPU (Sound Processing Unit) to determine playback frequency.
```
pitch_value = (note << 8) | fine_tune
```
- Upper 24 bits: Base musical note offset
- Lower 8 bits: Fine pitch adjustment (0-255)
---
---
url: /formats/sounds.md
---
---
description: 'Tables containing every sound effect and their pitch value from multiple releases.'
---
# Sound Table
{{ $frontmatter.description }}
## Main Releases
Here is the sound table used in both **Kula World** and **Roll Away**:
| Index | Name | Pitch Value |
| ----- | --------------------- | ----------- |
| 1 | Level Load | 6400 |
| 2 | Bounce Pill | 3328 |
| 3 | Crumble Block | 3328 |
| 4 | Transporter | 6400 |
| 5 | Hourglass | 8704 |
| 6 | Ball Bounce | 8704 |
| 7 | Gem Collection | 7936 |
| 8 | Key Collection | 9216 |
| 9 | Coin Collection | 9216 |
| 10 | Button Press | 7936 |
| 11 | Button Depress | 7936 |
| 12 | Ice Patch | 8704 |
| 13 | Moving Platform | 3328 |
| 14 | Lethargy Pill | 3328 |
| 15 | Fruit | 6400 |
| 16 | Bounce Pad | 7936 |
| 17 | Moving Spike Extend | 7936 |
| 18 | Moving Spike Retract | 6400 |
| 19 | Menu Selection | 6400 |
| 20 | Spiked | 6400 |
| 21 | Sunglasses Collection | 6400 |
| 22 | Game Over | 6400 |
| 23 | Loading | 6400 |
| 24 | Fire Patch | 6400 |
| 25 | Capture Pod | 3328 |
| 26 | Hourglass Tick | 3328 |
| 27 | Captured | 6400 |
| 28 | **Unknown** | 6400 |
| 29 | **Unknown** | 6400 |
| 30 | Last Key Collection | 8704 |
| 31 | Captivator | 6400 |
| 32 | Fast-moving Star | 9216 |
| 33 | Bonus Tile | 6400 |
In **Kula Quest**, the unused **sound 29** was replaced with a level completion sound affect, as well as more sounds being added:
| Index | Name | Pitch Value |
| ----- | --------------------------- | ----------- |
| 29 | Level Completion (Non-100%) | 5632 |
| 34 | Level Completion (100%) | 1792 |
| 35 | **Unknown** | 1792 |
| 36 | Time out | 1792 |
| 37 | CopyCat Wrong Move | 1792 |
## Previous Versions
In the **alpha** release of the game, only sounds up to the **sunglasses collection** were present.
However, the bounce pill sound effect used to sound completely different:
In the **beta** release of the game, only sounds up to the **hourglass tick** were present, and the capture pod used to also have a completely different sound effect:
It's unknown if these original sound effects were intended to be used for the bounce pill and capture pod as they go unused, it's just an assumption as they're placed in the same slots as the final version.
## Japanese Demos
In the following Japanese Kula Quest demos, the SFX file is sightly different. The level completion 100% sound is swapped with the non-100% completion sound, and the non-100% completion sound was changed to a completely new sound:
- Kula Quest Taikenban
- Famitsu Wave 6gatsu-gou Vol. 11
- Hyper PlayStation Re-mix 1999 No. 6
- Dengeki PlayStation D19
---
---
url: /formats/tgi.md
---
---
description: 'A custom binary format for storing theme information.'
---
# TGI Format
{{ $frontmatter.description }}
This format is still under heavy research!
## Overview
| Offset(h) | Size(h) | Type | Description |
| --------- | ------- | ----- | ---------------------- |
| 0x00000 | 0x00190 | u32[] | Header |
| 0x00190 | 0x037B0 | u32[] | Mipmap CLUT index list |
## Header
| Offset(h) | Size(h) | Type | Description |
| --------- | ------- | ---------------- | ---------------------------------------- |
| 0x00 | 0x1C | u32[7] | Unknown |
| 0x1C | 0x0C | channel_modifier | Channel modifier for **neutral** models |
| 0x28 | 0x0C | channel_modifier | Channel modifier for **light** models |
| 0x34 | 0x0C | channel_modifier | Channel modifier for **dark** models |
| 0x40 | 0x0C | channel_modifier | Channel modifier on **left** of blocks |
| 0x4C | 0x0C | channel_modifier | Channel modifier on **right** of blocks |
| 0x58 | 0x0C | channel_modifier | Channel modifier on **front** of blocks |
| 0x64 | 0x0C | channel_modifier | Channel modifier on **back** of blocks |
| 0x70 | 0x0C | channel_modifier | Channel modifier on **top** of blocks |
| 0x7C | 0x0C | channel_modifier | Channel modifier on **bottom** of blocks |
| 0x88 | 0x20 | u32[8] | Unknown |
| 0xA8 | 0x04 | u32 | Light index on **top** of blocks |
| 0xAC | 0x04 | u32 | Light index on **right** of blocks |
| 0xB0 | 0x04 | u32 | Light index on **front** of blocks |
| 0xB4 | 0x04 | u32 | Light index on **back** of blocks |
| 0xB8 | 0x04 | u32 | Light index on **left** of blocks |
| 0xBC | 0x04 | u32 | Light index on **bottom** of blocks |
| ... | ... | ... | ... |
| 0xE4 | 0x04 | u32 | Offset to palette indices |
| 0xE8 | 0x04 | u32 | Offset to model fog |
| 0xE8 | 0x04 | u32 | Offset to block fog |
| 0xE8 | 0x04 | u32 | Offset to tileset constants 1 |
| 0xE8 | 0x04 | u32 | Offset to tileset constants 2 |
| 0xE8 | 0x04 | u32 | Offset to 8-bit CLUT data |
| 0xE8 | 0x04 | u32 | Offset to VRAM constants |
| 0xE8 | 0x04 | u32 | Total size of file |
### Channel Modifier
Channel modifiers are used for object models so that objects that face away, towards, or are neutral to the sun are dark, neutral, and lightened respectively.
They can also be set for each side of a block as well.
| Offset(h) | Size(h) | Type | Description |
| --------- | ------- | ---- | ------------- |
| +0x00 | 0x04 | u32 | Red channel |
| +0x04 | 0x04 | u32 | Green channel |
| +0x08 | 0x04 | u32 | Blue channel |
### Light Index
The light index specify which sides of the block should be neutral, light, or dark, based on what sides face towards or away from the sun:
- `0`: Neutral
- `1`: Light
- `2`: Dark
---
---
url: /formats/xa.md
---
---
description: 'A common binary format for storing soundtracks in PlayStation 1 games.'
---
# XA Format
{{ $frontmatter.description }}
## Soundtrack Tables
Every XA file in Kula Quest contains 4 tracks, ordered by duration, even if it's the same track interleaved 4 times.
Below are tables documenting each soundtrack in each XA file from the main releases.
### Main Releases
**Kula World**:
| File | Track 1 | Track 2 | Track 3 | Track 4 |
| ------------ | -------------- | --------------- | -------------- | -------------- |
| `MUSIC_0.XA` | BONUS 1 • 2:54 | BONUS 1 • 2:54 | BONUS 1 • 2:54 | BONUS 1 • 2:54 |
| `MUSIC_1.XA` | BONUS 2 • 2:57 | HIRO • 4:01 | MARS • 4:10 | HELL • 4:14 |
| `MUSIC_2.XA` | FIELD • 4:36 | BONUS 3 • 4:52 | INCA • 5:01 | ARCTIC • 5:08 |
| `MUSIC_3.XA` | HAZE • 5:14 | ATLANTIS • 5:18 | HILLS • 6:05 | COWBOY • 6:21 |
In **Roll Away** and **Kula Quest**, the tracks are slightly shorted and the BONUS 1 and BONUS 2 tracks are switched:
| File | Track 1 | Track 2 | Track 3 | Track 4 |
| ------------ | -------------- | --------------- | -------------- | -------------- |
| `MUSIC_0.XA` | BONUS 1 • 2:52 | BONUS 1 • 2:52 | BONUS 1 • 2:52 | BONUS 1 • 2:52 |
| `MUSIC_1.XA` | BONUS 2 • 2:50 | HIRO • 3:56 | MARS • 4:05 | HELL • 4:09 |
| `MUSIC_2.XA` | FIELD • 4:31 | BONUS 3 • 4:46 | INCA • 4:56 | ARCTIC • 5:03 |
| `MUSIC_3.XA` | HAZE • 5:09 | ATLANTIS • 5:13 | HILLS • 6:00 | COWBOY • 6:16 |
---
---
url: /tools/mksfx.md
---
---
description: 'A command-line utility for modifying SFX files from Kula Quest.'
---
# mksfx
{{ $frontmatter.description }}
## Overview
Kula Quest uses a custom binary format known as [**.SFX**](/formats/sfx) for storing sound effects used in gameplay.
There is only one of these files on a disc, and **mksfx** can extract its contents as **WAV** files, as well as build them from a configuration file.
## Installation
The latest pre-compiled binaries are available for download on [GitHub](https://github.com/KulaWorkshop/mksfx/releases/).
## Usage
To extract an SFX file, use the **extract** command followed by its path and an output directory:
```bash
$ mksfx extract "HIRO.SFX" "output"
```
This will extract the sound files from the SFX file into a directory, and will also save a **yaml configuration** file in the directory:
```yaml
sounds:
- filename: sound001.wav
pitch_value: 6400
- filename: sound002.wav
pitch_value: 3328
- filename: sound003.wav
pitch_value: 3328
- filename: sound004.wav
pitch_value: 6400
- filename: sound005.wav
pitch_value: 8704
...
```
You can make any adjustments to the config file or audio files as you'd like, just **be sure** that you encode your custom WAV files properly and that you update the pitch value if needed (See the [FFmpeg](#ffmpeg) section for more information).
To create an SFX file, use the **build** command followed by the path to create it and the path to a config file:
```bash
$ mksfx build "CUSTOM.SFX" "output/build.yaml"
```
::: tip Important
**Note**: Make sure that your sound files are in the same directory as the config file.
:::
If you would like to extract the sounds as raw **SPU-ADPCM** files, you can use the `--format raw` flag:
```bash
$ mksfx extract "HIRO.SFX" "output" --format raw
```
When importing a raw sound, be sure to set `format: raw` in the config file for that sound:
```yaml
- filename: sound010.raw
format: raw
pitch_value: 7936
```
## FFmpeg
To import a custom sound file, it must be prepared in the correct format.
[FFmpeg](https://en.wikipedia.org/wiki/FFmpeg) is a popular tool used for converting audio files, and can be downloaded [here](https://www.ffmpeg.org/download.html) for your platform.
Only the `ffmpeg` executable is needed for this process.
The following command will encode an audio file, in this case a `coin.mp3` file as an example, into the required format for mksfx — a **mono** (1 audio channel) **signed 16-bit PCM WAV** file:
```bash
$ ffmpeg -i "coin.mp3" -ac 1 -ar 22050 -sample_fmt s16 "output_coin.wav"
```
This command will also set the sample rate to **22050hz**, so when importing this sound be sure to set the pitch value to **6040**.
Here is a table with some common sample rates and their associated pitch value:
| Sample Rate | Pitch Value |
| ----------- | ----------- |
| 11681hz | 3328 |
| 22050hz | 6040 |
| 23363hz | 6400 |
| 33042hz | 7936 |
| 39287hz | 8704 |
| 44100hz | 9216 |
In most cases, just using the example FFmpeg command above with a pitch value of 6040 will be sufficient for custom sounds.
---
---
url: /tools/quilt.md
---
---
description: 'A command-line utility for modifying archive files and compression used in Kula Quest.'
---
# Quilt
{{ $frontmatter.description }}
## Overview
Kula Quest uses custom archive formats for storing multiple compressed files into one file, similar to [**.ZIP**]() files.
The most common of these formats are [**.PAK**](/formats/pak) files, and are primarily used for **storing levels** associated with a world, though they are used for other purposes such as storing HUD textures and demo completion screenshots.
In the first demo release of the game, a slightly different [**.KUB**](/formats/kub) format is used instead of Pak files, which does not preserve filenames and uses a different compression algorithm.
It is **important** that when dealing with level archives, that you take note of how they're structured depending on what version of the game you're targeting.
For example, the following structure is used in the 3 primary releases of the game:
- The first 15 regular levels
- The 3 bonus levels
- The hidden level
- An unused object level
If you would like to create a custom Pak file for the main releases, and you would like bonus and hidden levels to work properly, then you **must** create a Pak file with the levels in the order seen above.
The same applies for creating Pak files from demo releases of the game.
## Installation
The latest pre-compiled binaries are available for download on [GitHub](https://github.com/KulaWorkshop/Quilt/releases/).
## Archives
To extract an archive file, use the **unpack** command followed by the path to the file and an output folder for its contents.
The following example command will extract the files inside of `HIRO.PAK` into a folder called `levels`:
```bash
$ quilt unpack "HIRO.PAK" "levels"
```
To create an archive, use the **pack** command followed by the path to create it and a list of files to use:
```bash
$ quilt pack "LEVELS.PAK" "LEVEL_1" "LEVEL_2" "LEVEL_3"
```
By default, Quilt will create a **.PAK** file.
Use the `-k` flag to set the creation type to **.KUB**:
```bash
$ quilt pack -k "LEVELS.KUB" "LEVEL_1" "LEVEL_2" "LEVEL_3"
```
### Using Text Files
When dealing with archives that contain many files, or for quick rebuilding of an archive after making adjustments to its contents, you can use a text file containing the files that you would like to use and their order.
You can generate this text file automatically when unpacking an archive, using the `-s` flag:
```bash
$ quilt unpack -s "HIRO.PAK" "levels"
```
This example command will unpack the contents of `HIRO.PAK`, and will additionally save a text file inside of the `levels` folder named `HIRO.PAK.txt`:
::: code-group
```:line-numbers [HIRO.PAK.txt]
LEVEL 1
LEVEL 2
LEVEL 3
LEVEL 4
LEVEL 5
LEVEL 6
LEVEL 7
LEVEL 8
LEVEL 9
LEVEL 10
LEVEL 11
LEVEL 12
LEVEL 13
LEVEL 14
LEVEL 15
BONUS 1
BONUS 2
BONUS 3
HIDDEN 1
LESSON
```
:::
The text file format is simple — one filename per line, in the order they should appear in the archive.
Instead of having to specify all of these files to build an archive, you can use the **@** parameter following the path of the text file:
```bash
$ quilt pack "HIRO.PAK" "@levels/HIRO.PAK.txt"
```
::: tip Important
Make sure that your files are inside the same folder as the text file.
:::
## Alpha Compression
In the first demo of the game, the [.TGI](../formats/tgi.md) and [.GGI](../formats/ggi.md) files are both fully compressed using the [**lzrw3a**](http://www.ross.net/compression/lzrw3a.html) algorithm.
Quilt allows you to decompress and recompress these files using the following examples below.
For decompression, the following commands can be used:
```bash
$ quilt decompress "KULA.TGI" "KULA.decompressed.TGI"
$ quilt decompress "KULA.GGI" "KULA.decompressed.GGI"
```
If you would like to recompress the file to put it back into the game, the following commands can be used:
```bash
$ quilt compress "KULA.decompressed.TGI" "KULA.TGI"
$ quilt compress "KULA.decompressed.GGI" "KULA.GGI"
```
---