8 Commits

Author SHA1 Message Date
Ethan Lee 9adb0552ff Network: Update to latest Steamworks SDK ABI.
This is mostly to support AArch64.
2026-07-15 10:13:26 -04:00
Ethan Lee ed795ea457 CMake: Rework Linux RPATH generation.
This is mostly to fix the AArch64 rpath. i686 will be different after this
change, but I haven't been shipping that architecture for a while now...

Also, took out BIN_LIBROOT which wasn't doing anything, woops!
2026-07-15 10:08:07 -04:00
Sönke Holz be29c31511 Correctly calculate FAudio block alignment
This fixes music playback on FAudio 26.01.

FAudio 26.01 added bounds checks to FAudioSourceVoice_SubmitSourceBuffer
in 033498ab08f5a1349ed5723a47aaa87163654be6.

The calculation of nBlockAlign is currently incorrect. nBlockAlign is
set to the block size in bits, not in bytes, causing this new bounds
check to trip.

Similarly, the wav_length for sound effects is in bytes.
2026-05-05 17:20:42 -04:00
Ethan Lee 1099a9ee8e Update to FAudio 26.05 2026-05-05 16:51:32 -04:00
NyakoFox 08c3590a0a Fix audio buffer issues on some platforms
On certain ports of the game, and possibly some Linux distros, the
game's audio would crackle, or slow down with loud buzzing. This commit
attempts to fix that, by enabling `FAUDIO_1024_QUANTUM`, which should
increase the internal FAudio buffer size.
2026-04-30 17:07:58 -04:00
Ethan Lee ea811e15bd 2.4.4 2026-04-21 12:02:06 -04:00
NyakoFox 4b9f26bf81 Solve render recaching issues
Past solutions were just "recache the screen textures under these known
circumstances" where whenever things OUTSIDE of those known
circumstances happened, the issue would reoccur.

I recently learned about an SDL event, `SDL_RENDER_TARGETS_RESET`,
which is for this exact problem. I ripped out all of the other places
`Screen::recacheTextures()` was called, and just slotted it in there,
and it worked perfectly.

