Final Fantasy III SNES disassembled (C source reimplementation in progress)

  • Thread starter Thread starter Givemesteak
  • Start date Start date
  • Views Views 1,044
  • Replies Replies 22
  • Likes Likes 7

Givemesteak

Well-Known Member
Newcomer
Joined
Aug 4, 2024
Messages
60
Reaction score
75
Trophies
0
Age
37
XP
266
Country
Sweden
Hi, not sure if I am shooting myself in the foot by opening this thread but here we go.

I have fully disassembled Final Fantasy III/VI and it builds the identical input ROM.
Work has started on converting it to C. Several functions are already reimplemented in C and build the identical SNES rom.

I don't know how much interest there is in this kind of project but it's one of my favorite games of all time so I hope to eventually have complete, readable and documented source code for this game.

The end goal is being able to port this to other platforms.

I'll keep this thread updated when I have things to share and milestones are reached and so on.

The reason why I am not yet sharing any repo to the source is that it's still full of extracted game-assets and that's copyright material.
Eventually I can remove the assets and just provide the scripts to extract the assets.
But for now I am running frequent rebuilds and tests so that's not a priority at the moment.
 
Last edited by Givemesteak,
Question: which tool are you using? I would really love to do the same to Sailor Moon S someday.
 
+8KB across 443 routines are now C owned. And builds the exact rom with matching hashes.

With a lot of compiler stuff getting sorted out I am hoping that progress will speed up. However I am like 1.2% done translating it to C. And once that's done then there will be a lot of clean-up and documentation to make sure we have a nice, tidy and readable source.

I expect this will take a long time to complete.
 
How can you build a C version with the exact matching hashes, as the game was written in 65816 assembly ?
Well, if you match the functions written in assembly and make a ton of compiler tweaks then they should compile the same bytes. At least that's what has been working for me so far.

My pipeline uses Clang to parse C, followed by My own 65816 code generator, alongside modified compiler paths.
So we are therefor doing both reverse engineering and compiler development
 
Last edited by Givemesteak,
Well, if you match the functions written in assembly and make a ton of compiler tweaks then they should compile the same bytes. At least that's what has been working for me so far.

My pipeline uses Clang to parse C, followed by My own 65816 code generator, alongside modified compiler paths.
So we are therefor doing both reverse engineering and compiler development
That’s not how it works. No C compiler can generate the same code as a hand-written 65816 assembly.
You don’t seems to understand how the various decomp projects works : you have to find the exact compiler used for the project, and compare the generated assembly code from your reimplementation, with the binary from the game’s executable.
There is no such thing in SNES games written in assembly (you can have a look at various snes games leaks).

