Difference between revisions of "Flipnote Files/PPM"
(12 intermediate revisions by 5 users not shown) | |||
Line 2: | Line 2: | ||
/private/ds/app/4B475556/001 - European location | /private/ds/app/4B475556/001 - European location | ||
+ | /private/ds/app/4B475545/001 - North American location | ||
You can save your images in a user folder as long as it's on the same level as the 001 folder. Clicking on the Choose Folder icon on the top of the View Flipnote / SD Card option gives you a list of normal folders, and you can swap to user folders by selecting the normal button again. This could possibly be to overcome a limitation in one of the files (possibly the dirmemo2.lst file). | You can save your images in a user folder as long as it's on the same level as the 001 folder. Clicking on the Choose Folder icon on the top of the View Flipnote / SD Card option gives you a list of normal folders, and you can swap to user folders by selecting the normal button again. This could possibly be to overcome a limitation in one of the files (possibly the dirmemo2.lst file). | ||
Line 206: | Line 207: | ||
|- | |- | ||
| 0000 | | 0000 | ||
− | | | + | | 2 |
| Size of the offset table | | Size of the offset table | ||
+ | |- | ||
+ | | 0002 | ||
+ | | 2 | ||
+ | | Padding | ||
|- | |- | ||
| 0004 | | 0004 | ||
Line 310: | Line 315: | ||
if( useByte == 0 ) // Set the full line to the current paper colour | if( useByte == 0 ) // Set the full line to the current paper colour | ||
+ | { | ||
memset( outB, paper, 256 ); | memset( outB, paper, 256 ); | ||
+ | } | ||
else // Bytes in this line, read and deal with them | else // Bytes in this line, read and deal with them | ||
{ | { | ||
Line 323: | Line 330: | ||
{ | { | ||
if( ( data & 0x01 ) == 0x01 ) | if( ( data & 0x01 ) == 0x01 ) | ||
+ | { | ||
outB[ x ] = invflag == 0 ? pen : paper; | outB[ x ] = invflag == 0 ? pen : paper; | ||
+ | } | ||
else | else | ||
+ | { | ||
outB[ x ] = invflag == 0 ? paper : pen; | outB[ x ] = invflag == 0 ? paper : pen; | ||
− | + | } | |
x ++; | x ++; | ||
Line 342: | Line 352: | ||
if( x < 256 ) | if( x < 256 ) | ||
+ | { | ||
memset( &outB[ x ], paper, 256 - x ); | memset( &outB[ x ], paper, 256 - x ); | ||
+ | } | ||
} | } | ||
Line 358: | Line 370: | ||
|- style="background-color: #ddd;" | |- style="background-color: #ddd;" | ||
! Length | ! Length | ||
+ | ! Function | ||
! Description | ! Description | ||
|- | |- | ||
− | | | + | | Framecount |
− | | Sound effect usage. Each byte | + | | Sound effect usage. |
+ | | Each byte represents a frame. bit #1(from Right to Left) represents Sound effect 1, bit #2 represents Sound effect 2 and bit #3 represents Sound effect 3. the last 5 bits is padding. | ||
|- | |- | ||
− | | | + | | 0 - 3 |
− | | Padding | + | | Padding |
+ | | 0x00 * (0 to 3). Makes the offset to the next value dividable by 4. | ||
|- | |- | ||
| 4 | | 4 | ||
− | | Size of | + | | Size of the background music. |
+ | | The size of the background music in bytes in the audio data below. 0x0 if not used/empty. | ||
|- | |- | ||
| 4 | | 4 | ||
− | | Size of | + | | Size of sound effect #1 |
+ | | The size of the sound effect #1 in bytes in the audio data below, max size is 0x2000. 0x0 if not used/empty. | ||
|- | |- | ||
| 4 | | 4 | ||
− | | Size of | + | | Size of sound effect #2 |
+ | | The size of the sound effect #3 in bytes in the audio data below, max size is 0x2000. 0x0 if not used/empty. | ||
|- | |- | ||
| 4 | | 4 | ||
− | | Size of | + | | Size of sound effect #3 |
+ | | The size of the sound effect #3 in bytes in the audio data below, max size is 0x2000. 0x0 if not used/empty. | ||
+ | |- | ||
+ | | 1 | ||
+ | | Current Framespeed. | ||
+ | | This is the speed of the flipnote. It's in the range of 1-8. To get the correct value, you must take 8, and subtract the decimal value of this byte. | ||
|- | |- | ||
| 1 | | 1 | ||
− | | | + | | Given Framespeed when BGM was recorded. |
+ | | This is the speed of the flipnote when the BGM was recorded/modified. It's in the range of 1-8. This is used to resample the sound to the new speed. To get the correct value, you must take 8, and subtract the decimal value of this byte. | ||
|- | |- | ||
− | | | + | | 14 |
− | | | + | | Padding |
+ | | 0x00 * 14 | ||
|} | |} | ||
− | + | Then comes the audio data. | |
+ | |||
+ | *First comes the BGM(if used) | ||
+ | |||
+ | *Then comes sound effect #1(if used) | ||
+ | |||
+ | *Then comes sound effect #2(if used) | ||
+ | |||
+ | *Then comes sound effect #3(if used) | ||
+ | |||
+ | |||
+ | The sound data is 4bit ADPCM with reversed nibbles @ 8kHz. | ||
+ | |||
+ | You can decode the sound yourself with "sox -t ima -N [input] [output]" | ||
+ | |||
+ | ==Signature== | ||
+ | |||
+ | The last 0x10-bytes in a PPM are all-zero. The 0x80 bytes before that is a RSA-1024 SHA-1 signature over the whole PPM, excluding the last 0x90 bytes with the signature. |
Latest revision as of 20:39, 8 January 2017
Flipnotes animation files are normally stored on the SD card in the following folder:
/private/ds/app/4B475556/001 - European location /private/ds/app/4B475545/001 - North American location
You can save your images in a user folder as long as it's on the same level as the 001 folder. Clicking on the Choose Folder icon on the top of the View Flipnote / SD Card option gives you a list of normal folders, and you can swap to user folders by selecting the normal button again. This could possibly be to overcome a limitation in one of the files (possibly the dirmemo2.lst file).
File Header
Start | Length | Description |
---|---|---|
0x0000 | 4 | Magic (should be PARA) |
0x0004 | 4 | Size of animation data |
0x0008 | 4 | size of audio data |
0x000C | 2 | # of frames |
0x000E | 2 | Unknown - all files looked at so far has this field set to 24 00 hex. |
0x0010 | 2 | Lock - 0 open, 1 locked |
0x0012 | 2 | Preview frame number |
0x0014 | 22 | Original author name (UCS-2) |
0x002A | 22 | Last Edited By Author name (UCS-2) |
0x0040 | 22 | User name (UCS-2) |
0x0056 | 8 | Original author ID |
0x005E | 8 | Edit Author ID - the last user to save the file |
0x0066 | 18 | Original File-name - see notes on format |
0x0078 | 18 | File-name - see notes on format |
0x008A | 8 | Previous Editing Author ID |
0x0092 | 8 | Partial File name? - see notes on format |
0x009A | 4 | Date stored as the number of seconds since midnight 1 Jan 2000. |
0x009E | 2 | Filler - 00 00 |
0x00A0 | 1536 | Preview Bitmap (4 bits/pixel). |
The file name seems to be stored in the file header 3 times:
- Original file name 0x0066
- Current file name 0x0078
- Partial file name 0x0092
The file name is stored in 3 parts:
- 3 hex bytes, the first byte's high nibble is not the same as the first byte of the file name
- 13 bytes for the central part of the file name
- 2 bytes for the last part of the file name.
For the file name : G35B20_0909841CDBEB1_002
- First location: D3 5B 20 30 39 30 39 38 34 31 43 44 42 45 42 31 00 00
- Second location: D3 5B 20 30 39 30 39 38 34 31 43 44 42 45 42 31 02 00
- Last location: D3 5B 20 09 09 84 1C DB
The 3 sections of the file name seem to be stored as:
- Last 6 digits of your Flipnote Studio ID
- A random number, possibly generated from a date stamp
- The version number of the file
Palette
Hex | Color | DS RGB Approximation |
---|---|---|
0x0 | Not used / White | 31, 31, 31 |
0x1 | Dark Grey | 10, 10, 10 |
0x2 | White | 31, 31, 31 |
0x3 | Light Grey | 20, 20, 20 |
0x4 | Pure Red | 31, 0, 0 |
0x5 | Dark Red | 15, 0, 0 |
0x6 | Light Red / Pink | 31, 15, 15 |
0x7 | Pure Green | 0, 31, 0 |
0x8 | Pure Blue | 0, 0, 31 |
0x9 | Dark Blue | 0, 0, 15 |
0xA | Light Blue | 15, 15, 31 |
0xB | Pure Green | 0, 31, 0 |
0xC | Magenta / Purple | 31, 0, 31 |
0xD | Pure Green | 0, 31, 0 |
0xE | Pure Green | 0, 31, 0 |
0xF | Pure Green | 0, 31, 0 |
The preview image is a 64x48 image stored in a 16 colour tile format, with each 8x8 tile taking up 32 bytes of the file.
Here's a photo taken of the altered preview image, reduced to about the correct size.
Link to the original picture, warning image is 2,048 × 1,536.
Here's two preview images that blasty decoded from some PPM files.
You can use pbsds's PPMtool to extract the preview image yourself
Animation Data Section
The animation section starts at offset 0x06A0, with a header section.
Animation Sequence Header
Start | Length | Description |
---|---|---|
0000 | 2 | Size of the offset table |
0002 | 2 | Padding |
0004 | 4 | Unknown |
0008 | 4 * number of frames | A list of offsets to the frames in the order to play them. |
The offsets are all relative to the end of the offset list, and the frames are not stored in any specific format in the file, so you have to read this table for the next offset as I've seen the list point to a frame at 00023572 and the next frame was at 00000000.
Animation Frame
Start | Length | Description |
---|---|---|
0000 | 1 | Pen and Paper information |
0001 | 48 | Layer 1 line encoding |
0031 | 48 | Layer 2 line encoding |
0061 | ? | The frame data stored layer 1 then layer 2 |
The pen and paper byte at the start is encoded as follows:
The paper bit indicates what color the paper is - 0 for black and 1 for white.
The Frame encoding is 0 for a change between last frame and this one, and 1 for a totally new frame.
The Layer 1 pen and Layer 2 pen information is as follows:
Number | Meaning |
---|---|
0 | Not used |
1 | Invert Paper (black on white or white on black) |
2 | Red |
3 | Blue |
Line encoding is compacted from the 192 lines on the screen down to 48 bytes per layer, each byte within the layer encoding section is stored as 2 bits per line, and 4 lines to the byte. The lines are stored in the same order as the bits in the byte, i.e. line 0 uses bit 0-1, line 1 uses bit 2-3, line 2 uses bit 4-5, line 3 uses bit 6-7. The value for the line encoding determins how many bytes you need to read at a minimum:
Number | Meaning |
---|---|
0 | Skip this line - there is no data for this line |
1 | Coded line |
2 | Inverted coded line |
3 | Raw line data |
Before I explain how to read the line data you must understand that the way flipnote works is in layers, each layer is a bit map of on or off, with on being the pen colour, and off being the paper colour.
The coded and inverted coded lines are stored with a 4 byte header that is used to indicate how many extra bytes to read, and what part of the line they represent. Each byte after the first 4 is then read in a reverse order, i.e. pixel 0 of 8 is bit 0, etc. To understand this you first take the 256 pixels of the line, and divide it by 8 to give 32, this is the maximum number of bytes a line encoding will ever use, and it's also the number of bits in 4 bytes. The first 8 pixels of the line are used only when the bit 0x80000000 is set in the initial 4 bytes.
For example the line is encoded as type 1, and the bytes that's stored is 80 00 00 00 20.
The first 4 bytes is indicating that there is 1 byte effacted in this line, and that it's the first 8 pixels in the line. The next byte is read and once it's expanded the 6'th byte of the line is set to the pen colour. If on the other hand the line was encoded with type 2, that one pixel would be the paper colour and every other pixel would be the pen colour.
Here's some code to show how to decode the line:
u32 decodeLine( u8 *outB, u8 *inB, u32 useByte, u8 paper, u8 pen, u8 invflag )
{
u32 offset = 0;
u16 x = 0, i;
u8 data;
if( useByte == 0 ) // Set the full line to the current paper colour
{
memset( outB, paper, 256 );
}
else // Bytes in this line, read and deal with them
{
while( useByte != 0 )
{
if( ( useByte & 0x80000000 ) == 0x80000000 )
{
// This byte exists...
data = inB[ offset++ ];
for( i = 0; i < 8; i ++ )
{
if( ( data & 0x01 ) == 0x01 )
{
outB[ x ] = invflag == 0 ? pen : paper;
}
else
{
outB[ x ] = invflag == 0 ? paper : pen;
}
x ++;
data >>= 1;
}
}
else
{
memset( &outB[ x ], paper, 8 );
x += 8;
}
useByte <<= 1;
}
if( x < 256 )
{
memset( &outB[ x ], paper, 256 - x );
}
}
return offset;
}
The outB and inB are pointers to pre-allocated memory blocks, the inB is a pointer to the file, and the outB is the output data block. The useByte is the 4 bytes that is at the start of the line, if your reading a type 3 line (full raw data) just pass in 0xFFFFFFFF. The pen and paper values are what was read from the pen and paper information block.
Sound data section
The position of the sound header is 0x6A0 + AnimationDataSize
Length | Function | Description |
---|---|---|
Framecount | Sound effect usage. | Each byte represents a frame. bit #1(from Right to Left) represents Sound effect 1, bit #2 represents Sound effect 2 and bit #3 represents Sound effect 3. the last 5 bits is padding. |
0 - 3 | Padding | 0x00 * (0 to 3). Makes the offset to the next value dividable by 4. |
4 | Size of the background music. | The size of the background music in bytes in the audio data below. 0x0 if not used/empty. |
4 | Size of sound effect #1 | The size of the sound effect #1 in bytes in the audio data below, max size is 0x2000. 0x0 if not used/empty. |
4 | Size of sound effect #2 | The size of the sound effect #3 in bytes in the audio data below, max size is 0x2000. 0x0 if not used/empty. |
4 | Size of sound effect #3 | The size of the sound effect #3 in bytes in the audio data below, max size is 0x2000. 0x0 if not used/empty. |
1 | Current Framespeed. | This is the speed of the flipnote. It's in the range of 1-8. To get the correct value, you must take 8, and subtract the decimal value of this byte. |
1 | Given Framespeed when BGM was recorded. | This is the speed of the flipnote when the BGM was recorded/modified. It's in the range of 1-8. This is used to resample the sound to the new speed. To get the correct value, you must take 8, and subtract the decimal value of this byte. |
14 | Padding | 0x00 * 14 |
Then comes the audio data.
- First comes the BGM(if used)
- Then comes sound effect #1(if used)
- Then comes sound effect #2(if used)
- Then comes sound effect #3(if used)
The sound data is 4bit ADPCM with reversed nibbles @ 8kHz.
You can decode the sound yourself with "sox -t ima -N [input] [output]"
Signature
The last 0x10-bytes in a PPM are all-zero. The 0x80 bytes before that is a RSA-1024 SHA-1 signature over the whole PPM, excluding the last 0x90 bytes with the signature.