Enhanced Portrait Format Support for GBAFE

Features

  1. Get rid of the limitation of hackbox. A box can be customized for your portrait.

  2. The size of eye frame can be customized which can be larger than 32x16.

  3. The position of the eye frame and mouth frame can be placed at any position including the non-8px-aligned coordinate.

  4. Compatibility with the vanilla format. The old format can also work without any changes.

Demo

Tiki

Sharena

Doc

  1. the format of the portrait info

  2. the format of the eye frame info

  3. the main combination template


    OAM Attributes
    It looks like this in the game:

    The character name in the OAM Attribute 2 is the tile number in the main portrait strip:

    Each portrait is supposed to have a pair of the combination templates (face towards left or right).

  4. the TSA template in stat screen

    TSA
    The tile number is also in the strip:

  5. the mask for stat screen

    2 bytes stands for a tile, 00 00 -> remove this tile, otherwise 01 00.
    9 rows 10 colums

  6. the eye template

    it is the TSA of the eye frame. The tile number here is also in the strip. It is only used when the eye control flag is set to 3.

  7. Eye Control Flag
    It controls the way in which the game handles the eye animation. A different way means a different requirement to the strip.
    If it is set to 1, there are 2 eye frames in the strip.

    If it is set to 2, there is one eye frame in the strip.

    If it is set to 3, no eye frame is in the strip.

    If it is set to 6, the eye is always closed.
    Any other values are invalid.
    Note: as I covered previously, the mode 2 and 3 also have a different format of the eye frame info.

Instance

Tiki(Adult)
Sheet:

Video Guide:


Result:

Patch

FE8 US Enhanced Portrait Format Support Patch
Quick notes:

  • Portrait template pointer table for the dialogue --> 10007D4
  • Portrait template pointer table for the stat screen --> 10007CC
  • Mask Array for the stat screen - > 100070C
    Feel free to expand and repoint them if necessary.
Code

struct EyeFrameInfo{
s8 width;
s8 height;
s8 winkWidth;
s8 winkHeight;
u8 *eyeFrame[3];
s16 *blinkTemplate;
};

struct Portrait{
void *mainPortrait;
void *miniPortrait;
void *portraitPalette;
void *mouthFrame;
union
{
void classCard;
struct EyeFrameInfo
eyeFrameInfo;
} ce;
s8 mouthPositionX;
s8 mouthPositionY;
s8 eyePositionX;
s8 eyePositionY;
s8 eyeControlFlag;
s8 dialoguePortraitTemplate;
s8 statScreenPortraitTemplate;
s8 statScreenPortraitMask:7;
s8 eyeMouthPositionAlignmentFlag:1;
};

const PTRFUN eyeAnimation[3] =
{
eyeAnimation1,
eyeAnimation2,
eyeAnimation3
};

const void *dialoguePortraitTemplate[][2] =
{
{0x859100C,0x859100C+26},
{0x8591040,0x8591040+38},
{0x859108C,0x859108C+50},
{0x85910F0,0x85910F0+50}
};

const void *statScreenPortraitTemplate[] =
{
0x85A0838,
0x85A08F0
};

const u16 statScreenPortraitMask[][9][10] =
{
{
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,1,0},
{1,1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1,1}
}
};

void eyeAnimation1(u32 *mempool, int eyeStatus)
{
int winkFlag;
s16 eyeTileIndexDelta;
int *data;
struct Portrait *portrait;
int x;
int y;

if(eyeStatus & (~0x81))
	return;
winkFlag = eyeStatus & 0x80;
eyeTileIndexDelta = 88 - (eyeStatus & 1)*(88 - 24);
data = (int *)mempool[11];
portrait = data[11];
x = 4 - portrait->eyePositionX;
if(!(sub(80057A4)(data) & 1))
	x = -x;
x = (8 * x * (1-portrait->eyeMouthPositionAlignmentFlag) + x * portrait->eyeMouthPositionAlignmentFlag + (s16)data[13] - 16) & 0x1FF;
if(sub(80057A4)(data) & 1)
	x += 0x1000;
y = ((-(sub(80057A4)(data) & 0x400)>>31) & 0x400) + ((*(u16 *)((int)data + 54) + 8 * portrait->eyePositionY * (1 - portrait->eyeMouthPositionAlignmentFlag) + portrait->eyePositionY * portrait->eyeMouthPositionAlignmentFlag) & 0xFF);
if(winkFlag)
{
	if(!(sub(80057A4)(data) & 1))
		x += 16;
	sub(8005428)(*(s8 *)((int)data + 65),x,y,0x8590F4C,*(s16 *)((int)data + 60) + eyeTileIndexDelta + 2);
}
else
	sub(8005428)(*(s8 *)((int)data + 65),x,y,0x8590F8C,*(s16 *)((int)data + 60) + eyeTileIndexDelta);

}