SNES to C projects, like snesrev’s project (https://github.com/snesrev/smw) ane reimplementation. The closest thing you can have for FF6 is Everything8215’s project (https://github.com/everything8215/ff6).

So, either you show some proof, either you’re lying.

(And if you want some proof that I know what I’m talking about, you’d understand the screenshot below)

1791012482741.png
 
That’s not how it works. No C compiler can generate the same code as a hand-written 65816 assembly.
You don’t seems to understand how the various decomp projects works : you have to find the exact compiler used for the project, and compare the generated assembly code from your reimplementation, with the binary from the game’s executable.
There is no such thing in SNES games written in assembly (you can have a look at various snes games leaks).

SNES to C projects, like snesrev’s project (https://github.com/snesrev/smw) ane reimplementation. The closest thing you can have for FF6 is Everything8215’s project (https://github.com/everything8215/ff6).

So, either you show some proof, either you’re lying.

(And if you want some proof that I know what I’m talking about, you’d understand the screenshot below)

View attachment 593726

It's possible that you are right that this is an impossible task.
But I am going to continue exploring this approach as I think it's far more interesting.

I already disassembled the entire game

I'll post some proof when/if I am ready.

But I have released stuff based on disassembly work before, I ported features from Pokemon Yellow over to Pokemon Crystal and released the .ips patch.

I am not lying about working on this. But perhaps I will run into a wall where I have to realize that this is impossible, time will tell.

fade_in.c
C:
typedef unsigned char u8;

void func_C00F4D(void) {
    *(volatile u8 *)0x004a = 0x10;
    *(volatile u8 *)0x004c = 0x10;
}

fade_out.c
C:
typedef unsigned char u8;

void func_C00F56(void) {
    *(volatile u8 *)0x004a = 0x90;
    *(volatile u8 *)0x004c = 0xf0;
}

fade_update.c
C:
typedef unsigned char u8;

void func_C00F5F(void)
{
    if ((signed char)*(volatile u8*)0x4a >= 0) {
        if ((*(volatile u8*)0x4c & 0xf0) == 0xf0)
            goto complete;
        *(volatile u8*)0x4c = (*(volatile u8*)0x4a & 0x1f) + *(volatile u8*)0x4c;
    } else {
        if (*(volatile u8*)0x4c == 0)
            goto complete;
        *(volatile u8*)0x10 = *(volatile u8*)0x4a & 0x1f;
        *(volatile u8*)0x4c = *(volatile u8*)0x4c - *(volatile u8*)0x10;
    }
    goto upload;
complete:
    *(volatile u8*)0x4a = 0;
upload:
    *(volatile u8*)0x2100 = *(volatile u8*)0x4c >> 4;
}

So we are going lower level than the one you provided in the screenshot.
 
Last edited by Givemesteak,
I didn’t say it is an impossible task. I said you lied when you write tha you use a C compiler to build a identical SNES rom, because it is not possible with a 65816 C compiler. What you are doing is similar to a recomp project, which makes the game unmaintainable.
For example, look at my "fade_update.c" version of your work :

C:
void UpdateBrightness(void)
{
    /* ASM: screen.asm @UpdateBrightness */
    uint8_t fade = wram_read8(DP_FADE_ACTIVE);   /* $4A */
    uint8_t bright = wram_read8(DP_SCREEN_BRIGHT); /* $4C */

    if (fade & 0x80) {
        /* ASM: FadingOut */
        /* ASM: lda $4c; beq FadeComplete */
        if (bright == 0) {
            wram_write8(DP_FADE_ACTIVE, 0);
        } else {
            /* ASM: lda $4a; and #$1f; sta $10;
             *      lda $4c; sec; sbc $10; sta $4c */
            uint8_t speed = fade & 0x1F;
            bright = (uint8_t)(bright - speed);
            wram_write8(DP_SCREEN_BRIGHT, bright);
        }
    } else {
        /* ASM: lda $4c; and #$f0; cmp #$f0; beq FadeComplete */
        if ((bright & 0xF0) == 0xF0) {
            wram_write8(DP_FADE_ACTIVE, 0);
        } else if (fade) {
            /* ASM: lda $4a; and #$1f; clc; adc $4c; sta $4c */
            uint8_t speed = fade & 0x1F;
            bright = (uint8_t)(bright + speed);
            wram_write8(DP_SCREEN_BRIGHT, bright);
        }
    }

    /* ASM: :lda $4c; lsr4; sta hINIDISP */
    bright = wram_read8(DP_SCREEN_BRIGHT);
    hw_write(hINIDISP, bright >> 4);
}

For those interested, here is the ASM equivalent :

Code:
.proc UpdateBrightness
        lda     $4a
        bmi     FadingOut
        lda     $4c
        and     #$f0
        cmp     #$f0
        beq     FadeComplete
        lda     $4a
        and     #$1f
        clc
        adc     $4c
        sta     $4c
        bra     :+

FadingOut:
        lda     $4c
        beq     FadeComplete
        lda     $4a
        and     #$1f
        sta     $10
        lda     $4c
        sec
        sbc     $10
        sta     $4c
        bra     :+

FadeComplete:
        stz     $4a
:       lda     $4c
        lsr4
        sta     hINIDISP
        rts
.endproc  ; UpdateBrightness

You can see the difference. Mine has variable names, no goto’s, and easier to modify if needed.

Far from me to prevent you to do your project, you can do whatever you want. Just don’t write fake informations.
 
I didn’t say it is an impossible task. I said you lied when you write tha you use a C compiler to build a identical SNES rom, because it is not possible with a 65816 C compiler. What you are doing is similar to a recomp project, which makes the game unmaintainable.
For example, look at my "fade_update.c" version of your work :

C:
void UpdateBrightness(void)
{
    /* ASM: screen.asm @UpdateBrightness */
    uint8_t fade = wram_read8(DP_FADE_ACTIVE);   /* $4A */
    uint8_t bright = wram_read8(DP_SCREEN_BRIGHT); /* $4C */

    if (fade & 0x80) {
        /* ASM: FadingOut */
        /* ASM: lda $4c; beq FadeComplete */
        if (bright == 0) {
            wram_write8(DP_FADE_ACTIVE, 0);
        } else {
            /* ASM: lda $4a; and #$1f; sta $10;
             *      lda $4c; sec; sbc $10; sta $4c */
            uint8_t speed = fade & 0x1F;
            bright = (uint8_t)(bright - speed);
            wram_write8(DP_SCREEN_BRIGHT, bright);
        }
    } else {
        /* ASM: lda $4c; and #$f0; cmp #$f0; beq FadeComplete */
        if ((bright & 0xF0) == 0xF0) {
            wram_write8(DP_FADE_ACTIVE, 0);
        } else if (fade) {
            /* ASM: lda $4a; and #$1f; clc; adc $4c; sta $4c */
            uint8_t speed = fade & 0x1F;
            bright = (uint8_t)(bright + speed);
            wram_write8(DP_SCREEN_BRIGHT, bright);
        }
    }

    /* ASM: :lda $4c; lsr4; sta hINIDISP */
    bright = wram_read8(DP_SCREEN_BRIGHT);
    hw_write(hINIDISP, bright >> 4);
}

For those interested, here is the ASM equivalent :

Code:
.proc UpdateBrightness
        lda     $4a
        bmi     FadingOut
        lda     $4c
        and     #$f0
        cmp     #$f0
        beq     FadeComplete
        lda     $4a
        and     #$1f
        clc
        adc     $4c
        sta     $4c
        bra     :+

FadingOut:
        lda     $4c
        beq     FadeComplete
        lda     $4a
        and     #$1f
        sta     $10
        lda     $4c
        sec
        sbc     $10
        sta     $4c
        bra     :+

FadeComplete:
        stz     $4a
:       lda     $4c
        lsr4
        sta     hINIDISP
        rts
.endproc  ; UpdateBrightness

You can see the difference. Mine has variable names, no goto’s, and easier to modify if needed.

Far from me to prevent you to do your project, you can do whatever you want. Just don’t write fake informations.

Sure it's maybe similar to a recomp in some ways. However at this stage the goal is simply to have as much as possible owned by C while still compiling an identical rom. (and I AM so far compiling an identical rom)

I am not just feeding C into a standard compiler and expecting it to magically produce the original ROM. We are using existing compilers like cc65 and Calypsi where they work, and adding targeted compiler support where needed. For more complex routines, we’re using Clang/LLVM with a custom 65816 backend that understands the game’s register usage, memory banks, flags, stack and calling conventions.

The idea is to compile readable C into the exact same instructions as the original assembly, not just code that behaves similarly. We then build those routines together with the remaining assembly and assets and compare the entire ROM against the original, byte for byte and by hash. We’re not copying the original machine code into the compiler or patching the output to make it match. This is working for some routines already, but we haven’t yet shown that the approach can handle the whole game.

This is mainly for the purpose of cross-referencing.
And as long as I can output an identical rom then I always know the code working as intended.

Once that's done then I will start working on a Decompilation port for MacOS and PC.
This port should have source code that is much more in line with what you'd expect (as this won't need to match the rom)
But I'll use the disassembly and C-code for cross-reference and feed that to Codex.
Then I could have a pretty nice path where each routine is validated against the logic in the already completed work.
This should minimize mistakes and allow me to automate a lot of the work.

Now I am kinda new in the whole recomp and decomp scene, and I do use quite a bit of AI.

But I am not lying to anyone.

The end goal is still ports of Final Fantasy VI with readable and maintainable source code, But I guess my way to get there is different from how you would have done it.
 
Last edited by Givemesteak,
Sounds good, but why not just make it a web app?


You mean to make a port runnable in the web browser?
I am not interested in that at the moment. The goal is a lightweight source code with high portability. I have been thinking about even the possibility to port the game to N64 or other older systems that were never able to run the game. That would of course require separate work. But it helps to have a robust source to begin with.

Also, not all systems have web browsers modern enough to run a game like this.
And I was never interested in running games in the browser.

However it might be possible if you build the web app in such a way that the user still has to provide the rom for asset extraction to local storage.

But I have always been more intrigued by native and efficient code.
 
Last edited by Givemesteak,
  • Like
Reactions: Eiffel2018
You mean to make a port runnable in the web browser?
I am not interested in that at the moment. The goal is a lightweight source code with high portability. I have been thinking about even the possibility to port the game to N64 or other older systems that were never able to run the game. The would of course require separate work. But it helps to have a robust source to begin with.

Also, not all systems have web browsers modern enough to run a game like this.
And I was never interested in running games in the browser.

However it might be possible if you build the web app in such a way that the user still has to provide the rom for asset extraction to local storage.

But I have always been more intrigued by native and efficient code.
I see your point. That makes sense for the direction you have in mind.
 
We got audio working well, now we're getting graphics and mapping data sorted out one step at a time.
 

Attachments

  • Screenshot 2026-10-08 at 16.03.06.png
    Screenshot 2026-10-08 at 16.03.06.png
    438.5 KB · Views: 7
This seems interesting on the level of making some native ports to all sorts of stuff, personally would have preferred II/IV but still. ;)
 
