From 1bed0047ea5259fd08a44d5a08a3cc306b7108d5 Mon Sep 17 00:00:00 2001 From: "Marco Antonio J. Costa" Date: Thu, 20 Aug 2026 21:31:10 -0300 Subject: [PATCH] Add AddressSanitizer support for clang-cl builds Select the clang-cl-asan CMake preset (RelWithDebInfo, clang-cl, ADDRESS_SANITIZER=ON) to instrument first-party code with AddressSanitizer. The wiring lives in cmake/AddressSanitizer.cmake; SANITIZERS.md tells how to add the clang-cl tools, build, and read the report. Details: - Add the clang-cl-asan preset so the asan build is one selection in Visual Studio, and a base for a CMakeUserPresets.json to inherit. - Instrument first-party code only; the vendored libraries keep default flags. - Use the release CRT and disable MSVC-STL container annotations, so instrumented and un-instrumented TUs stay compatible. - Pass /bigobj to the TUs asan inflates past the COFF section cap. - Link the asan runtime for clang-cl (lld-link does not infer it). - Stub Bink into the exe: retail binkw32.dll cannot load in an asan process (its image base is the 32-bit shadow), so compile no-op exports instead. - Route the asan report to gamedir/asan.report., since every app is a WIN32 GUI app with no console to receive the default stderr report. - Opt functions with 32-bit inline __asm out of instrumentation with cmake/asan-ignorelist.txt, one function at a time (asan reserves a register the asm needs). The rest of each translation unit stays instrumented. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 3 + CMakeLists.txt | 27 +++++--- CMakePresets.json | 21 ++++++- SANITIZERS.md | 53 ++++++++++++++++ cmake/AddressSanitizer.cmake | 50 +++++++++++++++ cmake/asan-ignorelist.txt | 104 +++++++++++++++++++++++++++++++ sgp/binkw32_stub.c | 117 +++++++++++++++++++++++++++++++++++ 7 files changed, 365 insertions(+), 10 deletions(-) create mode 100644 SANITIZERS.md create mode 100644 cmake/AddressSanitizer.cmake create mode 100644 cmake/asan-ignorelist.txt create mode 100644 sgp/binkw32_stub.c diff --git a/.gitignore b/.gitignore index a4aee5531..997488c6d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ /CMakeUserPresets.json /CMakeSettings.json /lib/ + +# AddressSanitizer reports (see SANITIZERS.md) +/gamedir/asan.report.* diff --git a/CMakeLists.txt b/CMakeLists.txt index 227b660e2..292f1f4fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,11 +33,8 @@ else() message(STATUS "Configuring WITHOUT link-time optimization ${IpoError}") endif() -option(ADDRESS_SANITIZER OFF) -if(ADDRESS_SANITIZER) - message(STATUS "AddressSanitizer ENABLED for non-Release builds") - add_compile_options($,$>,-fsanitize=address,>) -endif() +# declares the ADDRESS_SANITIZER option and the _asan genexpr used below +include(cmake/AddressSanitizer.cmake) # keep frame pointers in both MSVC and clang-cl add_compile_options(/Oy-) @@ -50,8 +47,9 @@ endif() set(usingMsBuild $) # RakNetLibStatic.lib carries /DEFAULTLIB:LIBCMT and /DEFAULTLIB:libcpmt, so the -# static runtime is forced on us until RakNet too is built from source -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +# static runtime is forced on us until RakNet too is built from source. Debug +# keeps its /MTd only when asan is off: asan needs the release CRT (see below). +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$,$>:Debug>") # make Debug builds /Z7 instead of /Zi so they can be sccache'd set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") @@ -85,6 +83,9 @@ add_subdirectory("ext/zlib") add_subdirectory("ext/VFS") target_link_libraries(bfVFS PRIVATE 7z) +# turn on asan for everything defined below; 3rd party libs above keep defaults +ja2_asan_instrument_first_party() + # from here on down our own code only: vendored code above keeps default flags include(cmake/Warnings.cmake) @@ -104,7 +105,10 @@ add_subdirectory(wine) # takes it without complaint but emits an import descriptor with an empty thunk # table, so every Bink call reaches a stale address and BinkInitialize faults the # process during the intro. mingw/GNU ar cannot do this at all, see mingw.cmake -if(NOT binkw32_lib) +# +# asan builds skip this entirely: they compile no-op Bink stubs into the exe +# (ja2_asan_link_binkw32_stub) so no binkw32.dll is imported at all. +if(NOT ADDRESS_SANITIZER AND NOT binkw32_lib) # Only the name shape differs by toolchain: lib.exe prepends the x86 leading # underscore to every name in the .def, llvm-lib and llvm-dlltool take the name as # written. The checked-in file carries the underscore for the llvm tools, so strip @@ -132,7 +136,6 @@ if(NOT binkw32_lib) endif() set(Ja2_Libraries -"${binkw32_lib}" "${CMAKE_SOURCE_DIR}/libexpatMT.lib" "winmm.lib" "ws2_32.lib" @@ -143,6 +146,10 @@ Multiplayer smacker wine ) +# empty under asan (stubs compiled into the exe instead, see above) +if(binkw32_lib) + list(APPEND Ja2_Libraries "${binkw32_lib}") +endif() # static libraries whose translation units rely on Application preprocessor definitions. set(Ja2_Libs @@ -204,6 +211,8 @@ foreach(app IN LISTS ApplicationTargets) target_link_libraries(${exe} PRIVATE ${Ja2_Libraries} legacy_stdio_definitions.lib) target_compile_definitions(${exe} PRIVATE ${compilationFlags} ${debugFlags}) + ja2_asan_link_binkw32_stub(${exe}) + # language library for the application, e.g. JA2MAPEDITOR_i18n — one per app, all 8 # languages compiled in, selected at runtime by BindLanguageStrings set(language_library ${exe}_i18n) diff --git a/CMakePresets.json b/CMakePresets.json index 068236716..c70187d5b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -51,11 +51,30 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + }, + { + "name": "clang-cl-asan", + "inherits": "base", + "displayName": "clang-cl RelWithDebInfo (AddressSanitizer)", + "description": "AddressSanitizer build. Needs the C++ Clang tools for Windows component; see SANITIZERS.md.", + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ "Windows" ], + "intelliSenseMode": "windows-clang-x86" + } + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "ADDRESS_SANITIZER": "ON" + } } ], "buildPresets": [ { "name": "msvc-relwithdebinfo", "configurePreset": "msvc-relwithdebinfo" }, { "name": "msvc-debug", "configurePreset": "msvc-debug" }, - { "name": "msvc-release", "configurePreset": "msvc-release" } + { "name": "msvc-release", "configurePreset": "msvc-release" }, + { "name": "clang-cl-asan", "configurePreset": "clang-cl-asan" } ] } diff --git a/SANITIZERS.md b/SANITIZERS.md new file mode 100644 index 000000000..6e4240da5 --- /dev/null +++ b/SANITIZERS.md @@ -0,0 +1,53 @@ +# AddressSanitizer + +AddressSanitizer (asan) finds memory errors at runtime: out-of-bounds accesses, +use-after-free, and similar. It reports the exact faulting access. + +## Requirements + +asan requires `C++ Clang tools for Windows` (MSVC's AddressSanitizer does not +support -fsanitize-ignorelist). + +It is NOT installed by default . You can install it via the Visual Studio +Installer. + +## Build and run + +Either use the included `clang-cl-asan` preset, or inherit from it in your own +`CMakeUserPresets.json` + +## Debug from Visual Studio + +asan needs `clang_rt.asan_dynamic-i386.dll` at run time. MSVC ships this DLL +with the x86 tools, but Visual Studio does not add that folder to the debugger +PATH. To start the exe with F5, add the tools folder to the +debugger PATH: + +1. Access the CMake Targets View in Solution Explorer +2. Right-click the exe target (for example `JA2.exe`) and select `Add Debug Configuration`. + Visual Studio creates `launch.vs.json` in the `.vs` folder and adds a configuration with + the correct `projectTarget`. +3. Add an `env` block to that configuration: + +```json + "env": { + "PATH": "C:\\Program Files\\Microsoft Visual Studio\\18\\Community\\VC\\Tools\\MSVC\\14.51.36231\\bin\\Hostx64\\x86;${env.PATH}" + } +``` + +Set the first path to the `bin\Hostx64\x86` folder of your MSVC tools. To find +it, search your Visual Studio installation for `clang_rt.asan_dynamic-i386.dll`. +Your edition (`Community`), MSVC version (`14.51.36231`), and host (`Hostx64` or +`Hostx86`) can differ. + +## Reading the report + +Every app is a GUI app with no console, so asan writes the report to a file by +default: + +``` +gamedir/asan.report. +``` + +When asan finds an error, the run stops and the report names the faulting access. +Set the `ASAN_OPTIONS` environment variable to change asan behaviour. diff --git a/cmake/AddressSanitizer.cmake b/cmake/AddressSanitizer.cmake new file mode 100644 index 000000000..594c5b847 --- /dev/null +++ b/cmake/AddressSanitizer.cmake @@ -0,0 +1,50 @@ +# AddressSanitizer wiring, kept in one place. Include this early: it declares +# the option and the _asan generator expression that the CRT selection in the +# root CMakeLists depends on. The two functions are ordered hook points the +# root calls at the right moments: +# ja2_asan_instrument_first_party() — after the vendored add_subdirectory()s +# (they must keep default flags), before our own targets are defined. +# ja2_asan_link_binkw32_stub() — per executable, links the Bink stubs in. + +option(ADDRESS_SANITIZER "Enable AddressSanitizer for Debug and RelWithDebInfo" OFF) + +# asan only on the debuggable configs, shared by the flag and the CRT choice +set(_asan "$,$,$>>") + +if(ADDRESS_SANITIZER) + message(STATUS "AddressSanitizer ENABLED for Debug and RelWithDebInfo (first-party code only)") + message(STATUS " Bink stubbed into the exe; no binkw32.dll import, so the asan exe runs from gamedir untouched") + # keep MSVC-STL container annotations uniform across un-instrumented TUs + add_compile_definitions("$<${_asan}:_DISABLE_STL_ANNOTATION>") + # retail binkw32.dll cannot load in an asan process (image base 0x30000000 is + # the 32-bit shadow). __RADINEXE__ makes bink.h declare the Bink functions as + # in-exe calls instead of dllimports (sgp/RAD.H), so ja2_asan_link_binkw32_stub + # can satisfy them from sgp/binkw32_stub.c and the exe imports no binkw32.dll. + # Configure-time, not per-config: it must stay in lockstep with dropping the + # binkw32 import library from the link (see root CMakeLists). + add_compile_definitions(__RADINEXE__) +endif() + +function(ja2_asan_instrument_first_party) + # instrument first-party code; /bigobj for the TUs asan inflates past the COFF + # section cap (LanguageStrings.cpp). The ignorelist opts individual functions + # with 32-bit inline __asm out of instrumentation (see cmake/asan-ignorelist.txt). + # Empty when the option is off. + add_compile_options( + "$<${_asan}:-fsanitize=address;-fsanitize-ignorelist=${CMAKE_SOURCE_DIR}/cmake/asan-ignorelist.txt;/bigobj>") + + # clang-cl/lld-link do not infer the asan runtime; /WHOLEARCHIVE is invalid for llvm-lib + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_link_options( + "$<$,EXECUTABLE>>:clang_rt.asan_dynamic-i386.lib;/WHOLEARCHIVE:clang_rt.asan_static_runtime_thunk-i386.lib>") + endif() +endfunction() + +function(ja2_asan_link_binkw32_stub exe) + # Compile the no-op Bink exports into the exe. With __RADINEXE__ above the game + # calls them directly, so this replaces the binkw32.dll the retail import + # library would have pulled in. + if(ADDRESS_SANITIZER) + target_sources(${exe} PRIVATE "${CMAKE_SOURCE_DIR}/sgp/binkw32_stub.c") + endif() +endfunction() diff --git a/cmake/asan-ignorelist.txt b/cmake/asan-ignorelist.txt new file mode 100644 index 000000000..b2bf46934 --- /dev/null +++ b/cmake/asan-ignorelist.txt @@ -0,0 +1,104 @@ +# AddressSanitizer ignorelist. Functions with 32-bit inline __asm blocks: +# asan's shadow-base register reservation starves a register the asm needs, +# so instrumenting them fails to compile. Excluded from instrumentation only +# (still linked and called); the rest of each translation unit stays checked. +# Patterns end in @@ so each matches one clang-cl (MSVC) mangled name exactly, +# never a longer-named sibling (e.g. Blt16BPPTo16BPP vs Blt16BPPTo16BPPTrans). +# Regenerate: grep the *.cpp for __asm / _asm and list the enclosing functions. + +[address] +# sgp/vobject_blitters.cpp +fun:*BlitZRect@@* +fun:*Blt16BPPBufferPixelateRectWithColor@@* +fun:*Blt16BPPBufferShadowRect@@* +fun:*Blt16BPPBufferShadowRectAlternateTable@@* +fun:*Blt16BPPDataTo16BPPBufferTransZClip@@* +fun:*Blt16BPPDataTo16BPPBufferTransparentClip@@* +fun:*Blt16BPPTo16BPP@@* +fun:*Blt16BPPTo16BPPMirror@@* +fun:*Blt8BPPDataSubTo16BPPBuffer@@* +fun:*Blt8BPPDataTo16BPPBuffer@@* +fun:*Blt8BPPDataTo16BPPBufferFullTransparent@@* +fun:*Blt8BPPDataTo16BPPBufferHalf@@* +fun:*Blt8BPPDataTo16BPPBufferHalfRect@@* +fun:*Blt8BPPDataTo16BPPBufferIntensity@@* +fun:*Blt8BPPDataTo16BPPBufferIntensityClip@@* +fun:*Blt8BPPDataTo16BPPBufferIntensityZ@@* +fun:*Blt8BPPDataTo16BPPBufferIntensityZClip@@* +fun:*Blt8BPPDataTo16BPPBufferIntensityZNB@@* +fun:*Blt8BPPDataTo16BPPBufferIntensityZNBClip@@* +fun:*Blt8BPPDataTo16BPPBufferMask@@* +fun:*Blt8BPPDataTo16BPPBufferMonoShadow@@* +fun:*Blt8BPPDataTo16BPPBufferMonoShadowClip@@* +fun:*Blt8BPPDataTo16BPPBufferOutline@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineClip@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineShadow@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineShadowClip@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineZ@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineZClip@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineZNB@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineZPixelateObscured@@* +fun:*Blt8BPPDataTo16BPPBufferOutlineZPixelateObscuredClip@@* +fun:*Blt8BPPDataTo16BPPBufferShadow@@* +fun:*Blt8BPPDataTo16BPPBufferShadowClip@@* +fun:*Blt8BPPDataTo16BPPBufferShadowZ@@* +fun:*Blt8BPPDataTo16BPPBufferShadowZClip@@* +fun:*Blt8BPPDataTo16BPPBufferShadowZNB@@* +fun:*Blt8BPPDataTo16BPPBufferShadowZNBClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransMirror@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadow@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowBelowOrEqualZNBClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowClipAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZ@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZClipAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNB@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBClipAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBObscured@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBObscuredAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBObscuredClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBObscuredClipAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransShadowZNBObscuredTest@@* +fun:*Blt8BPPDataTo16BPPBufferTransZ@@* +fun:*Blt8BPPDataTo16BPPBufferTransZClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransZClipPixelate@@* +fun:*Blt8BPPDataTo16BPPBufferTransZClipPixelateObscured@@* +fun:*Blt8BPPDataTo16BPPBufferTransZClipTranslucent@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNB@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNBClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNBClipColor@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNBClipPixelate@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNBClipTranslucent@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNBColor@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNBPixelate@@* +fun:*Blt8BPPDataTo16BPPBufferTransZNBTranslucent@@* +fun:*Blt8BPPDataTo16BPPBufferTransZPixelate@@* +fun:*Blt8BPPDataTo16BPPBufferTransZPixelateObscured@@* +fun:*Blt8BPPDataTo16BPPBufferTransZTranslucent@@* +fun:*Blt8BPPDataTo16BPPBufferTransparent@@* +fun:*Blt8BPPDataTo16BPPBufferTransparentClip@@* +fun:*Blt8BPPTo8BPP@@* +fun:*FillRect16BPP@@* +fun:*blendWithAlpha@@* +# sgp/shading.cpp +fun:*FindIndecies@@* +# TileEngine/renderworld.cpp +fun:*Blt8BPPDataTo16BPPBufferTransInvZ@@* +fun:*Blt8BPPDataTo16BPPBufferTransZIncClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransZIncClipZSameZBurnsThrough@@* +fun:*Blt8BPPDataTo16BPPBufferTransZIncObscureClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransZTransShadowIncClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransZTransShadowIncClipAlpha@@* +fun:*Blt8BPPDataTo16BPPBufferTransZTransShadowIncObscureClip@@* +fun:*Blt8BPPDataTo16BPPBufferTransZTransShadowIncObscureClipAlpha@@* +fun:*IsTileRedundent@@* +fun:*Zero8BPPDataTo16BPPBufferTransparent@@* +# TileEngine/Interactive Tiles.cpp +fun:*CheckVideoObjectScreenCoordinateInData@@* +# Ja2/profiler.cpp +fun:*getCPUCount@PerfManager@@* diff --git a/sgp/binkw32_stub.c b/sgp/binkw32_stub.c new file mode 100644 index 000000000..935b4dcbc --- /dev/null +++ b/sgp/binkw32_stub.c @@ -0,0 +1,117 @@ +/* No-op Bink exports, linked straight into AddressSanitizer builds. + * + * Retail binkw32.dll is based at 0x30000000, which is asan's 32-bit Windows + * shadow (kWindowsShadowOffset32 = 3<<28). Loading it aborts asan before + * main(). Rather than ship a stub DLL, the asan build defines __RADINEXE__ so + * bink.h declares these as plain in-exe functions (see sgp/RAD.H) instead of + * dllimports, and compiles this file into the exe. Every call returns 0, so + * BinkOpen fails and the game skips video. The asan exe imports no binkw32.dll + * at all, so gamedir/binkw32.dll is never consulted or replaced. */ + +/* WIN32 GUI apps have no console, so asan's stderr report is lost. Route it to + * gamedir/asan.report. instead. ASAN_OPTIONS overrides this. */ +__declspec(dllexport) const char *__asan_default_options(void) +{ + return "log_path=asan.report"; +} + +typedef void *P; + +#define S0(fn) P __stdcall fn(void) { return 0; } +#define S1(fn) P __stdcall fn(P a) { return 0; } +#define S2(fn) P __stdcall fn(P a, P b) { return 0; } +#define S3(fn) P __stdcall fn(P a, P b, P c) { return 0; } +#define S4(fn) P __stdcall fn(P a, P b, P c, P d) { return 0; } +#define S5(fn) P __stdcall fn(P a, P b, P c, P d, P e) { return 0; } +#define S7(fn) P __stdcall fn(P a, P b, P c, P d, P e, P f, P g) { return 0; } +#define S11(fn) P __stdcall fn(P a, P b, P c, P d, P e, P f, P g, P h, P i, P j, P k) { return 0; } +#define S12(fn) P __stdcall fn(P a, P b, P c, P d, P e, P f, P g, P h, P i, P j, P k, P l) { return 0; } +#define S13(fn) P __stdcall fn(P a, P b, P c, P d, P e, P f, P g, P h, P i, P j, P k, P l, P m) { return 0; } +#define S14(fn) P __stdcall fn(P a, P b, P c, P d, P e, P f, P g, P h, P i, P j, P k, P l, P m, P n14) { return 0; } +#define S15(fn) P __stdcall fn(P a, P b, P c, P d, P e, P f, P g, P h, P i, P j, P k, P l, P m, P n14, P o) { return 0; } + +S3(BinkBufferBlit) +S3(BinkBufferCheckWinPos) +S2(BinkBufferClear) +S1(BinkBufferClose) +S1(BinkBufferGetDescription) +S0(BinkBufferGetError) +S1(BinkBufferLock) +S4(BinkBufferOpen) +S2(BinkBufferSetDirectDraw) +S2(BinkBufferSetHWND) +S3(BinkBufferSetOffset) +S3(BinkBufferSetResolution) +S3(BinkBufferSetScale) +S1(BinkBufferUnlock) +S5(BinkCheckCursor) +S1(BinkClose) +S1(BinkCloseTrack) +S7(BinkCopyToBuffer) +S11(BinkCopyToBufferRect) +S1(BinkDDSurfaceType) +S1(BinkDX8SurfaceType) +S1(BinkDoFrame) +S0(BinkGetError) +S3(BinkGetKeyFrame) +S3(BinkGetRealtime) +S2(BinkGetRects) +S2(BinkGetSummary) +S2(BinkGetTrackData) +S2(BinkGetTrackID) +S2(BinkGetTrackMaxSize) +S2(BinkGetTrackType) +S3(BinkGoto) +S2(BinkIsSoftwareCursor) +S0(BinkLogoAddress) +S1(BinkNextFrame) +S2(BinkOpen) +S1(BinkOpenDirectSound) +S1(BinkOpenMiles) +S2(BinkOpenTrack) +S1(BinkOpenWaveOut) +S2(BinkPause) +S1(BinkRestoreCursor) +S1(BinkService) +S1(BinkSetError) +S2(BinkSetFrameRate) +S1(BinkSetIO) +S1(BinkSetIOSize) +S5(BinkSetMixBinVolumes) +S4(BinkSetMixBins) +S3(BinkSetPan) +S1(BinkSetSimulate) +S2(BinkSetSoundOnOff) +S2(BinkSetSoundSystem) +S2(BinkSetSoundTrack) +S2(BinkSetVideoOnOff) +S3(BinkSetVolume) +S1(BinkWait) +S2(RADSetMemory) +S0(RADTimerRead) +S13(YUV_blit_16a1bpp) +S15(YUV_blit_16a1bpp_mask) +S13(YUV_blit_16a4bpp) +S15(YUV_blit_16a4bpp_mask) +S12(YUV_blit_16bpp) +S14(YUV_blit_16bpp_mask) +S12(YUV_blit_24bpp) +S14(YUV_blit_24bpp_mask) +S12(YUV_blit_24rbpp) +S14(YUV_blit_24rbpp_mask) +S13(YUV_blit_32abpp) +S15(YUV_blit_32abpp_mask) +S12(YUV_blit_32bpp) +S14(YUV_blit_32bpp_mask) +S13(YUV_blit_32rabpp) +S15(YUV_blit_32rabpp_mask) +S12(YUV_blit_32rbpp) +S14(YUV_blit_32rbpp_mask) +S12(YUV_blit_UYVY) +S14(YUV_blit_UYVY_mask) +S12(YUV_blit_YUY2) +S14(YUV_blit_YUY2_mask) +S13(YUV_blit_YV12) +S1(YUV_init) +S1(radfree) +S1(radmalloc)