Thumbnail image

TP Link 64 Deluxe source now available

5/7/2021 5-minute read

The TP Link 64 Deluxe mod was a collaboration with Skilar: his models, my code. The goal of this write-up is to briefly touch on the key points so you can apply these principles to your own projects.

For the impatient

Maybe you just want the code and assets. You can find them here.

Intro

zzconvert has seen a lot of use over the years. It even has a cult following of sorts. It was lacking features, however, and the code was too spaghetti-like to maintain. I had big plans for z64convert, its successor, which is a complete rewrite.

When I build new software, I try to teach users by example. While tests involving colorful cubes are fun, the best example is a real-world use case, one that shows (rather than tells) users what they can do with it.

I decided each feature needs illustrated in an exciting way, and what’s a more wowing demonstration of proxy pointers than fitting three entirely new outfits into a file that normally only contains one?

What is a proxy pointer?

As per tradition, I have created new terminology to confuse everyone.

A proxy pointer is a lame wrapper for a display list. This wrapper is accessed instead of the display list itself. Then, all would-be pointers to the display list are instead pointers to a pointer to the display list. This way, all references to it can easily be redirected to any model we wish by updating one pointer. That’s a mouthful, so I settled on the term “proxy pointer”, which succinctly describes what is going on and is an easy concept to grasp.

Reusing pieces

Link’s original model file is a mere 227.3 kB, and there is a lot of extra content going in it, so it’s a good idea to reuse pieces when possible.

image

All reusable pieces have been moved into their own rigged mesh. Dummy triangles are used to keep limb indices consistent. A high priority is assigned, ensuring these pieces are written first so subsequent meshes can reference them.

image

Control triangles and their materials are used to tell the software what pieces to fill in.

image

Nesting is supported, so the Goron Tunic is able to cleverly reuse the Kokiri Tunic hat.

Proxies (skeletal)

Skeletal proxies provide an easy and convenient way to update every bone in a skeleton through code.

image

When PROXY is enabled on a rigged mesh, it is neatly configured to use a proxy table.

z64convert writes these as code for you…

static const uint32_t proxy_Kokiri[] = {
	0xDE010000, 0x0601FFA8,
	0xDE010000, 0x06020358,
	0xDE010000, 0x0601CE20,
	0xDE010000, 0x0601D068,
	0xDE010000, 0x060207D8,
	0xDE010000, 0x0601D430,
	0xDE010000, 0x0601D678,
	0xDE010000, 0x06020AC8,
	0xDE010000, 0x06020CC0,
	0xDE010000, 0x06020D70,
	0xDE010000, 0x06020FB8,
	0xDE010000, 0x0601EBF0,
	0xDE010000, 0x0601F098,
	0xDE010000, 0x06021298,
	0xDE010000, 0x0601F4C8,
	0xDE010000, 0x0601F908,
	0xDE010000, 0x06021378,
	0xDE010000, 0x06021E60,
};
static const uint32_t proxy_Goron[] = {
	/* etc */
};
static const uint32_t proxy_Zora[] = {
	/* etc */
};

… which you can then use in your embedded overlay like so:

/* override riggedmesh with one of the available tunics */
if (curTunic != prevTunic)
{
	unsigned proxySize = sizeof(proxy_Kokiri);
	const void *proxy = 0;
	void *overwrite = (void*)zh_seg2ram(PROXY_RIGGEDMESH);
	switch (curTunic)
	{
		case TUNIC_KOKIRI:
			proxy = proxy_Kokiri;
			break;
		case TUNIC_GORON:
			proxy = proxy_Goron;
			break;
		case TUNIC_ZORA:
			proxy = proxy_Zora;
			break;
	}
	z_bcopy(proxy, overwrite, proxySize);
	prevTunic = curTunic;
}

Proxies (generic)

If you select the Master Sword handle in Blender and look at its settings in the Object Data tab, you’ll see that it has the proxy setting enabled. This is so all references to it can easily be redirected to any model we want.

Why? Because normally, the game uses the Master Sword model when you hold the Kokiri Sword as Adult Link (the Ordon Sword in this mod). It is also displayed in Link’s sheath when the Biggoron Sword is equipped (lazy Nintendo). This behavior is unacceptable, but is easily solvable now.

/* override Master Sword and Sheath models */
if (curSword != prevSword)
{
	switch (curSword)
	{
		case SWORD_KOKIRI:
			QVLO(DL_HILT_2_PROXY) = DL_HILT_1;
			QVLO(DL_BLADE_2_PROXY) = DL_BLADE_1;
			QVLO(DL_SHEATH_PROXY) = DL_SHEATH_KOKIRI;
			break;
		case SWORD_MASTER:
			QVLO(DL_HILT_2_PROXY) = DL_HILT_2;
			QVLO(DL_BLADE_2_PROXY) = DL_BLADE_2;
			QVLO(DL_SHEATH_PROXY) = DL_SHEATH;
			break;
		case SWORD_GORON:
			QVLO(DL_HILT_2_PROXY) = DL_HILT_3;
			QVLO(DL_SHEATH_PROXY) = DL_SHEATH_BIGGORON;
			break;
	}
	prevSword = curSword;
}

Disappearing arm brace

We thought it would be cool if Link’s arm brace disappeared when he acquired the Silver/Golden Gauntlets in our mod. I accomplished this in the cheapest way possible: the material assigned to his arm brace is given a negative priority so the associated geometry is at the end of the display list. Then, when Link has acquired the upgraded gauntlets, my code steps through the display list, finds the first reference to the arm brace material, and makes the display list end there instead. Voila: no more arm brace!

image

Having the gauntlet appear on only one arm was a stylistic choice.

Underwater Zora Tunic Mask

This was another fun Twilight Princess feature to recreate. When Link is submerged in water and wearing the Zora Tunic, a mask appears over his mouth and nose. This was accomplished kind of like the arm brace: my code steps through his head display list, finds the end, and then appends a pointer to the mask. When it is time to remove the mask, it disables this pointer.

image

I would have never slept if I didn’t add this.

Boomerang

Adult Link has unused Boomerang pointers in Ocarina of Time. These were reenabled so he can use the Gale Boomerang just like in Twilight Princess.

The end

It required some clever optimizations, but miraculously, I got the final result to fit in the same space as Link’s original model file, even with all the extra equipment.

How much space was left over? I don’t know, maybe a hundred bytes… definitely less than a kilobyte.

image

This happened to me once in real life.