Ok got layers, sprites and animations working as well (and also the black border).
Now it's starting to look like the game :)

Sound and menus are also implemented.

Still a long way to go but there is progress being done here.
Post automatically merged:

Work on, player camera, map transitions and doors has been completed. Work on scripted events has been started. Work on treasure chests and surrounding rules is also being done right now.
Post automatically merged:

the game now starts normally and the entire intro sequence is playing and routing into gameplay that all works up until the first battle. I will post a video soon
Post automatically merged:

ok here's the promised video.
 

Attachments

  • Screenshot 2026-10-08 at 18.41.03.png
    Screenshot 2026-10-08 at 18.41.03.png
    123 KB · Views: 3
Last edited by Givemesteak,
Did you use the US FF-III rom? If so I'd love to see a native Windows port that uncensores the game (changes cafes back to bars again, gives some female espers back the more revealing design from the Japanese FF-VI etc.). Also there are fan retranslations that are considered superior and while we are at it, why not restore the FF-VI title screen with the logo of Terra riding that Magitec armor.
OK, maybe a too early stage for these kinds of requests, but the first things that went through my mind when seeing these good news 😄
 
  • Like
Reactions: Givemesteak
Did you use the US FF-III rom? If so I'd love to see a native Windows port that uncensores the game (changes cafes back to bars again, gives some female espers back the more revealing design from the Japanese FF-VI etc.). Also there are fan retranslations that are considered superior and while we are at it, why not restore the FF-VI title screen with the logo of Terra riding that Magitec armor.
OK, maybe a too early stage for these kinds of requests, but the first things that went through my mind when seeing these good news 😄

A bit early yes but I definitely want robust mod support that allows these sort of things.
And yes it's the US rom

The port now reaches the Whelk fight and most battle animations are displayed correctly in the game including the Esper animations.
Screenshot 2026-10-14 at 08.15.18.png
 
  • Like
Reactions: homebrewfan88
Inventory, Main menu, submenu's and the saving feature have been added.

Running another accuracy pass for all battle animations doing visual comparisons against the Mesen emulator for obvious issues. If there is any issue then we are looking for solutions in the disassembled source.

AI models can have difficulties with X vs Y type of understanding.
Giving visual feedback makes it easy to swap those kind of problems.

We also fixed a few layering order issues.

So we are progressing steadily.

I want to map all controls to a Switch pro controller and work my way accurately towards the world map before doing a large script pull from the disassembly and world map rendering.

The battle system has taken longer than I expected due to having a lot of advanced and flashy animations.
 

Site & Scene News

New Hot Discussed User Submitted