How did Final Fantasy VI do a non-fixed-width font?

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by koitsu »

Asaki wrote:Hmm...it doesn't look like it says. "Text has been rewritten" could mean either way, really.
This is why I said "you can decide for yourself".
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by Bregalad »

This means it's not a retranslation. If they took the original text, re-translate it and insert it into the game, it would be a re-translation. Changing the text to "look better" is not a retranslation. At least this hack does not pretend to be one.

J2E's FF4 so called "re-translation" is an offender here, it does not re-translate the game (or, at least, only parts of it), yet they self claimed their hack was a retranslation.
Revenant
Posts: 462
Joined: Sat Apr 25, 2015 1:47 pm
Location: FL

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by Revenant »

The Secret of Mana VWF hack contains references to Final Fantasy VII, so if it's supposed to be a retranslation, it's not a very faithful one.
User avatar
whicker
Posts: 228
Joined: Sun Dec 13, 2009 11:37 am
Location: Wisconsin

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by whicker »

Revenant wrote:The Secret of Mana VWF hack contains references to Final Fantasy VII, so if it's supposed to be a retranslation, it's not a very faithful one.
Fascinating website, never had come across it before...


and yeah a little controversy is fun now and then. :P But please don't damn the whole project as "unfaithful" for making fun of Square fanbois. (or being one). As for personal opinion, I never considered it a literal translation either, just more fun and understandable to play.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by tepples »

Besides, Ted Woolsey's translations weren't literal anyway. Apparently the translation of SOM was so terse because ROM size.
Asaki
Posts: 81
Joined: Sat Jun 16, 2007 11:55 pm

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by Asaki »

Revenant wrote:The Secret of Mana VWF hack contains references to Final Fantasy VII, so if it's supposed to be a retranslation, it's not a very faithful one.
That's too bad. I tried replaying the game a couple years ago (hadn't played it since I was a kid), and that game could seriously use a proper re-translation. IMO, Final Fantasy Adventure had a more enjoyable localization >_<
Zonomi
Posts: 69
Joined: Wed May 09, 2007 12:45 pm

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by Zonomi »

Maybe the iPhone version was a retranslation ?
User avatar
whicker
Posts: 228
Joined: Sun Dec 13, 2009 11:37 am
Location: Wisconsin

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by whicker »

no, the iphone version uses the original text.
leina
Posts: 1
Joined: Mon Feb 20, 2023 6:00 am

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by leina »

Here's how Ultimate Mortal Kombat 3 does it (posting here for a wiki link that links here)

Unrelated, but this seems to be a compiled game: a lot of stack manipulation, code mostly deals with 16-bit and 32-bit values (each 32-bit ram var wasting a byte due to it being a 24-bit space)

The main routine is at 88:90f8, XA is the src of a 0-terminated ascii string, and Y is the pixel Y to plot to.
Example of code that calls it:

Code: Select all

; 83:b9c4
; First displayed VWF line (copyright), loads its ascii text from 83:bbd9 to pixel row 4
	ldy #$0004
	lda #$bbd9
	ldx #$0083
	jsl $8890f8
Each ascii char is mapped, through different tables, eventually to a pixel width (for example, for calculating where to plot the 1st char in a centered line, by summing all the line's char's widths, or where to plot subsequent chars) eg 88:8db6 having a collection of pixel widths

Some more snippets of the core part of the VWF routine:

This does the pixel column shifting, and adding to a buffer

Code: Select all

; 88:8fca
; This is a 16-bit table that maps 0-7 (pixel col in tile) to an address in `BufferShiftedCharByte` so that 0-7 `lsr`s are performed
ShiftsForBufferingCharByte:
.rept 8 index i
	.dw BufferShiftedCharByte+(7-i)
.endr


; 88:8fda
; A - curr char's pixel row within a tile (0-7)
; X - curr char's row byte for the current bitplane (gets stored in wVwfCharPxRowByte)
; wVwfBitplaneDest - 0: bitplane 1, 1: bitplane 0, 2: bitplane 3, 3: bitplane 2
; wCurrVwfBufferAddr - manipped based on bitplane and pixel row, then passed onto `BufferShiftedCharByte`
; wVwfCurrCharsPixelColInTile - similar to A, but for col in tile
BufferVwfCharBitplane:
	...
; B = char's row byte for the current bitplane. Shift it and add it to a tile data buffer
	lda wVwfCharPxRowByte.b
	xba
	ldx wVwfCurrCharsPixelColInTile.b
	jmp (ShiftsForBufferingCharByte, X)


; 88:9023
; B - char pixel row byte
; wVwfExistingTileDataForChar - the leftover tile data from previous chars
; wVwfRightHalfsTileDataOffs - offset into `wCurrVwfBufferAddr` for placing the left tile the shifted char tile data takes up
; wVwfLeftHalfsTileDataOffs - as above, for the right tile
; wCurrVwfBufferAddr
BufferShiftedCharByte:
; 0-7 of these `lsr`s are jumped to depending on the char's col position in a tile
	lsr
	lsr
	lsr
	lsr
	lsr
	lsr
	lsr

; unknown - $a4 seems to be unused after this, but it will contain the final left+right combined result
	ora wVwfExistingTileDataForChar.b
	sta $a4

