ref: 00109467fff99967448fdc78e227ab9e05642262
parent: c2e31a9d888d734bda5981b1d01cb5a5de97ee85
author: Snesrev <snesrev@protonmail.com>
date: Sat Oct 1 03:37:12 EDT 2022
Add support for ZSPR files to change Link's appearance
--- a/config.c
+++ b/config.c
@@ -246,6 +246,9 @@
return true;
} else if (StringEqualsNoCase(key, "NoSpriteLimits")) {
return ParseBool(value, &g_config.no_sprite_limits);
+ } else if (StringEqualsNoCase(key, "LinkGraphics")) {
+ g_config.link_graphics = value;
+ return true;
}
} else if (section == 2) {
if (StringEqualsNoCase(key, "EnableAudio")) {
@@ -399,7 +402,7 @@
}
}
}
- free(file);
+ g_config.memory_buffer = file;
}
void AfterConfigParse() {
--- a/config.h
+++ b/config.h
@@ -51,6 +51,9 @@
bool display_perf_title;
bool enable_msu;
uint32 features0;
+
+ const char *link_graphics;
+ uint8 *memory_buffer;
} Config;
extern Config g_config;
--- a/load_gfx.c
+++ b/load_gfx.c
@@ -7,7 +7,9 @@
#include "sprite.h"
#include "assets.h"
-static const uint16 kGlovesColor[2] = {0x52f6, 0x376};
+// Allow this to be overwritten
+uint16 kGlovesColor[2] = {0x52f6, 0x376};
+
static const uint8 kGraphics_IncrementalVramUpload_Dst[16] = {0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f};
static const uint8 kGraphics_IncrementalVramUpload_Src[16] = {0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e};
static const uint16 kPaletteFilteringBits[64] = {
--- a/load_gfx.h
+++ b/load_gfx.h
@@ -1,4 +1,5 @@
-#pragma once
+#ifndef ZELDA3_LOAD_GFX_H_
+#define ZELDA3_LOAD_GFX_H_
enum {
kSrmOffs_Gloves = 0x354,
@@ -10,6 +11,8 @@
kSrmOffs_Health = 0x36c,
};
+extern uint16 kGlovesColor[2];
+
const uint16 *GetFontPtr();
void ApplyPaletteFilter_bounce();
void PaletteFilter_Range(int from, int to);
@@ -160,3 +163,5 @@
void Palette_LoadForFileSelect_Shield(int k, uint8 shield);
void Palette_LoadAgahnim();
void HandleScreenFlash();
+
+#endif // ZELDA3_LOAD_GFX_H_
--- a/main.c
+++ b/main.c
@@ -22,9 +22,12 @@
#include "config.h"
#include "assets.h"
+#include "load_gfx.h"
+
// Forwards
-static bool LoadRom(const char *name);
+static bool LoadRom(const char *filename);
+static void LoadLinkGraphics();
static void PlayAudio(SDL_AudioDeviceID device, int channels, int16 *audioBuffer);
static void RenderScreen(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture *texture, bool fullscreen);
static void HandleInput(int keyCode, int modCode, bool pressed);
@@ -194,7 +197,9 @@
ParseConfigFile();
AfterConfigParse();
LoadAssets();
+ LoadLinkGraphics();
+
ZeldaInitialize();
g_zenv.ppu->extraLeftRight = UintMin(g_config.extended_aspect_ratio, kPpuExtraLeftRight);
g_snes_width = 2 * (g_config.extended_aspect_ratio * 2 + 256);
@@ -653,6 +658,39 @@
free(file);
return result;
}
+
+static bool ParseLinkGraphics(uint8 *file, size_t length) {
+ if (length < 27 || memcmp(file, "ZSPR", 4) != 0)
+ return false;
+ uint32 pixel_offs = DWORD(file[9]);
+ uint32 pixel_length = WORD(file[13]);
+ uint32 palette_offs = DWORD(file[15]);
+ uint32 palette_length = WORD(file[19]);
+ if ((uint64)pixel_offs + pixel_length > length ||
+ (uint64)palette_offs + palette_length > length ||
+ pixel_length != 0x7000)
+ return false;
+ if (kPalette_ArmorAndGloves_SIZE != 150 || kLinkGraphics_SIZE != 0x7000)
+ Die("ParseLinkGraphics: Invalid asset sizes");
+ memcpy(kLinkGraphics, file + pixel_offs, 0x7000);
+ if (palette_length >= 120)
+ memcpy(kPalette_ArmorAndGloves, file + palette_offs, 120);
+ if (palette_length >= 124)
+ memcpy(kGlovesColor, file + palette_offs + 120, 4);
+ return true;
+}
+
+static void LoadLinkGraphics() {
+ if (g_config.link_graphics) {
+ fprintf(stderr, "Loading Link Graphics: %s\n", g_config.link_graphics);
+ size_t length = 0;
+ uint8 *file = ReadFile(g_config.link_graphics, &length);
+ if (file == NULL || !ParseLinkGraphics(file, length))
+ Die("Unable to load file");
+ free(file);
+ }
+}
+
const uint8 *g_asset_ptrs[kNumberOfAssets];
uint32 g_asset_sizes[kNumberOfAssets];
--- a/types.h
+++ b/types.h
@@ -48,6 +48,7 @@
#define BYTE(x) (*(uint8*)&(x))
#define HIBYTE(x) (((uint8*)&(x))[1])
#define WORD(x) (*(uint16*)&(x))
+#define DWORD(x) (*(uint32*)&(x))
#define XY(x, y) ((y)*64+(x))
static inline uint16 swap16(uint16 v) { return (v << 8) | (v >> 8); }
--- a/zelda3.ini
+++ b/zelda3.ini
@@ -3,7 +3,6 @@
Autosave = 0
DisplayPerfInTitle = 0
-
# Extended aspect ratio, either 16:9, 16:10, or 18:9. 4:3 means normal aspect ratio.
# Add ", unchanged_sprites" to avoid changing sprite spawn/die behavior. Without this
# replays will be incompatible.
@@ -12,6 +11,7 @@
# Add "extend_y, " right before the aspect radio specifier to display 240 lines instead of 224.
ExtendedAspectRatio = 4:3
+
[Graphics]
# Fullscreen mode (0=windowed, 1=desktop fullscreen, 2=fullscreen w/mode change)
Fullscreen = 0
@@ -23,6 +23,12 @@
# Enable this option to remove the sprite limits per scan line
NoSpriteLimits = 1
+
+# Change the appearance of Link by loading a ZSPR file
+# See all sprites here: https://snesrev.github.io/sprites-gfx/snes/zelda3/link/
+# Download the files with "git clone https://github.com/snesrev/sprites-gfx.git"
+# LinkGraphics = sprites-gfx/snes/zelda3/link/sheets/megaman-x.2.zspr
+
[Sound]
EnableAudio = 1