NCER format and plugin

26 views
Skip to first unread message

meta...@gmail.com

unread,
Feb 4, 2015, 8:25:00 AM2/4/15
to ti...@googlegroups.com
Hi
After any changes in NCER file (after press SAVE button in OAM Editor), plugin change file structure:

1) value ncer.cebk.unknown1 change to 0x0
2) tile offsets in oam objects (in obj2) correctly modified.

But several games incorrect used this files. Apparently, the program arm9 different open specific NCER-files, and use different methods to open.

About ncer.cebk.unknown1:

This is offset to "partition data", relativity CEBK block position + 8 (also ncer.cebk.constant is start bank offset, relativity CEBK block position + 8)
Partition data used when tile offset (modified with ncer.cebk.block_size) >= 0x400, because tile offset size = 12 bit.
If tile offset >= 0x400, this value can't writing to 12 bit in oam.obj2.

Partition data is part of tiles, used for 1 bank:
UInt32 - max partition size
UInt32 - ?? (0x8)
repeat (banks.Count)
    UInt32 - part offset;
    UInt32 - part size;

I dont't change type of file strucure if (ncer.cebk.unknown1 != 0) or (max tile offset >= 0x400)


I writing NCER plugin from your code (Image  plugin):

          1) ncer.cebk.unknown1 has been renamed to ncer.cebk.partition_data_offset
          
         void Read(string fileIn)