; Shifted char byte has right half in A, and left half in B, add them to the tile data buffer
	ldy wVwfRightHalfsTileDataOffs
	seta8
	sta [wCurrVwfBufferAddr], Y

	xba
	ldy wVwfLeftHalfsTileDataOffs
	sta [wCurrVwfBufferAddr], Y
	seta16
	rts
And at 88:8f67, this handles plotting to a pixel row

Code: Select all

.accu 16
.index 16
; wVwfCurrCharsPixelColInTile - curr char's pixel col within a tile (0-7)
; wVwfPxRowAdjust - curr char's starting pixel row within a tile (0-7)
; wVwfCharsFullTileDataSrc - points to the $20-byte tile data for the char
; wVwfCharBufferAddrTopHalf - in case `wVwfPxRowAdjust` isn't 0, this ram var points to the top tile's dest addr in a tile data buffer
; wVwfCharBufferAddrBottomHalf - as above, but for the bottom half
BufferFullVwfChar:
; Init bitplane dest, and save bank of VWF data buffer dest
	stz wVwfBitplaneDest.b
	lda wVwfCharBufferAddrTopHalf.b+2
	sta wCurrVwfBufferAddr.b+2

@nextBitplane:
; Init pixel row for the bitplane, and the 1st pixel row idx in the current tile to draw to
	stz wVwfCurrCharPxRow.b
	lda wVwfPxRowAdjust.b
	sta wVwfCharsPxRowAdjusted.b

; A = pixel row
	lda #$0000

	@nextPxRow:
	; Each pixel row takes 2 bytes per $10 bytes
		asl
		tay

	; Bitplanes 2 and 3 get tile data $10 bytes ahead
		lda wVwfBitplaneDest.b
		cmp #$0002
		bcc @afterBitplane23adjust

		tya
		clc
		adc #$0010
		tay

	@afterBitplane23adjust:
	; Get char's pixel row tile data. Bitplanes 1/3 use the high/2nd byte...
		lda [wVwfCharsFullTileDataSrc], Y
		ldx wVwfBitplaneDest.b
		beq @bitplane0or2

		cpx #$0002
		beq @bitplane1or3

	; Bitplanes 0/2 use the low/1st byte
		and #$00ff
		bra +

	@bitplane1or3:
		and #$ff00
		xba

	; X (char's pixel row tile data byte) is saved for the below buffer routine
	+	tax

	; Jump if we're now drawing for the bottom tile
		ldy wVwfCharsPxRowAdjusted.b
		cpy #$0008
		bcs @rowInBottomTile

	; Else set the buffer addr to the top half, and pixel row to its adjusted val/plot Y
		lda wVwfCharBufferAddrTopHalf.b
		sta wCurrVwfBufferAddr.b
		tya
		jmp @bufferCharPxRow

	@rowInBottomTile:
	; Set buffer addr to the bottom half, and pixel row to its adjusted val-8 (plot Y within the bottom tile
		lda wVwfCharBufferAddrBottomHalf.b
		sta wCurrVwfBufferAddr.b
		lda wVwfCharsPxRowAdjusted.b
		and #$0007

	@bufferCharPxRow:
	; Buffer the char's row byte for the current bitplane
		jsr BufferVwfCharBitplane

	; +1 to pixel row being processed (to check if we've done all 8) and to the plot Y
		inc wVwfCurrCharPxRow.b
		inc wVwfCharsPxRowAdjusted.b
		lda wVwfCurrCharPxRow.b

		cmp #$0008
		bcc @nextPxRow

; End once all bitplanes are done
	inc wVwfBitplaneDest.b
	lda wVwfBitplaneDest.b
	cmp #$0004
	bcc @nextBitplane

	rts
There's a lot of code omitted, but this is gist of the core VWF bit.
lex
Posts: 13
Joined: Sat Dec 31, 2022 9:31 pm

Re: How did Final Fantasy VI do a non-fixed-width font?

Post by lex »

As someone who has hacked SNES games and added variable width fonts before I can explain how it works.

So to draw text most SNES games will use a background layer. Since you have a limited amount of sprites you
can display each scanline; developers using sprites for text is rare.

Since SNES uses a background layer to display text; it is more difficult to do a non-fixed-width font on SNES
than on later consoles such as the PS1. On consoles such as PS1 to create a variable width font you simply
need to change the positions of textures.

So to create a variable width font on tile-based systems; developers use software rendering.

The main idea is this, you would draw a letter of text to a 16x16 tile buffer in WRAM. Let's call this tile
the "text_buffer_tile". Then you would shift each pixel in the next letter by the current letter's width and
store the shifted next letter into a second tile; let's call it the "shifted_tile". Then you would merge
the pixels in the shifted tile into the text buffer tile using "OR" instructions.

So now the text buffer tile should contain data from the first and second letter.

However, there's still one problem. You might shift the tile for the next letter and all of the pixels might not
fit into the shifted tile since the tile is only 16x16. Therefore, you will need a third tile to store pixels that overflow.
Overflowing pixels are basically pixels that don't fit into the current text tile.

So once the text buffer tile is filled, you DMA that tile to VRAM, clear the tile in WRAM and then write any overflowing
pixels to the text buffer tile. Then you rinse and repeat. Once the text buffer tile is filled again, you DMA it to the next
16x16 slot in VRAM and rinse and repeat.
Post Reply