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)
{
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 !