2) long posicion = br.BaseStream.Position;
                // <ADD THIS
                if (ncer.cebk.partition_data_offset != 0x00)
                {
                    br.BaseStream.Position = ncer.header.header_size + ncer.cebk.partition_data_offset + 16 + 8 * i;
                    ncer.cebk.banks[i].partition_offset = br.ReadUInt32();
                    ncer.cebk.banks[i].partition_size = br.ReadUInt32();
                    br.BaseStream.Position = posicion;
                }
                // -------------------->

           3) if (ncer.cebk.partition_data_offset != 0x00)
                    {
                        // <
ADD THIS
                        int depth = 4 << ncer.cebk.banks[i].oams[j].obj0.depth;
                        tilePos = (uint)(ncer.cebk.banks[i].partition_offset * 8 / depth / 64) << ncer.cebk.banks[i].oams[j].obj0.depth >> (byte)(ncer.cebk.block_size);
                        // -------------------->
                        ncer.cebk.banks[i].oams[j].obj2.tileOffset += tilePos;
                    }

           4) DELETTE/COMMENT THIS:
                // Calculate the next tileOffset if unknonw1 != 0
                if (ncer.cebk.partition_data_offset != 0x00 && ncer.cebk.banks[i].nCells != 0x00)
                {
                    OAM last_oam = Get_LastOAM(ncer.cebk.banks[i]);

                    int ultimaCeldaSize = (int)(last_oam.height * last_oam.width);
                    ultimaCeldaSize /= (int)(64 << (byte)ncer.cebk.block_size);
                    if (last_oam.obj0.depth == 1)
                        ultimaCeldaSize *= 2;
                    if (ultimaCeldaSize == 0)
                        ultimaCeldaSize = 1;

                    tilePos += (uint)((last_oam.obj2.tileOffset - tilePos) + ultimaCeldaSize);
                }

            5) ADD THIS (to end of "Read banks" region)
              if (ncer.cebk.partition_data_offset != 0)
              {
                  br.BaseStream.Position = ncer.header.header_size + ncer.cebk.partition_data_offset + 8;
                  ncer.cebk.max_partition_size = br.ReadUInt32();
                  ncer.cebk.unk_partition_size = br.ReadUInt32();
              }

            void Write()
            
            6) byte depth = (image.BPP == 4) ? (byte)0 : (byte)1;
                int maxBlockIndex = (image.Tiles.Length * 8 / image.BPP / 64) << depth >> (byte)(ncer.cebk.block_size);
                Update_Struct(ncer.cebk.partition_data_offset != 0 || maxBlockIndex >= 0x400);

            7) ADD THIS (before // LBAL section)

            // Partition data
            if (ncer.cebk.partition_data_offset != 0)
            {
                bw.Write(ncer.cebk.max_partition_size);
                bw.Write(ncer.cebk.unk_partition_size);
                for (int i = 0; i < ncer.cebk.banks.Length; i++)
                {
                    bw.Write(ncer.cebk.banks[i].partition_offset);
                    bw.Write(ncer.cebk.banks[i].partition_size);
                }
            }

           8) void Update_Struct(bool hasPartitionData)
            // Update OAMs and LABL section
            uint offset_cells = 0;
            uint size = 0;
            uint max_partition_size = 0;
            for (int i = 0; i < Banks.Length; i++)
            {
                .........
                // ADD THIS to end
                if (hasPartitionData)
                {
                    if (ncer.cebk.banks[i].oams.Length > 0)
                    {
                        byte depth = (byte)(4 << ncer.cebk.banks[i].oams[0].obj0.depth);
                        uint tileSize = (uint)(64 << (byte)(ncer.cebk.block_size) >> ncer.cebk.banks[i].oams[0].obj0.depth);
                        uint firstTileIndex = uint.MaxValue;
                        uint lastTileIndex = 0;
                        for (int j = 0; j < ncer.cebk.banks[i].oams.Length; j++)
                        {
                            if (ncer.cebk.banks[i].oams[j].obj2.tileOffset < firstTileIndex) firstTileIndex = ncer.cebk.banks[i].oams[j].obj2.tileOffset;
                            else
                            {
                                uint ultimaCeldaSize = (uint)(ncer.cebk.banks[i].oams[j].height * ncer.cebk.banks[i].oams[j].width / tileSize);
                                if (ultimaCeldaSize == 0) ultimaCeldaSize = 1;
                                if (ncer.cebk.banks[i].oams[j].obj2.tileOffset + ultimaCeldaSize > lastTileIndex) lastTileIndex = ncer.cebk.banks[i].oams[j].obj2.tileOffset + ultimaCeldaSize;
                            } 
                        }

                        for (int j = 0; j < ncer.cebk.banks[i].oams.Length; j++) ncer.cebk.banks[i].oams[j].obj2.tileOffset -= firstTileIndex;

                        ncer.cebk.banks[i].partition_offset = firstTileIndex * tileSize * depth / 8;
                        ncer.cebk.banks[i].partition_size = (lastTileIndex - firstTileIndex) * tileSize * depth / 8;
                        if (ncer.cebk.banks[i].partition_size > max_partition_size) max_partition_size = ncer.cebk.banks[i].partition_size;
                    }
                    else
                    {
                        ncer.cebk.banks[i].partition_offset = 0;
                        ncer.cebk.banks[i].partition_size = 0;
                    }
                }
            }

           9) ADD this to end of // Update the rest (before // Update the header)
            ncer.cebk.partition_data_offset = ncer.cebk.section_size - 8;
            ncer.cebk.max_partition_size = max_partition_size;
            ncer.cebk.unk_partition_size = (ncer.cebk.unk_partition_size > 0) ? ncer.cebk.unk_partition_size : 8;
            if (hasPartitionData)
                ncer.cebk.section_size += (uint)(8 * (Banks.Length + 1));

WARNING: but new tiles must be added to end of part. Else maximum tile offset for first bank can be >= 0x400.
Can you change OAM Editor, where on new tiles adding, data write to end of part (or after last oam of bank)???

Finaly. IF I IMPORT TILES with option "ADD IMAGE"
1) Find max tile (block of tiles) index
2) Calculate position offset in bytes of the part end
3) Input to this offset new data
4) Update info of others oams, where position > new offset 

Control with numericBox "Tile offset" must be from 0 to 0x3FF relativity start position of current part: from "part offset" to "part offset" + 0x3FF !

MetLob

unread,
Feb 5, 2015, 7:34:36 AM2/5/15
to ti...@googlegroups.com, meta...@gmail.com
block data size = 0x20 bytes for 4 and 8 BPP?


Reply all
Reply to author
Forward
0 new messages