...well, the old behavior worked perfectly; but the old behavior was
flawed as well, because it only checked for "ingame_titlemode" (if
you're in the main menu during gameplay) and forgot to check for the
map screen... plus, it didn't ever regenerate the minimap in custom
levels, which is another "persistant" render target.

Hopefully, this is the last time we'll ever have to think about this
one. I'm certainly sick of it.

This should 100% be backported into 2.4, as the bug occurs there as
well.
2026-04-12 09:09:49 -04:00
NyakoFox 6ed72297da Clamp editor mouse coordinates
The editor has never clamped mouse bounds, and instead relied on SDL to
return clamped mouse coordinates. Unfortunately, in #1140, this detail
was missed, and the behavior changed, allowing the cursor to leave the
bounds of the screen.

This isn't much of a problem most of the time because anything that
uses position as array indeces has bounds checks. Unfortunately,
placing entities outside of a room's bounds leads to entities you can't
ever touch ever again without modifying the level externally. This is
the worst with checkpoints, as pressing Enter in a room will now take
you to the out-of-bounds checkpoint, causing you to immediately wrap to
another room.

This fix should be backported to 2.4, as the issue can lead to level
files which need manual editing to fix!
2026-04-12 09:09:43 -04:00
9 changed files with 45 additions and 32 deletions
+8 -7
View File
@@ -49,15 +49,16 @@ endif()
# RPATH # RPATH
if(NOT WIN32) if(NOT WIN32)
if(APPLE) if(APPLE)
set(BIN_LIBROOT "osx")
set(BIN_RPATH "@executable_path/osx") set(BIN_RPATH "@executable_path/osx")
elseif(CMAKE_SIZEOF_VOID_P MATCHES "8")
set(BIN_LIBROOT "lib64")
set(BIN_RPATH "\$ORIGIN/lib64")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-new-dtags")
else() else()
set(BIN_LIBROOT "lib") SET(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-Wl,--disable-new-dtags")
set(BIN_RPATH "\$ORIGIN/lib")
if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
# Naming quirk for backward compatibility
SET(BIN_RPATH "\$ORIGIN/lib64")
else()
SET(BIN_RPATH "\$ORIGIN/lib${CMAKE_SYSTEM_PROCESSOR}")
endif()
endif() endif()
set(CMAKE_SKIP_BUILD_RPATH TRUE) set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
+2 -3
View File
@@ -3175,8 +3175,8 @@ void editorinput(void)
ed.old_tilex = ed.tilex; ed.old_tilex = ed.tilex;
ed.old_tiley = ed.tiley; ed.old_tiley = ed.tiley;
ed.tilex = key.mousex / 8; ed.tilex = SDL_clamp(key.mousex, 0, SCREEN_WIDTH_PIXELS - 1) / 8;
ed.tiley = key.mousey / 8; ed.tiley = SDL_clamp(key.mousey, 0, SCREEN_HEIGHT_PIXELS - 1) / 8;
bool up_pressed = key.isDown(SDLK_UP) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_UP); bool up_pressed = key.isDown(SDLK_UP) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_UP);
bool down_pressed = key.isDown(SDLK_DOWN) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_DOWN); bool down_pressed = key.isDown(SDLK_DOWN) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
@@ -3594,7 +3594,6 @@ void editorinput(void)
if (game.currentmenuname == Menu::ed_settings) if (game.currentmenuname == Menu::ed_settings)
{ {
ed.state = EditorState_DRAW; ed.state = EditorState_DRAW;
gameScreen.recacheTextures();
} }
else else
{ {
-1
View File
@@ -7800,7 +7800,6 @@ void Game::mapmenuchange(const enum GameGamestate newgamestate, const bool user_
gamestate = newgamestate; gamestate = newgamestate;
graphics.resumegamemode = false; graphics.resumegamemode = false;
mapheld = true; mapheld = true;
gameScreen.recacheTextures();
if (prevgamestate == GAMEMODE) if (prevgamestate == GAMEMODE)
{ {
+4 -1
View File
@@ -459,6 +459,10 @@ void KeyPoll::Poll(void)
break; break;
} }
case SDL_RENDER_TARGETS_RESET:
gameScreen.recacheTextures();
break;
/* Window Events */ /* Window Events */
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
switch (evt.window.event) switch (evt.window.event)
@@ -496,7 +500,6 @@ void KeyPoll::Poll(void)
} }
} }
SDL_DisableScreenSaver(); SDL_DisableScreenSaver();
gameScreen.recacheTextures();
break; break;
case SDL_WINDOWEVENT_FOCUS_LOST: case SDL_WINDOWEVENT_FOCUS_LOST:
if (!game.disablepause) if (!game.disablepause)
+6 -6
View File
@@ -134,7 +134,7 @@ public:
format.nSamplesPerSec = spec.freq; format.nSamplesPerSec = spec.freq;
format.wFormatTag = FAUDIO_FORMAT_PCM; format.wFormatTag = FAUDIO_FORMAT_PCM;
format.wBitsPerSample = SDL_AUDIO_BITSIZE(spec.format); format.wBitsPerSample = SDL_AUDIO_BITSIZE(spec.format);
format.nBlockAlign = format.nChannels * format.wBitsPerSample; format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0; format.cbSize = 0;
valid = true; valid = true;
@@ -158,7 +158,7 @@ end:
format.wBitsPerSample = sizeof(float) * 8; format.wBitsPerSample = sizeof(float) * 8;
format.nChannels = vorbis_info.channels; format.nChannels = vorbis_info.channels;
format.nSamplesPerSec = vorbis_info.sample_rate; format.nSamplesPerSec = vorbis_info.sample_rate;
format.nBlockAlign = format.nChannels * format.wBitsPerSample; format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0; format.cbSize = 0;
@@ -210,7 +210,7 @@ end:
} }
FAudioBuffer faudio_buffer = { FAudioBuffer faudio_buffer = {
FAUDIO_END_OF_STREAM, /* Flags */ FAUDIO_END_OF_STREAM, /* Flags */
wav_length * 8, /* AudioBytes */ wav_length, /* AudioBytes */
wav_buffer, /* AudioData */ wav_buffer, /* AudioData */
0, /* playbegin */ 0, /* playbegin */
0, /* playlength */ 0, /* playlength */
@@ -262,7 +262,7 @@ end:
format.nSamplesPerSec = audio_rate; format.nSamplesPerSec = audio_rate;
format.wFormatTag = FAUDIO_FORMAT_PCM; format.wFormatTag = FAUDIO_FORMAT_PCM;
format.wBitsPerSample = 16; format.wBitsPerSample = 16;
format.nBlockAlign = format.nChannels * format.wBitsPerSample; format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0; format.cbSize = 0;
voice_formats[i] = format; voice_formats[i] = format;
@@ -392,7 +392,7 @@ public:
format.wBitsPerSample = sizeof(float) * 8; format.wBitsPerSample = sizeof(float) * 8;
format.nChannels = vorbis_info.channels; format.nChannels = vorbis_info.channels;
format.nSamplesPerSec = vorbis_info.sample_rate; format.nSamplesPerSec = vorbis_info.sample_rate;
format.nBlockAlign = format.nChannels * format.wBitsPerSample; format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0; format.cbSize = 0;
@@ -731,7 +731,7 @@ musicclass::musicclass(void)
void musicclass::init(void) void musicclass::init(void)
{ {
if (FAudioCreate(&faudioctx, 0, FAUDIO_DEFAULT_PROCESSOR)) if (FAudioCreate(&faudioctx, FAUDIO_1024_QUANTUM, FAUDIO_DEFAULT_PROCESSOR))
{ {
vlog_error("Unable to initialize FAudio"); vlog_error("Unable to initialize FAudio");
return; return;
+1 -1
View File
@@ -1,6 +1,6 @@
#ifndef RELEASEVERSION_H #ifndef RELEASEVERSION_H
#define RELEASEVERSION_H #define RELEASEVERSION_H
#define RELEASE_VERSION "v2.4.3" #define RELEASE_VERSION "v2.4.4"
#endif /* RELEASEVERSION_H */ #endif /* RELEASEVERSION_H */
+11 -6
View File
@@ -5,6 +5,8 @@
#include "Alloc.h" #include "Alloc.h"
#include "Constants.h" #include "Constants.h"
#include "CustomLevels.h"
#include "Enums.h"
#include "Exit.h" #include "Exit.h"
#include "FileSystemUtils.h" #include "FileSystemUtils.h"
#include "Game.h" #include "Game.h"
@@ -14,6 +16,7 @@
#include "GraphicsResources.h" #include "GraphicsResources.h"
#endif #endif
#include "InterimVersion.h" #include "InterimVersion.h"
#include "Map.h"
#include "Render.h" #include "Render.h"
#include "Vlogging.h" #include "Vlogging.h"
@@ -152,7 +155,6 @@ void Screen::ResizeScreen(int x, int y)
if (!isWindowed || isForcedFullscreen()) if (!isWindowed || isForcedFullscreen())
{ {
int result = SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP); int result = SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
recacheTextures();
if (result != 0) if (result != 0)
{ {
vlog_error("Error: could not set the game to fullscreen mode: %s", SDL_GetError()); vlog_error("Error: could not set the game to fullscreen mode: %s", SDL_GetError());
@@ -175,7 +177,6 @@ void Screen::ResizeScreen(int x, int y)
SDL_WINDOWPOS_CENTERED_DISPLAY(windowDisplay) SDL_WINDOWPOS_CENTERED_DISPLAY(windowDisplay)
); );
} }
recacheTextures();
} }
} }
@@ -352,8 +353,6 @@ void Screen::toggleVSync(void)
{ {
vsync = !vsync; vsync = !vsync;
SDL_RenderSetVSync(m_renderer, (int) vsync); SDL_RenderSetVSync(m_renderer, (int) vsync);
recacheTextures();
} }
void Screen::recacheTextures(void) void Screen::recacheTextures(void)
@@ -366,15 +365,21 @@ void Screen::recacheTextures(void)
graphics.towerbg.tdrawback = true; graphics.towerbg.tdrawback = true;
graphics.titlebg.tdrawback = true; graphics.titlebg.tdrawback = true;
if (game.ingame_titlemode) if (game.gamestate == MAPMODE || game.ingame_titlemode)
{ {
// Redraw the cached gameplay texture if we're in the in-game menu. // Redraw the cached gameplay texture if we're in the map screen.
// Additionally, reset alpha so things don't jitter when re-entering gameplay. // Additionally, reset alpha so things don't jitter when re-entering gameplay.
float oldAlpha = graphics.alpha; float oldAlpha = graphics.alpha;
graphics.alpha = 0; graphics.alpha = 0;
gamerender(); gamerender();
graphics.alpha = oldAlpha; graphics.alpha = oldAlpha;
} }
if (map.custommode)
{
// If we're in a custom level, regenerate the minimap, which also got cleared.
cl.generatecustomminimap();
}
} }
bool Screen::isForcedFullscreen(void) bool Screen::isForcedFullscreen(void)
+12 -6
View File
@@ -10,8 +10,8 @@
/* Steamworks interface versions */ /* Steamworks interface versions */
#define VVVVVV_STEAMCLIENT "SteamClient017" #define VVVVVV_STEAMCLIENT "SteamClient023"
#define VVVVVV_STEAMUSERSTATS "STEAMUSERSTATS_INTERFACE_VERSION011" #define VVVVVV_STEAMUSERSTATS "STEAMUSERSTATS_INTERFACE_VERSION013"
#define VVVVVV_STEAMSCREENSHOTS "STEAMSCREENSHOTS_INTERFACE_VERSION003" #define VVVVVV_STEAMSCREENSHOTS "STEAMSCREENSHOTS_INTERFACE_VERSION003"
/* Shared object file name */ /* Shared object file name */
@@ -45,8 +45,16 @@ struct SteamAPICallCompleted_t
uint32_t m_cubParam; uint32_t m_cubParam;
}; };
typedef enum ESteamAPIInitResult
{
k_ESteamAPIInitResult_OK = 0,
k_ESteamAPIInitResult_FailedGeneric = 1,
k_ESteamAPIInitResult_NoSteamClient = 2,
k_ESteamAPIInitResult_VersionMismatch = 3,
} ESteamAPIInitResult;
#define FUNC_LIST \ #define FUNC_LIST \
FOREACH_FUNC(uint8_t, SteamAPI_Init, (void)) \ FOREACH_FUNC(ESteamAPIInitResult, SteamAPI_InitFlat, (void*)) \
FOREACH_FUNC(void, SteamAPI_Shutdown, (void)) \ FOREACH_FUNC(void, SteamAPI_Shutdown, (void)) \
FOREACH_FUNC(void, SteamAPI_RunCallbacks, (void)) \ FOREACH_FUNC(void, SteamAPI_RunCallbacks, (void)) \
FOREACH_FUNC(struct ISteamClient*, SteamInternal_CreateInterface, (const char*)) \ FOREACH_FUNC(struct ISteamClient*, SteamInternal_CreateInterface, (const char*)) \
@@ -58,7 +66,6 @@ struct SteamAPICallCompleted_t
int32_t, \ int32_t, \
const char* \ const char* \
)) \ )) \
FOREACH_FUNC(uint8_t, SteamAPI_ISteamUserStats_RequestCurrentStats, (struct ISteamUserStats*)) \
FOREACH_FUNC(uint8_t, SteamAPI_ISteamUserStats_StoreStats, (struct ISteamUserStats*)) \ FOREACH_FUNC(uint8_t, SteamAPI_ISteamUserStats_StoreStats, (struct ISteamUserStats*)) \
FOREACH_FUNC(uint8_t, SteamAPI_ISteamUserStats_SetAchievement, ( \ FOREACH_FUNC(uint8_t, SteamAPI_ISteamUserStats_SetAchievement, ( \
struct ISteamUserStats*, \ struct ISteamUserStats*, \
@@ -177,7 +184,7 @@ int32_t STEAM_init(void)
FUNC_LIST FUNC_LIST
#undef FOREACH_FUNC #undef FOREACH_FUNC
if (!SteamAPI_Init()) if (SteamAPI_InitFlat(NULL) != k_ESteamAPIInitResult_OK)
{ {
vlog_error("Steamworks not initialized!"); vlog_error("Steamworks not initialized!");
ClearPointers(); ClearPointers();
@@ -207,7 +214,6 @@ int32_t STEAM_init(void)
ClearPointers(); ClearPointers();
return 0; return 0;
} }
SteamAPI_ISteamUserStats_RequestCurrentStats(steamUserStats);
steamScreenshots = SteamAPI_ISteamClient_GetISteamScreenshots( steamScreenshots = SteamAPI_ISteamClient_GetISteamScreenshots(
steamClient, steamClient,
steamUser, steamUser,