Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 154 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,23 @@ set(bf2_aidll_only_sources
)
list(REMOVE_ITEM bf2_engine_sources ${bf2_aidll_only_sources})

# These translation units belong to BF2VoipServer_w32ded.dll, not bf2_w32ded.exe. The whole
# Code/BF2/VoipServer subtree is a THIRD server-side image (the bf2_voipserver target, below); the
# glob above discovers its units but they never link into the engine. Code/Build/build.cmd mirrors
# this with a directory rule. Add each VoipServer file here as it lands.
set(bf2_voipserver_only_sources
"${BF2_ROOT}/VoipServer/VoipServer.cpp"
"${BF2_ROOT}/VoipServer/VoipServerInstance.cpp"
"${BF2_ROOT}/VoipServer/VoipRouter.cpp"
"${BF2_ROOT}/VoipServer/VoipClients.cpp"
"${BF2_ROOT}/VoipServer/VoipOutgoingChannels.cpp"
"${BF2_ROOT}/VoipServer/VoipDebug.cpp"
)
list(REMOVE_ITEM bf2_engine_sources ${bf2_voipserver_only_sources})

# BF2VoipServerShell.cpp carries the standalone exe's main; it belongs only to bf2_voipserver_standalone.
list(REMOVE_ITEM bf2_engine_sources "${BF2_ROOT}/VoipServer/BF2VoipServerShell.cpp")

# CLIENT-ONLY translation units. A TU that exists only in BF2.exe belongs to the client image and must not
# enter bf2_w32ded - list it here, the same discipline as the AIDLL list above (Code/Build/build.cmd
# carries the mirror list). Client-only REGIONS of shared TUs are not a manifest matter: they sit
Expand Down Expand Up @@ -976,6 +993,11 @@ if(BF2_BUILD_LINUX)
if(TARGET dice_py)
target_link_libraries("${BF2_DEDICATED_TARGET}" PRIVATE dice_py)
endif()
# The Linux image statically links the voice-server module (the win32 image is the side DLL).
# Its MD5Digest dependency arrives through bf2_gamespy_objects, which md5c.c is already part of.
target_sources("${BF2_DEDICATED_TARGET}" PRIVATE
${bf2_voipserver_only_sources}
)
elseif(EXISTS "${BF2_DICE_PY_LIB}")
target_link_libraries("${BF2_DEDICATED_TARGET}" PRIVATE "${BF2_DICE_PY_LIB}")
else()
Expand Down Expand Up @@ -1219,6 +1241,132 @@ target_link_options(bf2_aidll PRIVATE
/IGNORE:4006,4088,4098
)

# =================================================================================================
# bf2_voipserver - THE VOICE-SERVER SIDE-DLL IMAGE.
#
# BF2VoipServer_w32ded.dll is a THIRD server-side image built from this tree (the AIDLL precedent
# above). bf2_w32ded.exe imports eleven CBF2VoipServer members by decorated name;
# Code/VoipServerAbi/BF2VoipServer_w32ded.def pins them to the shipped ordinals. The routing library
# underneath (VOIP::CVoipRouterInstance and its containers) is what the instance drives.
#
# EXCLUDE_FROM_ALL, and /FORCE for the same reason as bf2_aidll: unwritten members link at RVA 0 and
# fault BY NAME on the path that reaches them. The facade bodies are Phase 0 [FRONTIER] stops until
# the facade phase replaces them (Code/BF2/VoipServer/VoipServer.cpp). OUTPUT stays in the build
# tree's bin/, NEVER the game folder - the original reference binary lives there.
# =================================================================================================
# The module sources plus the engine support the win32 image statically links (the same support
# exists in libdice; compiling it here keeps the side image self-contained). Shared by the dedicated
# and client voice DLL targets.
set(bf2_voipserver_sources
"${BF2_ROOT}/Core/Timer.cpp"
"${BF2_ROOT}/Core/Mutex.cpp" # the facade's instance-registry lock
"${BF2_ROOT}/Core/Debug.cpp" # UDPSocket's diagnostics dependency
"${BF2_ROOT}/Core/StringUtil.cpp" # Debug's StringHashFunc dependency
"${BF2_ROOT}/Core/SystemInfo.cpp" # InitChallengeString's systemInfo()
"${BF2_ROOT}/IO/Network/NetworkLayer/Socket/UDPSocket.cpp"
"${BF2_ROOT}/IO/Network/SocketManager.cpp"
"${BF2_ROOT}/External/GameSpy/md5c.c" # InitChallengeString's MD5Digest
"${BF2_ROOT}/VoipServer/VoipServer.cpp"
"${BF2_ROOT}/VoipServer/VoipServerInstance.cpp"
"${BF2_ROOT}/VoipServer/VoipDebug.cpp"
"${BF2_ROOT}/VoipServer/VoipRouter.cpp"
"${BF2_ROOT}/VoipServer/VoipClients.cpp"
"${BF2_ROOT}/VoipServer/VoipOutgoingChannels.cpp"
)