void eyeAnimation2(u32 *mempool, int eyeStatus)
{
int eyeShape;
int winkFlag;
struct Portrait *portrait;
int *data;
int x;
int y;

data = mempool[11];
portrait = data[11];
winkFlag = eyeStatus & 0x80;
eyeShape = 2 - (eyeStatus & 1);
if(eyeStatus & (~0x81))
	eyeShape = 0;
sub(8012FF4)(portrait->ce.eyeFrameInfo->eyeFrame[eyeShape],32 * ((*(s16 *)((int)data + 60) + 28 + 32 * 2) & 0x3FF) + 0x6010000,4,2);
x = 4 - portrait->eyePositionX;
if(winkFlag)
	x += 2;
if(!portrait->eyeMouthPositionAlignmentFlag)
	x = 8 * x;
if(!(sub(80057A4)(data) & 1))
	x = - x;
x = 0x1FF & (x + *(s16 *)((int)data + 52) - 16);
if(sub(80057A4)(data) & 1)
	x += 0x1000;
y = portrait->eyePositionY;
if(!portrait->eyeMouthPositionAlignmentFlag)
	y = 8 * y;
y = ((y + *(s16 *)((int)data + 54)) & 0xFF) + (0x400 & (-(0x400 & sub(80057A4)(data)) >> 31));
if(winkFlag)
	if(!(sub(80057A4)(data) & 1))
		x += 16 + 2;
sub(8005428)(*(s8 *)((int)data + 65),x,y,winkFlag ? 0x8590F4C : 0x8590F8C,*(s16 *)((int)data + 60) + 28 + 32 * 2 + 2 * !!winkFlag);	

}

void eyeAnimation3(u32 *mempool, int eyeStatus)
{
int eyeShape;
int winkFlag;
struct Portrait *portrait;
int i;
int j;

portrait = (struct Portrait *)(*(u32 *)(mempool[11] + 44));
if(eyeStatus & (~0x81))
{
	for(i = 0;i < portrait->ce.eyeFrameInfo->height;i++)
		for(j = 0;j < portrait->ce.eyeFrameInfo->width;j++)
			sub(8012FF4)((int)portrait->ce.eyeFrameInfo->eyeFrame[0] + 32 * (portrait->ce.eyeFrameInfo->width * i + j),0x6010000 + 32 * ((*(u16 *)(mempool[11] + 60) + portrait->ce.eyeFrameInfo->blinkTemplate[portrait->ce.eyeFrameInfo->width * i + j]) & 0x3FF),1,1);
	return;
}
winkFlag = eyeStatus & 0x80;
eyeShape = 2 - (eyeStatus & 1);
if(winkFlag)
	for(i = portrait->ce.eyeFrameInfo->height - portrait->ce.eyeFrameInfo->winkHeight;i < portrait->ce.eyeFrameInfo->height; i++)
		for(j = portrait->ce.eyeFrameInfo->width - portrait->ce.eyeFrameInfo->winkWidth;j < portrait->ce.eyeFrameInfo->width; j++)
			sub(8012FF4)((int)portrait->ce.eyeFrameInfo->eyeFrame[eyeShape] + 32 * (portrait->ce.eyeFrameInfo->width * i + j),0x6010000 + 32 * ((*(u16 *)(mempool[11] + 60) + portrait->ce.eyeFrameInfo->blinkTemplate[portrait->ce.eyeFrameInfo->width * i + j]) & 0x3FF),1,1);
else
	for(i = 0;i < portrait->ce.eyeFrameInfo->height;i++)
		for(j = 0;j < portrait->ce.eyeFrameInfo->width;j++)
			sub(8012FF4)((int)portrait->ce.eyeFrameInfo->eyeFrame[eyeShape] + 32 * (portrait->ce.eyeFrameInfo->width * i + j),0x6010000 + 32 * ((*(u16 *)(mempool[11] + 60) + portrait->ce.eyeFrameInfo->blinkTemplate[portrait->ce.eyeFrameInfo->width * i + j]) & 0x3FF),1,1);				

}

attribute((section(".callEyeAnimation")))
void callEyeAnimation(u32 *mempool, int eyeStatus)
{
struct Portrait *portrait;

portrait = (struct Portrait *)(*(u32 *)(mempool[11] + 44));
eyeAnimation[portrait->eyeControlFlag - 1](mempool,eyeStatus);

}

void drawStatScreenPortrait(u16 *TSABufferInWRAM, int portraitID, int presentBGTileIndex, int presentBGPaletteIndex)
{
struct Portrait *portrait;
int i;
int j;

if(!portraitID)
	return;
portrait = sub(8005514)(portraitID);
sub(8000DB8)(portrait->portraitPalette, 32 * presentBGPaletteIndex, 32);
if(portrait->mainPortrait)
{
	sub(8012F50)(portrait->mainPortrait, 32 * presentBGTileIndex + 0x6000000);
	sub(8000DB8)(portrait->portraitPalette, 32 * presentBGPaletteIndex, 32);
	if((sub(8005C24)(portraitID)<<24) && (portrait->statScreenPortraitTemplate == 0))
		sub(80D74A0)(TSABufferInWRAM, statScreenPortraitTemplate[1], ((presentBGPaletteIndex & 0xF)<< 12) + (presentBGTileIndex & 0x3FF));
	else
		sub(80D74A0)(TSABufferInWRAM, statScreenPortraitTemplate[portrait->statScreenPortraitTemplate], ((presentBGPaletteIndex & 0xF)<< 12) + (presentBGTileIndex & 0x3FF));
	for(i = 0; i < 9; i++)
		for(j = 0; j < 10; j++)
			TSABufferInWRAM[32 * i + j] &= - statScreenPortraitMask[portrait->statScreenPortraitMask][i][j];
}
else
{
	sub(8012F50)(portrait->ce.classCard, 32 * presentBGTileIndex + 0x6000000);
	sub(8013104)(TSABufferInWRAM, (presentBGPaletteIndex << 12) + (presentBGTileIndex & 0x3FF), 10, 9);
}

}

