A randomizer for Final Fantasy 1 on the NES.
This is a randomizer for Final Fantasy 1 for the NES. It currently operates on the US version of the ROM only. A randomizer takes certain elements of a game and randomize them, creating a whole new gameplay experience. If you just want to play, it is deployed at https://finalfantasyrandomizer.com
The general project structure is as follows:
The randomizer is written in C#. The user interface is written using Blazor. Blazor is a web framework that includes the ability to run .NET applications directly in the browser. This means that the randomizer is able to run entirely on the user's computer and is cross-platform without users having to explicitly download or install anything.
Different randomizer versions are handled as instanced deploys. The version is embedded in the URL as https://4-5-1.finalfantasyrandomizer.com, https://4-5-2.finalfantasyrandomizer.com, etc and the finalfantasyrandomizer.com start page is just a redirect to the latest version.
Most of the code that actually changes the ROM can be found in FF1Lib. The entry point for randomization is FF1Rom.Randomize(). It is initalized with the starting ROM and then goes through and makes all the changes that the user selected. You should read data from the ROM using FF1Rom.Get() or FF1Rom.GetFromBank() and apply changes using FF1Rom.Put() or FF1Rom.PutInBank().
Some data is loaded into tables which are written back at the end. In these cases, reading the data and writing it back will produce incorrect results. Check FF1Rom.LoadSharedDataTables().
Offsets used by Get() and Put() do not include the 16 byte iNES header from the .nes file. If you are getting ROM offsets from somewhere else (like another ROM hack) they might be .nes file offsets which will be 16 bytes greater than the correct ROM offset.
The randomizer changes the mapper from MMC1 to MMC3 in order to bump the ROM size from 256k to 512k and have more space for hacks. It also moves some code out of their original locations into the newly available banks. When creating patches, be aware that some functions may have moved.
The web user interface can be found in FF1Blazor. It calls FF1Rom.Randomize() in FileTab.razor.
When adding a new flag, you need to add it to FF1Lib.Flags and FF1FLib.FlagsViewModel. The latter class wraps the Flags in order to raise events when a flag is changed (used to trigger a re-render by the UI).
The FF1Rom.Randomize() method is an asynchronous method. Because randomization can take a while, it periodically returns control to the caller using "await" to give the browser's javascript event loop a chance to process input, re-render, etc. This is necessary because the .NET virtual machine that executes the randomizer is written in webassembly and runs in the same thread as the page's javascript.
The randomizer has been touched by a number of people with varying style, programming skill, goals, and at different points of evolution of abstractions in the code base. As a result, different features manipulate the ROM in many different ways. When in doubt, prefer to use functions and classes that abstract interaction with the ROM instead of just blindly changing bytes, it will be less likely your feature has unintended interactions with other features.
In the Visual Studio Installer, select the following optional pieces:
ASP.NET and web development .NET desktop development
Clone the randomizer (git specific information below)
All done!
cd FF1Blazorizer && dotnet publish -c Debug -o output
cd output/wwwroot && python3 -m http.server 8000
For example, if you need to assemble 0E_9500_ShopUpgrade.asm
and get a hex string (the listing is optional but incredibly helpful):
ca65 0E_9500_ShopUpgrade.asm --listing 0E_9500_ShopUpgrade.lst
ld65 -t nes -o 0E_9500_ShopUpgrade 0E_9500_ShopUpgrade.o
hexdump -s 0x10 -v -e '/1 "%02X"' 0E_9500_ShopUpgrade
The randomizer is hosted on github and therefore some level of git knowledge is required in order to contribute. In order to help get anyone ready to contribute, this will assume no prior git knowledge. Please skip past any steps you have already done, or this entire section if you are already familiar with git and able to rebase, merge, deal with multiple repos, bisect, and handle PRs on github.
A version control system. This will control a folder or directory for you, managing the files inside it so you can swap between versions of those files.
commit: A discrete piece of history in the version control system.
branch: A distinct history of the files in the git directory. This can have a history that shares a common ancestor with another branch (the usual case) or be fully separate. Usually features/bugfixes start with a new branch off the main branch.
repo, repository, or remote: A remote server that facilitates distributed development through being a synchronized source of truth for the files in the git directory. There can be multiple repos.
rebase or merge: Methods of taking changes that are in a repo and combining them with your local work.
pull and push: Transferring files from the repo to your local stuff or the other way around.
clone: The action of setting up a local git directory that is a clone of a specific repo.
checkout: Swap to a different branch or commit.
use git status
to see what the current situation in your local git directory is.
A company that provides git hosting services.
git clone <url you copied, without the angled brackets here>
git remote add <some name you can remember like "main" or "upstream" or whatever> <the url you copied>
-- note that we will be using "upstream" in this documentation, you can sub in your own name you choose.So now you want to work on a new feature or fix a bug! How do you start?
git fetch --all
this will update your local git with the knowledge of what the remotes have.git checkout upstream/dev --track
this checkout the main development branch of the upstream repo, and track it for changes.Run git checkout -b <some branch name>
this will create a new local branch with any name you choose.
Run git push origin -u HEAD
this will setup your forked repo to have a copy the local branch you just created.
Get to work on some feature code!
When you have some work done, or are going to stop for a while, run one of the following git add
commands:
git add -u
adds updated files
git add -p
interactively adds updated files
git add .
adds all the files in the current directory and its sub directories
git add -A
adds all files in the git directory and its sub directories
git add relative/path/to/file
adds the file specified
Then run git commit
and type in a commit message, this can be anything but try to make it obvious what is contained in that commit by just the message. git commit -m "some message"
is a shortcut to add small commit messages.
Ensure all current work is stored in a commit, so its easy to recover if something bad happens.
git push
- this will update your remote with the commits you have made, and will be a backup incase anything happens.git fetch upstream
then git rebase upstream/dev
- this rebase command will find a common history point (when you created this branch) and then take all the commits since then, and place them on top of the current upstream/dev. What this means is git will try to make it like you just wrote all this code after the stuff in upstream/dev, even the new things. You might have conflicts, this is ok. Look at the Merging and Rebasing section below for help.At this point you should have been talking with people in the dev discord and have something functional that maybe still needs some polish, but is ready for feedback, if not ready to be included in the beta site yet.
git push origin
When you are working, other people will also be working and we need a way to combine the changes. We can do this in two ways, merge the changes or rebase your changes. A key difference between merges and rebases is the commit history. In a merge, there will be a merge commit, this is a commit that has two parent commits. In a rebase, there will not be any merge commit, instead all the upstream work will be in your branch's history, before your work. Generally try to do a rebase unless the rebase is giving you trouble, then try a merge. Keeping the history linear is not worth a huge hassle.
git fetch upstream
git rebase upstream/dev
will rebase you on the dev branch of the main repogit fetch upstream
git merge upstream/dev
will merge your work with the dev branch of the main repoSo you have rebased or merged and need to fix a conflict? And what even is a conflict? Well a conflict is when you change something and someone else changes the same thing, git doesn't know which is correct, or if there needs to be reworking of the code to make stuff work with both pieces.
First you will want to do git status
- this will show you what is labeled as "both modified". The term "ours" refers to the main branch or the one being rebased onto, and the "theirs" refers to the branch you made changes in, usually. This will be where you have conflicts to resolve. So open up those files and find the areas with "<<<<<<< HEAD". This is the start of a conflict. You need to look at the two pieces of code and fix it up to work as expected with your changes AND the changes that were made by others since you started your work.
The first section with be after the "<<<<<<< HEAD" until the "=======" and the second section will be from the "=======" until the ">>>>>>>
Go through each of these and fix them up, (try searching <<<<<<< HEAD to be sure you got them all), then run git add -u
followed by:
git rebase --continue
, you might have more conflicts as more of your commits are applied.git merge --continue
, you will see a commit screen with a pre-filled message, just leave the message and exit out as normal with commits.If you have issues with resolving a conflict and making stuff work afterwards (either your work or the other work), don't be afraid to reach out in the dev discord for help.
And now your conflict should be resolved!
Our deployments utilize CircleCi and Netlify. The deployment config can be found in the .circleci folder in the config.yml file. This defines a series of jobs that need to run for our deployment process, first we build with the build.sh file, then we deploy to netlify using the deploy.sh file. The build process modifies a couple values depending on if this is a beta and real release. The deploy process will deploy previews for PRs from maintainers working in the main repo, as well as deploy to either the beta or main site, depending on the branch.
If you are a maintainer working in the main repo, in order to get deployment previews, you need to pass the hold job in circleci: just look in the pinned resources in dev discord to see how to do this.
Beta is updated basically whenever. Never feel bad about updating beta, if the flags break and it ruin's someone's race, its ok.
The main site is updated every month or 3 (or when we get around to it). This generally has a 2 week waiting period where beta is frozen, except for bug fixes and the like. If this is the case, your PRs will be sitting there waiting until the release happens.