add_library(bf2_voipserver SHARED EXCLUDE_FROM_ALL
${bf2_voipserver_sources}
)
set_target_properties(bf2_voipserver PROPERTIES
LINKER_LANGUAGE CXX
OUTPUT_NAME BF2VoipServer_w32ded
PREFIX ""
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_RECONSTRUCTION "${CMAKE_BINARY_DIR}/bin"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
FOLDER "BF2"
)
bf2_configure_cpp_target(bf2_voipserver)
bf2_set_image(bf2_voipserver 1)
target_link_libraries(bf2_voipserver PRIVATE kernel32 user32 advapi32 ws2_32)
target_link_options(bf2_voipserver PRIVATE
/FORCE
/DEBUG
# The shipped image's preferred base; cosmetic under ASLR, kept for a readable RVA diff.
/BASE:0x10000000
"/DEF:${CMAKE_CURRENT_SOURCE_DIR}/Code/VoipServerAbi/BF2VoipServer_w32ded.def"
"/MAP:${CMAKE_BINARY_DIR}/bin/BF2VoipServer_w32ded.map"
/IGNORE:4006,4088,4098
)

# The dedicated server imports the CBF2VoipServer members from BF2VoipServer_w32ded.dll (the six
# decorated names in Code/VoipServerAbi/BF2VoipServer_w32ded.def). Linking the DLL target gives the
# exe its import library and forces the DLL to build first; both land in bin/.
if(TARGET bf2_voipserver)
target_link_libraries("${BF2_DEDICATED_TARGET}" PRIVATE bf2_voipserver)
endif()

# =================================================================================================
# bf2_voipserver_client - THE CLIENT LISTEN-SERVER VOICE DLL.
#
# BF2.exe can host a listen server and loads BF2VoipServer.dll. Its export set is identical to the
# dedicated image - the same eleven CBF2VoipServer members in the same ordinals - so it is the same
# module source compiled as the client image (/DDEDICATED=0). It stages beside BF2.exe in bin_client/.
# =================================================================================================
add_library(bf2_voipserver_client SHARED EXCLUDE_FROM_ALL
${bf2_voipserver_sources}
)
set_target_properties(bf2_voipserver_client PROPERTIES
LINKER_LANGUAGE CXX
OUTPUT_NAME BF2VoipServer
PREFIX ""
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin_client"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin_client"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin_client"
RUNTIME_OUTPUT_DIRECTORY_RECONSTRUCTION "${CMAKE_BINARY_DIR}/bin_client"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin_client"
FOLDER "BF2"
)
bf2_configure_cpp_target(bf2_voipserver_client)
bf2_set_image(bf2_voipserver_client 0)
target_link_libraries(bf2_voipserver_client PRIVATE kernel32 user32 advapi32 ws2_32)
target_link_options(bf2_voipserver_client PRIVATE
/FORCE
/DEBUG
/BASE:0x10000000
# Same export names; the image name comes from OUTPUT_NAME (no LIBRARY line in the .def).
"/DEF:${CMAKE_CURRENT_SOURCE_DIR}/Code/VoipServerAbi/BF2VoipServer_w32ded.def"
"/MAP:${CMAKE_BINARY_DIR}/bin_client/BF2VoipServer.map"
/IGNORE:4006,4088,4098
)

# =================================================================================================
# bf2_voipserver_standalone - THE VOICE SERVER AS ITS OWN PROCESS.
#
# The shipped BF2VoipServer.exe shell (Patch_1_50 BF2VoipServerShell.cpp): read Voip.con, create one
# instance per port, tick the facade. It drives the recovered DLL through its import library, so a
# retail game server can point at it with sv.voipServerRemote 1 and only UDP packets cross the
# boundary - avoiding the MSVCP71 std::string ABI that rules out driving this DLL from the retail
# shell. Run it beside BF2VoipServer_w32ded.dll in bin/.
# =================================================================================================
add_executable(bf2_voipserver_standalone EXCLUDE_FROM_ALL
Code/BF2/VoipServer/BF2VoipServerShell.cpp
)
set_target_properties(bf2_voipserver_standalone PROPERTIES
OUTPUT_NAME BF2VoipServerStandalone
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY_RECONSTRUCTION "${CMAKE_BINARY_DIR}/bin"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
FOLDER "BF2"
)
bf2_configure_cpp_target(bf2_voipserver_standalone)
bf2_set_image(bf2_voipserver_standalone 1)
target_link_libraries(bf2_voipserver_standalone PRIVATE bf2_voipserver kernel32 user32 advapi32 ws2_32)