attribute((section(".callDrawStatScreenPortrait")))
void callDrawStatScreenPortrait(u16 *TSABufferInWRAM, int portraitID, int presentBGTileIndex, int presentBGPaletteIndex)
{
drawStatScreenPortrait(TSABufferInWRAM, portraitID, presentBGTileIndex, presentBGPaletteIndex);
}

void drawDialoguePortrait(u32 *mempool)
{
u32 flag1;
u32 flag2;
s16 delta;
struct Portrait *portrait;

portrait = (struct Portrait *)mempool[11];
flag1 = mempool[12] & 0x807;

if(flag1 == 0x800 || flag1 == 0x801)
	mempool[14] = dialoguePortraitTemplate[max(portrait->dialoguePortraitTemplate,3)][flag1 & 1];
else if(flag1 <= 5)
	mempool[14] = dialoguePortraitTemplate[max(portrait->dialoguePortraitTemplate,flag1 >> 1)][flag1 & 1];

flag2 = mempool[12] & 0x3C0;
if ( flag2 == 0x80 )
{
	delta = 0x400;
}
else if ( flag2 > 0x80 )
{
	if ( flag2 != 0x200 )
		goto LABEL_1;
	delta = 0xC00;
}
else
{
	if ( flag2 != 0x40 )
	{

LABEL_1:
delta = 0x800;
goto LABEL_2;
}
delta = 0;
}
LABEL_2:
*((s16 )mempool + 30) = ((s32 *)(8 * *((s8 )mempool + 64) + 0x202A68C) >> 5)
+ ((
(s16 *)(8 * *((s8 *)mempool + 64) + 0x202A68C + 4) & 0xF) << 12)
+ delta;
}

attribute((section(".callDrawDialoguePortrait")))
void callDrawDialoguePortrait(s16 *mempool)
{
drawDialoguePortrait((u32 *)mempool);
}

void mouthAnimation(int *mempool)
{
int mouthFrameTileNoDela;
int *data;
int x;
int y;
struct Portrait *portrait;

data = mempool[11];
portrait = data[11];
mouthFrameTileNoDela = 0;
if(!(sub(80057A4)(data) & 8))
	mouthFrameTileNoDela = 24;

if(sub(80057A4)(data) & 0x30)
{
	if(!(--*((s16 *)mempool + 25) & 0x8000))
		goto LABEL_3;
	*((s16 *)mempool + 25) = ((sub(8000CE8)() >> 16) & 7) + 1;
	*((s16 *)mempool + 24) = (*((s16 *)mempool + 24) + 1) & 3;
	if(*((s16 *)mempool + 24) == 1 || *((s16 *)mempool + 24) == 3)
		mouthFrameTileNoDela += 8;
	if(*((s16 *)mempool + 24) == 2)
		mouthFrameTileNoDela += 16;
	sub(8012FF4)((int)portrait->mouthFrame + 32 * mouthFrameTileNoDela,32 * ((*(s16 *)((int)data + 60) + 28) & 0x3FF) + 0x6010000,4,2);
	goto LABEL_3;
}

sub(8012FF4)((int)portrait->mouthFrame + 32 * (mouthFrameTileNoDela + 16),32 * ((*(s16 *)((int)data + 60) + 28) & 0x3FF) + 0x6010000,4,2);

LABEL_3:
x = 4 - portrait->mouthPositionX;
if(!portrait->eyeMouthPositionAlignmentFlag)
x = 8 * x;
if(!(sub(80057A4)(data) & 1))
x = - x;
x = 0x1FF & (x + *(s16 *)((int)data + 52) - 16);
if(sub(80057A4)(data) & 1)
x += 0x1000;
y = portrait->mouthPositionY;
if(!portrait->eyeMouthPositionAlignmentFlag)
y = 8 * y;
y = ((y + *(s16 )((int)data + 54)) & 0xFF) + (0x400 & (-(0x400 & sub(80057A4)(data)) >> 31));
sub(8005428)(
(s8 )((int)data + 65),x,y,0x8590F8C,(s16 *)((int)data + 60) + 28);
}

attribute((section(".callMouthAnimation")))
void callMouthAnimation(int *mempool)
{
mouthAnimation(mempool);
};

18 Likes

Now that is a format I can work with :!

2 Likes

The page for the download does not exist?

1 Like