From 46a1674250d5a8cce7f89c3cb55ea51a60641852 Mon Sep 17 00:00:00 2001
From: nmlgc <nmlgc@nmlgc.net>
Date: Tue, 11 May 2021 23:13:35 +0200
Subject: [PATCH] [Reverse-engineering] [th02/th03/th04/th05] snd_load() buffer
 size

Hardcoding these *might* have been acceptable if the numbers actually
matched the sizes defined in GAME.BAT, but they don't. With PMD's
AH=22h function, there's really no excuse though.
About time I looked into this, and expressed that constant as an inline
function that can easily be replaced with a proper implementation.

Part of P0139, funded by [Anonymous].
---
 libs/kaja/kaja.h              |  5 +++++
 th02/snd/{load.c => load.cpp} |  5 ++++-
 th02/snd/snd.h                | 13 +++++++++++++
 th02/snd_load.c               |  1 -
 th02/snd_load.cpp             |  1 +
 th04/snd/snd.h                |  9 +++++++++
 th05/snd/load.cpp             |  2 +-
 7 files changed, 33 insertions(+), 3 deletions(-)
 rename th02/snd/{load.c => load.cpp} (90%)
 delete mode 100644 th02/snd_load.c
 create mode 100644 th02/snd_load.cpp

diff --git a/libs/kaja/kaja.h b/libs/kaja/kaja.h
index 7becf66b..9c2e984d 100644
--- a/libs/kaja/kaja.h
+++ b/libs/kaja/kaja.h
@@ -24,6 +24,11 @@ typedef enum {
 	PMD_SE_PLAY = 0x0C,
 	PMD_GET_WORKAREA_ADDRESS = 0x10,
 
+	// Returns the sizes of the song (AL), instrument (AH), and sound effect
+	// (DL) buffers, in KiB. These can be customized on the PMD command line
+	// with the /M, /V and /E options respectively.
+	PMD_GET_BUFFER_SIZES = 0x22,
+
 	_kaja_func_t_FORCE_UINT16 = 0xFFFF
 } kaja_func_t;
 
diff --git a/th02/snd/load.c b/th02/snd/load.cpp
similarity index 90%
rename from th02/snd/load.c
rename to th02/snd/load.cpp
index c9c0c17d..400b19e0 100644
--- a/th02/snd/load.c
+++ b/th02/snd/load.cpp
@@ -1,5 +1,6 @@
 #pragma option -zCSHARED
 
+extern "C" {
 #include "platform.h"
 #include "x86real.h"
 #include "libs/kaja/kaja.h"
@@ -47,7 +48,7 @@ void snd_load(const char fn[SND_FN_LEN], kaja_func_t func)
 
 	// DOS file read; song data address is in DS:DX
 	_AX = 0x3F00;
-	_CX = 0x5000;
+	_CX = snd_load_size();
 	geninterrupt(0x21);
 
 	__asm { pop ds; }
@@ -56,3 +57,5 @@ void snd_load(const char fn[SND_FN_LEN], kaja_func_t func)
 	_AH = 0x3E;
 	geninterrupt(0x21);
 }
+
+}
diff --git a/th02/snd/snd.h b/th02/snd/snd.h
index f36fa348..99b4225f 100644
--- a/th02/snd/snd.h
+++ b/th02/snd/snd.h
@@ -60,6 +60,19 @@ void snd_delay_until_volume(uint8_t volume);
 #define SND_FN_LEN 13
 
 #if defined(PMD) && (GAME <= 3) /* requires kaja.h */
+	#if defined(__cplusplus) && (GAME <= 4)
+		static inline uint16_t snd_load_size() {
+			// ZUN bug: Should rather retrieve the maximum data size for song
+			// or sound effect data via PMD_GET_BUFFER_SIZES, instead of
+			// hardcoding a random maximum and risking overflowing PMD's data
+			// buffer.
+			// (Unfortunately, MMD lacks a similar function...)
+			// MODDERS: When implementing this properly, rearrange the call
+			// sites to make sure that AX isn't destroyed.
+			return 0x5000;
+		}
+	#endif
+
 	// Loads a song in .M format ([func] = SND_LOAD_SONG) or a sound effect
 	// bank in EFC format ([func] = SND_LOAD_SE) into the respective work
 	// buffer of the sound driver. If MIDI is used, 'md' is appended to the
diff --git a/th02/snd_load.c b/th02/snd_load.c
deleted file mode 100644
index 54dce7e7..00000000
--- a/th02/snd_load.c
+++ /dev/null
@@ -1 +0,0 @@
-#include "th02/snd/load.c"
diff --git a/th02/snd_load.cpp b/th02/snd_load.cpp
new file mode 100644
index 00000000..1c1b2b14
--- /dev/null
+++ b/th02/snd_load.cpp
@@ -0,0 +1 @@
+#include "th02/snd/load.cpp"
diff --git a/th04/snd/snd.h b/th04/snd/snd.h
index 1daf4ba4..f1c68ab1 100644
--- a/th04/snd/snd.h
+++ b/th04/snd/snd.h
@@ -39,6 +39,7 @@ static inline bool16 snd_se_active() {
 // sets [snd_se_mode] and [snd_bgm_mode] accordingly. Returns [snd_bgm_mode].
 int pascal snd_determine_modes(int req_bgm_mode, int req_se_mode);
 
+#if defined(PMD) /* requires kaja.h */
 // Loads a song ([func] = SND_LOAD_SONG) or a sound effect bank ([func] =
 // SND_LOAD_SE) into the respective work buffer of the sound driver. [fn] must
 // not have any extension. Depending on [snd_bgm_mode], [snd_se_mode], and
@@ -60,3 +61,11 @@ int pascal snd_determine_modes(int req_bgm_mode, int req_se_mode);
 // Note that the TH05 version will infinitely loop if neither the file for the
 // current [snd_bgm_mode] nor "[fn].m" exist.
 void pascal snd_load(const char fn[SND_FN_LEN], int16_t func);
+
+#if defined(__cplusplus) && (GAME == 5)
+	// Refer to TH02's implementation for an explanation of how wrong this is.
+	static inline uint16_t snd_load_size() {
+		return 0xFFFF;
+	}
+#endif
+#endif
diff --git a/th05/snd/load.cpp b/th05/snd/load.cpp
index 0815946c..3a1faba7 100644
--- a/th05/snd/load.cpp
+++ b/th05/snd/load.cpp
@@ -101,7 +101,7 @@ void pascal snd_load(const char fn[SND_FN_LEN], int16_t func)
 
 	// DOS file read; song data address is in DS:DX
 	_AX = 0x3F00;
-	_CX = 0xFFFF;
+	_CX = snd_load_size();
 	geninterrupt(0x21);
 
 	// DOS file close