# =================================================================================================
# bf2_client - THE CLIENT LINK-REACH IMAGE (2026-09-03).
#
Expand Down Expand Up @@ -1445,10 +1593,15 @@ add_custom_target(bf2_client_plugins
# point where it hurts, which is a live debugging session.
add_dependencies(bf2_client_plugins bf2_textureatlas_native bf2_renddx9_native bf2_swiffplayer_native bf2_bf2audio_native)
add_dependencies(bf2_client bf2_client_plugins)
# System imports present in the retail BF2.exe. VoIP remains intentionally omitted.
# System imports present in the retail BF2.exe.
target_link_libraries(bf2_client PRIVATE
advapi32 crypt32 dbghelp dinput8 dsound dxguid gdi32 ole32 psapi shell32 user32 winmm ws2_32
)
# BF2.exe hosts a listen server, so it imports the CBF2VoipServer members from BF2VoipServer.dll
# (the client voice image built by bf2_voipserver_client above).
if(TARGET bf2_voipserver_client)
target_link_libraries(bf2_client PRIVATE bf2_voipserver_client)
endif()
target_link_options(bf2_client PRIVATE
/FORCE
/INCREMENTAL:NO
Expand Down
89 changes: 89 additions & 0 deletions Code/BF2/Core/Timer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Wall-clock counter and elapsed-time timer.
//
// Recovered from fh2 (PerformanceCounter 0x74B130-0x74B1A0, Timer 0x74AAE0-0x74AC10). The POSIX arm
// reads gettimeofday and reports microseconds; the Win32 arm is [PLATFORM] and uses the performance
// counter. The shipped update() truncates the tick delta and the running total to 32 bits before
// scaling, so long-running timers lose the high bits - preserve that.

#include "Timer.h"

#if defined(_WIN32)
# include <windows.h>
#else
# include <sys/time.h>
#endif

namespace dice {
namespace hfe {

PerformanceCounter::PerformanceCounter()
{
}

long long PerformanceCounter::getFrequency()
{
#if defined(_WIN32)
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
return frequency.QuadPart;
#else
return 1000000;
#endif
}

long long PerformanceCounter::getCount()
{
#if defined(_WIN32)
LARGE_INTEGER count;
QueryPerformanceCounter(&count);
return count.QuadPart;
#else
struct timeval now;
now.tv_sec = 0;
now.tv_usec = 0;
gettimeofday(&now, 0);
return now.tv_usec + 1000000LL * now.tv_sec;
#endif
}

Timer::Timer()
: m_lastCount(0)
, m_accumCount(0)
, m_deltaTime(0.0)
, m_time(0.0)
, m_secondsPerCount(0.0)
{
m_secondsPerCount = 1.0 / static_cast<double>(getFrequency());
reset(0.0);
}

void Timer::reset(double seconds)
{
m_lastCount = getCount();
m_accumCount = (unsigned int)(int)(seconds / m_secondsPerCount);
update();
}

void Timer::update()
{
long long count = getCount();
long long delta = count - m_lastCount;
double scale = m_secondsPerCount;
m_lastCount = count;
m_accumCount += delta;
m_deltaTime = (double)(int)delta * scale;
m_time = (double)(int)m_accumCount * scale;
}

double Timer::getDeltaTime() const
{
return m_deltaTime;
}

double Timer::getTime() const
{
return m_time;
}

} // namespace hfe
} // namespace dice
58 changes: 58 additions & 0 deletions Code/BF2/Core/Timer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Wall-clock counter and elapsed-time timer used by the voice server.
//
// Path is [INFERRED]: no __FILE__ string attests this location. The class is dice::hfe::Timer,
// non-polymorphic, and PerformanceCounter is its stateless base. Timer::getCount is [PLATFORM]:
// the Linux arm reads gettimeofday and reports microseconds (getFrequency == 1000000); the Win32
// arm is not read yet and is expected to use the performance counter.
//
// Layout is the recovered x86-64 ELF: Timer is 48 bytes, member offsets below.

#ifndef CORE_TIMER_H // [INFERRED] guard style
#define CORE_TIMER_H

namespace dice {
namespace hfe {

// Elapsed-time source. Stateless; the frequency is a constant on the recovered build.
class PerformanceCounter
{
public:
PerformanceCounter();

// Ticks per second.
long long getFrequency();

// Monotonic tick count.
long long getCount();
};

// Tracks elapsed seconds across update() calls and supports a reset origin.
class Timer : public PerformanceCounter
{
public:
Timer();

// Re-origins the timer at the current tick count minus seconds.
void reset(double seconds);

// Samples the counter and advances deltaTime and time.
void update();

// Seconds elapsed since the last update.
double getDeltaTime() const;

// Seconds accumulated since reset.
double getTime() const;

private:
long long m_lastCount; // +0x08
long long m_accumCount; // +0x10
double m_deltaTime; // +0x18
double m_time; // +0x20
double m_secondsPerCount; // +0x28
};

} // namespace hfe
} // namespace dice

#endif // CORE_TIMER_H
Loading
Loading