├── .gitattributes ├── .gitignore ├── CHANGELOG.TXT ├── README.md ├── bin └── data │ ├── cfg │ ├── svr_movie_end.cfg │ └── svr_movie_start.cfg │ ├── games │ ├── csgo_steam.ini │ ├── cstrike_steam.ini │ └── tf2_steam.ini │ └── profiles │ └── default.ini ├── build_shaders.cmd ├── deps ├── ffmpeg │ ├── .gitignore │ └── readme.txt ├── minhook │ ├── .editorconfig │ ├── .gitignore │ ├── AUTHORS.txt │ ├── CMakeLists.txt │ ├── LICENSE.txt │ ├── README.md │ ├── build │ │ └── VC16 │ │ │ ├── MinHook.vcxproj │ │ │ ├── MinHookVC16.sln │ │ │ └── libMinHook.vcxproj │ ├── dll_resources │ │ ├── MinHook.def │ │ └── MinHook.rc │ ├── include │ │ └── MinHook.h │ └── src │ │ ├── buffer.c │ │ ├── buffer.h │ │ ├── hde │ │ ├── hde32.c │ │ ├── hde32.h │ │ ├── hde64.c │ │ ├── hde64.h │ │ ├── pstdint.h │ │ ├── table32.h │ │ └── table64.h │ │ ├── hook.c │ │ ├── trampoline.c │ │ └── trampoline.h └── stb │ ├── stb_sprintf.cpp │ └── stb_sprintf.h ├── profiling.txt ├── publish.cmd ├── shaders ├── downsample.hlsl ├── motion_sample.hlsl └── tex2vid.hlsl ├── src ├── svr_common │ ├── encoder_shared.h │ ├── svr_alloc.cpp │ ├── svr_alloc.h │ ├── svr_api.h │ ├── svr_array.h │ ├── svr_array.natvis │ ├── svr_atom.cpp │ ├── svr_atom.h │ ├── svr_common.cpp │ ├── svr_common.h │ ├── svr_common.vcxproj │ ├── svr_defs.h │ ├── svr_fifo.cpp │ ├── svr_fifo.h │ ├── svr_ini.cpp │ ├── svr_ini.h │ ├── svr_locked_array.h │ ├── svr_locked_queue.h │ ├── svr_prof.cpp │ ├── svr_prof.h │ ├── svr_queue.h │ ├── svr_standalone_common.h │ ├── svr_vdf.cpp │ └── svr_vdf.h ├── svr_encoder │ ├── encoder_audio.cpp │ ├── encoder_dnxhr.cpp │ ├── encoder_libx264.cpp │ ├── encoder_main.cpp │ ├── encoder_priv.h │ ├── encoder_render.cpp │ ├── encoder_render_threads.cpp │ ├── encoder_state.cpp │ ├── encoder_state.h │ ├── encoder_video.cpp │ ├── svr_encoder.vcxproj │ └── unity_encoder.cpp ├── svr_game │ ├── proc_encoder.cpp │ ├── proc_mosample.cpp │ ├── proc_priv.h │ ├── proc_profile.cpp │ ├── proc_profile_opts.cpp │ ├── proc_profile_opts.h │ ├── proc_state.cpp │ ├── proc_state.h │ ├── proc_velo.cpp │ ├── proc_video.cpp │ ├── svr_api.cpp │ ├── svr_game.vcxproj │ └── unity_game.cpp ├── svr_launcher │ ├── launcher_ipc.cpp │ ├── launcher_main.cpp │ ├── launcher_priv.h │ ├── launcher_start.cpp │ ├── launcher_state.cpp │ ├── launcher_state.h │ ├── launcher_steam.cpp │ ├── launcher_sys.cpp │ ├── svr_launcher.exe.manifest │ ├── svr_launcher.vcxproj │ └── unity_launcher.cpp ├── svr_shared │ ├── svr_console.cpp │ ├── svr_console.h │ ├── svr_log.cpp │ ├── svr_log.h │ └── svr_shared.vcxproj └── svr_standalone │ ├── game_audio.cpp │ ├── game_audio_v1.cpp │ ├── game_audio_v2.cpp │ ├── game_cfg.cpp │ ├── game_common.h │ ├── game_d3d9ex.cpp │ ├── game_hook.cpp │ ├── game_init.cpp │ ├── game_libs.cpp │ ├── game_overrides.cpp │ ├── game_priv.h │ ├── game_proxies.cpp │ ├── game_rec.cpp │ ├── game_scan.cpp │ ├── game_search.cpp │ ├── game_util.cpp │ ├── game_velo.cpp │ ├── game_video.cpp │ ├── game_wind.cpp │ ├── svr_standalone.vcxproj │ └── unity_standalone.cpp ├── svr.sln ├── todo.txt └── update.cmd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | /build/ 3 | *.dll 4 | *.exe 5 | *.sqlite 6 | *.ilk 7 | *.pdb 8 | *.ipdb 9 | *.iobj 10 | *.vcxproj.user 11 | *.vcxproj.filters 12 | bin/data/shaders 13 | bin/data/SVR_LOG.TXT 14 | *.mp4 15 | *.wav 16 | *.pro.user 17 | *.exp 18 | *.lib 19 | bin/data/ENCODER_LOG.txt 20 | *.mov 21 | svr.zip 22 | *.idb 23 | -------------------------------------------------------------------------------- /bin/data/cfg/svr_movie_end.cfg: -------------------------------------------------------------------------------- 1 | // This file is used by standalone SVR. Don't edit this file as it is only used by SVR. Set your own commands in svr_movie_end_user.cfg. 2 | 3 | host_framerate 0 4 | fps_max 300 5 | mat_queue_mode 2 6 | engine_no_focus_sleep 50 7 | 8 | snd_lockpartial 1 9 | snd_mix_async 1 10 | -------------------------------------------------------------------------------- /bin/data/cfg/svr_movie_start.cfg: -------------------------------------------------------------------------------- 1 | // This file is used by standalone SVR. Don't edit this file as it is only used by SVR. Set your own commands in svr_movie_start_user.cfg. 2 | 3 | sv_cheats 1 4 | 5 | // This should always be 0 because it allows the Source loop to go as fast as possible. 6 | fps_max 0 7 | 8 | // This must be 0. Related to graphics rendering. 9 | mat_queue_mode 0 10 | 11 | // This should be 0 if you intend to be alt tabbed, so the game doesn't slow down. 12 | engine_no_focus_sleep 0 13 | 14 | // These must be set for audio to work. Some of these don't exist in CSGO. 15 | snd_lockpartial 0 16 | snd_noextraupdate 1 17 | 18 | snd_mix_async 0 19 | -------------------------------------------------------------------------------- /bin/data/games/csgo_steam.ini: -------------------------------------------------------------------------------- 1 | name=Counter-Strike: Global Offensive 2 | steam_path=common\Counter-Strike Global Offensive\csgo.exe 3 | args=-game csgo 4 | -------------------------------------------------------------------------------- /bin/data/games/cstrike_steam.ini: -------------------------------------------------------------------------------- 1 | name=Counter-Strike: Source 2 | steam_path=common\Counter-Strike Source\hl2.exe 3 | args=-game cstrike 4 | -------------------------------------------------------------------------------- /bin/data/games/tf2_steam.ini: -------------------------------------------------------------------------------- 1 | name=Team Fortress 2 2 | steam_path=common\Team Fortress 2\tf_win64.exe 3 | args=-game tf 4 | -------------------------------------------------------------------------------- /bin/data/profiles/default.ini: -------------------------------------------------------------------------------- 1 | # This is the default profile that gets used when no other profile is specified. This is meant as a 2 | # general case profile and may not match your needs exactly. This does not have the perfect quality but aims for 3 | # a good balance of quality and speed and compatibility. You can create a new INI file in this directory 4 | # and override individual settings to create your own profiles. You can then use it when starting the movie like this: 5 | # 6 | # startmovie a.mov profile=my_profile 7 | # 8 | # The above command will select the my_profile.ini file in this directory. New profiles can selectively override individual 9 | # settings inside the default profile. 10 | 11 | ################################################################# 12 | # Movie encoding 13 | ################################################################# 14 | 15 | # The constant framerate to use for the movie. Whole numbers only. 16 | video_fps=60 17 | 18 | # The video encoder to use for the movie. Available options are: libx264, libx264_444, dnxhr. 19 | # libx264 is used with the NV12 pixel format (12 bits per pixel). 20 | # libx264_444 is used with the YUV444 pixel format (24 bits per pixel). 21 | # dnxhr is used with the YUV422 pixel format (16 bits per pixel). 22 | # 23 | # The libx264 encoder does not work well in video editors and is slower to encode. It is a good format if you intend 24 | # to directly distribute the output with no processing. Files using this codec will be small. 25 | # Using libx264_444 holds more color information, but may lead to compatibility issues and may not work properly in some media players. 26 | # 27 | # The dnxhr encoder works best if you intend to use the output in a video editor, and not directly distribute the resulting video. 28 | # Files using this codec will be large, but it will produce the best result if you intend to delete the files after. 29 | # Videos using dnxhr can be compressed well if you intend to archive the output later. 30 | # 31 | # You cannot use the mp4 container with dnxhr. You must instead use mov or mkv. 32 | # Note that not all video and audio encoders and containers are compatible with each other. 33 | video_encoder=dnxhr 34 | 35 | # The constant rate factor to use for the movie. This is the direct link between quality and file size. 36 | # Using 0 here produces lossless video, but may cause the video stream to not be supported in some media players. 37 | # This should be between 0 and 52. A lower value means better quality but larger file size. 38 | video_x264_crf=15 39 | 40 | # The quality vs speed to use for encoding the movie. Basically how much time to spend on quality for each frame. 41 | # This can be one of ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo. 42 | # A slower preset may decrease the file size, and will produce slightly better quality but will significantly slow down 43 | # the processing speed. 44 | # A faster preset can create worse quality and will create larger files but will be much faster. 45 | video_x264_preset=ultrafast 46 | 47 | # This decides whether or not the video stream will consist only of keyframes. 48 | # This essentially disables any compression and will very *greatly* increase the file size, but makes video editing 49 | # very fast. 50 | video_x264_intra=0 51 | 52 | # What quality to use for dnxhr. 53 | # Available options are lb, sq, hq. 54 | # The options meaning low bitrate (lb), standard quality (sq), high quality (hq). 55 | # Typically you will leave this on hq, but you can use lb and sq for fast low quality tests. 56 | video_dnxhr_profile=hq 57 | 58 | # Enable if you want audio. 59 | audio_enabled=1 60 | 61 | # The audio encoder to use for the movie. Available options are: aac. 62 | # Note that not all video and audio encoders and containers are compatible with each other. 63 | audio_encoder=aac 64 | 65 | ################################################################# 66 | # Motion blur 67 | ################################################################# 68 | 69 | # Whether or not motion blur should be enabled or not. 70 | motion_blur_enabled=0 71 | 72 | # How much to multiply the movie framerate with. The product of this is how many samples per second 73 | # that will be processed. For example, a 60 fps movie with 60 motion blur mult becomes 3600 samples per second. 74 | # This must be greater than 1. 75 | motion_blur_fps_mult=60 76 | 77 | # Fraction of how much time per movie frame (video_fps above) that should be exposed for sampling. 78 | # This should be between 0.0 and 1.0. 79 | motion_blur_exposure=0.5 80 | 81 | ################################################################# 82 | # Velocity overlay 83 | ################################################################# 84 | 85 | # The velocity overlay will show the velocity of the current player. In case of multiplayer games with spectating, 86 | # it will use the spectated player. 87 | 88 | # Whether or not the velocity overlay is enabled. 89 | velo_enabled=0 90 | 91 | # The font family name to use. 92 | # This should be the name of a font family that is installed on the system (such as Arial. You can see the 93 | # installed fonts by searching Fonts in Start). 94 | velo_font=Segoe UI 95 | 96 | # The size of the font in points. 97 | velo_font_size=48 98 | 99 | # The RGBA color components between 0 and 255. 100 | # This is the color of the text. 101 | velo_color=255 255 255 100 102 | 103 | # The RGBA color components between 0 and 255. 104 | # This is the color of the text border. 105 | velo_border_color=0 0 0 255 106 | 107 | # Border size of velocity overlay. Set to 0 to disable. The border is expanded inwards from the outer edges. 108 | velo_border_size=0 109 | 110 | # This is how tilted the text should be. 111 | # This can be one of normal, italic, extraitalic. 112 | velo_font_style=normal 113 | 114 | # This is how bold or thin the font should be. 115 | # It can be one of thin, extralight, light, semilight, normal, medium, semibold, bold, extrabold, black, extrablack. 116 | velo_font_weight=thin 117 | 118 | # Percentage alignments based from the center of the screen. First value is horizontal and second is vertical. 119 | # 0 in both axes mean the center of the screen. A positive value will increase to the right and down. 120 | # A negative value will increase to the left and up. 121 | velo_align=-80 80 122 | 123 | # Direction of text expansion. Possible values are: left, center, right. 124 | # If you intend to show the text on the left side, you would want to use left here, and so on. 125 | # A value of center means that the text will expand outwards both left and right. 126 | # A value of left means that the text will expand to the right. 127 | velo_anchor=left 128 | 129 | # Length of which velocity to show. Possible values are: xy, xyz, z. 130 | velo_length=xy 131 | -------------------------------------------------------------------------------- /build_shaders.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | where /Q fxc || ( 4 | echo This must be run in the Visual Studio Developer Command Prompt. In Visual Studio 2022, you can use Tools -^> Command Line -^> Developer Command Prompt 5 | exit /b 6 | ) 7 | 8 | REM These shaders used to be compiled with D3DCompile but that sucks because you have very minimal options and you get no information on errors. 9 | REM Before that, shaders were compiled part of the Visual Studio project but that sucks even more beacuse configuring the shaders with the configuration pages is a nightmare. 10 | REM We use files for the outputs instead of a header containing arrays because we don't want to recompile svr_game when a shader changes. 11 | 12 | set CS_FXCOPTS=/T cs_5_0 /E main /nologo /WX /Ges /Zpc /Qstrip_reflect /Qstrip_debug 13 | set VS_FXCOPTS=/T vs_5_0 /E main /nologo /WX /Ges /Zpc /Qstrip_reflect /Qstrip_debug 14 | set PS_FXCOPTS=/T ps_5_0 /E main /nologo /WX /Ges /Zpc /Qstrip_reflect /Qstrip_debug 15 | set OUTDIR=bin\data\shaders 16 | 17 | mkdir %OUTDIR% > NUL 18 | fxc shaders\tex2vid.hlsl %CS_FXCOPTS% /D AV_PIX_FMT_NV12=1 /Fo %OUTDIR%\convert_nv12 19 | fxc shaders\tex2vid.hlsl %CS_FXCOPTS% /D AV_PIX_FMT_YUV422P=1 /Fo %OUTDIR%\convert_yuv422 20 | fxc shaders\tex2vid.hlsl %CS_FXCOPTS% /D AV_PIX_FMT_YUV444P=1 /Fo %OUTDIR%\convert_yuv444 21 | fxc shaders\motion_sample.hlsl %CS_FXCOPTS% /Fo %OUTDIR%\mosample 22 | fxc shaders\downsample.hlsl %CS_FXCOPTS% /Fo %OUTDIR%\downsample 23 | -------------------------------------------------------------------------------- /deps/ffmpeg/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !readme.txt 4 | -------------------------------------------------------------------------------- /deps/ffmpeg/readme.txt: -------------------------------------------------------------------------------- 1 | Extract bin\ and lib\ and include\ of ffmpeg-n5.1-latest-win64-gpl-shared-5.1 from https://github.com/BtbN/FFmpeg-Builds/releases/tag/latest in here. 2 | 3 | Then copy the dlls in bin\ to svr\bin\. 4 | -------------------------------------------------------------------------------- /deps/minhook/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Windows-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = crlf 9 | insert_final_newline = true 10 | 11 | # 4 space indentation 12 | [*.{c,h,def}] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | # Trim trailing whitespaces 17 | [*.{c,h,def,txt}] 18 | trim_trailing_whitespace = true 19 | 20 | # UTF-8 with BOM 21 | [*.{c,h,def,txt}] 22 | charset=utf-8-bom 23 | -------------------------------------------------------------------------------- /deps/minhook/.gitignore: -------------------------------------------------------------------------------- 1 | #OS junk files 2 | [Tt]humbs.db 3 | *.DS_Store 4 | 5 | #Visual Studio files 6 | *.[Oo]bj 7 | *.user 8 | *.aps 9 | *.pch 10 | *.vspscc 11 | *.vssscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.[Cc]ache 20 | *.ilk 21 | *.log 22 | *.sbr 23 | *.sdf 24 | *.opensdf 25 | *.unsuccessfulbuild 26 | ipch/ 27 | obj/ 28 | [Ll]ib 29 | [Bb]in 30 | [Dd]ebug*/ 31 | [Rr]elease*/ 32 | Ankh.NoLoad 33 | *.VC.db 34 | .vs/ 35 | 36 | #GCC files 37 | *.o 38 | *.d 39 | *.res 40 | *.dll 41 | *.a 42 | 43 | #Visual Studio Code files 44 | .vscode/ 45 | -------------------------------------------------------------------------------- /deps/minhook/AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Tsuda Kageyu 2 | Creator, maintainer 3 | 4 | Michael Maltsev 5 | Added "Queue" functions. A lot of bug fixes. 6 | 7 | Andrey Unis 8 | Rewrote the hook engine in plain C. 9 | -------------------------------------------------------------------------------- /deps/minhook/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # MinHook - The Minimalistic API Hooking Library for x64/x86 2 | # Copyright (C) 2009-2017 Tsuda Kageyu. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions 7 | # are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 19 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | cmake_minimum_required(VERSION 3.0) 28 | 29 | project(minhook LANGUAGES C) 30 | 31 | include(CMakePackageConfigHelpers) 32 | 33 | set(MINHOOK_MAJOR_VERSION 1) 34 | set(MINHOOK_MINOR_VERSION 3) 35 | set(MINHOOK_PATCH_VERSION 3) 36 | set(MINHOOK_VERSION ${MINHOOK_MAJOR_VERSION}.${MINHOOK_MINOR_VERSION}.${MINHOOK_PATCH_VERSION}) 37 | 38 | ################ 39 | # BUILD # 40 | ################ 41 | 42 | option(BUILD_SHARED_LIBS "build shared version" OFF) 43 | 44 | set(SOURCES_MINHOOK 45 | "src/buffer.c" 46 | "src/hook.c" 47 | "src/trampoline.c" 48 | ) 49 | 50 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 51 | set(SOURCES_HDE "src/hde/hde64.c") 52 | else() 53 | set(SOURCES_HDE "src/hde/hde32.c") 54 | endif() 55 | 56 | if(BUILD_SHARED_LIBS) 57 | set(RESOURCES 58 | "dll_resources/minhook.rc" 59 | "dll_resources/minhook.def" 60 | ) 61 | endif() 62 | 63 | add_library(minhook ${SOURCES_MINHOOK} ${SOURCES_HDE} ${RESOURCES}) 64 | 65 | target_include_directories(minhook PUBLIC 66 | $ 67 | $ 68 | ) 69 | 70 | target_include_directories(minhook PRIVATE "src/") 71 | target_include_directories(minhook PRIVATE "src/hde/") 72 | 73 | if(WIN32) 74 | set_target_properties(minhook PROPERTIES PREFIX "") 75 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 76 | set_target_properties(minhook PROPERTIES DEBUG_POSTFIX ".x64d") 77 | set_target_properties(minhook PROPERTIES RELEASE_POSTFIX ".x64") 78 | set_target_properties(minhook PROPERTIES RELWITHDEBINFO_POSTFIX ".x64") 79 | set_target_properties(minhook PROPERTIES MINSIZEREL_POSTFIX ".x64") 80 | else() 81 | set_target_properties(minhook PROPERTIES DEBUG_POSTFIX ".x32d") 82 | set_target_properties(minhook PROPERTIES RELEASE_POSTFIX ".x32") 83 | set_target_properties(minhook PROPERTIES RELWITHDEBINFO_POSTFIX ".x32") 84 | set_target_properties(minhook PROPERTIES MINSIZEREL_POSTFIX ".x64") 85 | endif() 86 | else() 87 | set_target_properties(minhook PROPERTIES PREFIX "lib") 88 | set_target_properties(minhook PROPERTIES POSTFIX "") 89 | set_target_properties(minhook PROPERTIES DEBUG_POSTFIX "d") 90 | endif() 91 | 92 | ################ 93 | # CMAKE CONFIG # 94 | ################ 95 | 96 | configure_package_config_file( 97 | "cmake/minhook-config.cmake.in" 98 | "minhook-config.cmake" 99 | INSTALL_DESTINATION 100 | "lib/minhook" 101 | ) 102 | 103 | write_basic_package_version_file( 104 | "minhook-config-version.cmake" 105 | VERSION 106 | ${MINHOOK_VERSION} 107 | COMPATIBILITY 108 | AnyNewerVersion 109 | ) 110 | 111 | install( 112 | FILES 113 | "${CMAKE_CURRENT_BINARY_DIR}/minhook-config.cmake" 114 | "${CMAKE_CURRENT_BINARY_DIR}/minhook-config-version.cmake" 115 | DESTINATION 116 | "lib/minhook" 117 | ) 118 | 119 | ################### 120 | # INSTALL # 121 | ################### 122 | 123 | install(TARGETS minhook 124 | EXPORT minhook-targets 125 | RUNTIME DESTINATION "bin" 126 | ARCHIVE DESTINATION "lib" 127 | LIBRARY DESTINATION "lib" 128 | ) 129 | 130 | install( 131 | EXPORT 132 | minhook-targets 133 | NAMESPACE 134 | minhook:: 135 | DESTINATION 136 | "lib/minhook" 137 | ) 138 | 139 | install( 140 | DIRECTORY include DESTINATION . 141 | ) 142 | -------------------------------------------------------------------------------- /deps/minhook/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MinHook - The Minimalistic API Hooking Library for x64/x86 2 | Copyright (C) 2009-2017 Tsuda Kageyu. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 19 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | ================================================================================ 28 | Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov. 29 | ================================================================================ 30 | Hacker Disassembler Engine 32 C 31 | Copyright (c) 2008-2009, Vyacheslav Patkov. 32 | All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions 36 | are met: 37 | 38 | 1. Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | 2. Redistributions in binary form must reproduce the above copyright 41 | notice, this list of conditions and the following disclaimer in the 42 | documentation and/or other materials provided with the distribution. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 45 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 46 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 47 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 48 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 49 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 50 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 51 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 52 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 53 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 54 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55 | 56 | ------------------------------------------------------------------------------- 57 | Hacker Disassembler Engine 64 C 58 | Copyright (c) 2008-2009, Vyacheslav Patkov. 59 | All rights reserved. 60 | 61 | Redistribution and use in source and binary forms, with or without 62 | modification, are permitted provided that the following conditions 63 | are met: 64 | 65 | 1. Redistributions of source code must retain the above copyright 66 | notice, this list of conditions and the following disclaimer. 67 | 2. Redistributions in binary form must reproduce the above copyright 68 | notice, this list of conditions and the following disclaimer in the 69 | documentation and/or other materials provided with the distribution. 70 | 71 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 72 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 73 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 74 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 75 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 76 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 77 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 78 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 79 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 80 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 81 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 82 | -------------------------------------------------------------------------------- /deps/minhook/README.md: -------------------------------------------------------------------------------- 1 | # MinHook 2 | 3 | [![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause) 4 | 5 | The Minimalistic x86/x64 API Hooking Library for Windows 6 | 7 | http://www.codeproject.com/KB/winsdk/LibMinHook.aspx 8 | 9 | ### Version history 10 | 11 | - **v1.3.3 - 8 Jan 2017** 12 | * Added a helper function ```MH_CreateHookApiEx```. (Thanks to asm256) 13 | * Support Visual Studio 2017 RC. 14 | 15 | - **v1.3.2.1 - 9 Nov 2015** (Nuget package only) 16 | * Fixed an insufficient support for Visual Studio 2015. 17 | 18 | - **v1.3.2 - 1 Nov 2015** 19 | * Support Visual Studio 2015. 20 | * Support MinGW. 21 | 22 | - **v1.3.2-beta3 - 21 Jul 2015** (Nuget package only) 23 | * Support MinGW. (Experimental) 24 | 25 | - **v1.3.2-beta2 - 18 May 2015** 26 | * Fixed some subtle bugs. (Thanks to RaMMicHaeL) 27 | * Added a helper function ```MH_StatusToString```. (Thanks to Jan Klass) 28 | 29 | - **v1.3.2-beta - 12 May 2015** 30 | * Fixed a possible thread deadlock in x64 mode. (Thanks to Aleh Kazakevich) 31 | * Reduced the footprint a little more. 32 | * Support Visual Studio 2015 RC. (Experimental) 33 | 34 | - **v1.3.1.1 - 7 Apr 2015** (Nuget package only) 35 | * Support for WDK8.0 and 8.1. 36 | 37 | - **v1.3.1 - 19 Mar 2015** 38 | * No major changes from v1.3.1-beta. 39 | 40 | - **v1.3.1-beta - 11 Mar 2015** 41 | * Added a helper function ```MH_CreateHookApi```. (Thanks to uniskz). 42 | * Fixed a false memory leak reported by some tools. 43 | * Fixed a degradated compatibility issue. 44 | 45 | - **v1.3 - 13 Sep 2014** 46 | * No major changes from v1.3-beta3. 47 | 48 | - **v1.3-beta3 - 31 Jul 2014** 49 | * Fixed some small bugs. 50 | * Improved the memory management. 51 | 52 | - **v1.3-beta2 - 21 Jul 2014** 53 | * Changed the parameters to Windows-friendly types. (void* to LPVOID) 54 | * Fixed some small bugs. 55 | * Reorganized the source files. 56 | * Reduced the footprint a little more. 57 | 58 | - **v1.3-beta - 17 Jul 2014** 59 | * Rewrote in plain C to reduce the footprint and memory usage. (suggested by Andrey Unis) 60 | * Simplified the overall code base to make it more readable and maintainable. 61 | * Changed the license from 3-clause to 2-clause BSD License. 62 | 63 | - **v1.2 - 28 Sep 2013** 64 | * Removed boost dependency ([jarredholman](https://github.com/jarredholman/minhook)). 65 | * Fixed a small bug in the GetRelativeBranchDestination function ([pillbug99](http://www.codeproject.com/Messages/4058892/Small-Bug-Found.aspx)). 66 | * Added the ```MH_RemoveHook``` function, which removes a hook created with the ```MH_CreateHook``` function. 67 | * Added the following functions to enable or disable multiple hooks in one go: ```MH_QueueEnableHook```, ```MH_QueueDisableHook```, ```MH_ApplyQueued```. This is the preferred way of handling multiple hooks as every call to `MH_EnableHook` or `MH_DisableHook` suspends and resumes all threads. 68 | * Made the functions ```MH_EnableHook``` and ```MH_DisableHook``` enable/disable all created hooks when the ```MH_ALL_HOOKS``` parameter is passed. This, too, is an efficient way of handling multiple hooks. 69 | * If the target function is too small to be patched with a jump, MinHook tries to place the jump above the function. If that fails as well, the ```MH_CreateHook``` function returns ```MH_ERROR_UNSUPPORTED_FUNCTION```. This fixes an issue of hooking the LoadLibraryExW function on Windows 7 x64 ([reported by Obble](http://www.codeproject.com/Messages/4578613/Re-Bug-LoadLibraryExW-hook-fails-on-windows-2008-r.aspx)). 70 | 71 | - **v1.1 - 26 Nov 2009** 72 | * Changed the interface to create a hook and a trampoline function in one go to prevent the detour function from being called before the trampoline function is created. ([reported by xliqz](http://www.codeproject.com/Messages/3280374/Unsafe.aspx)) 73 | * Shortened the function names from ```MinHook_*``` to ```MH_*``` to make them handier. 74 | 75 | - **v1.0 - 22 Nov 2009** 76 | * Initial release. 77 | 78 | ### Building MinHook - Using vcpkg 79 | 80 | You can download and install MinHook using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: 81 | 82 | git clone https://github.com/microsoft/vcpkg 83 | .\vcpkg\bootstrap-vcpkg.bat 84 | .\vcpkg\vcpkg integrate install 85 | .\vcpkg\vcpkg install minhook 86 | 87 | The MinHook port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. 88 | -------------------------------------------------------------------------------- /deps/minhook/build/VC16/MinHookVC16.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.352 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" 9 | ProjectSection(ProjectDependencies) = postProject 10 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Win32 = Debug|Win32 16 | Debug|x64 = Debug|x64 17 | Release|Win32 = Release|Win32 18 | Release|x64 = Release|x64 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 22 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 23 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 24 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 25 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 26 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 27 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 28 | {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 29 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 30 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 31 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 32 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 33 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 34 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 35 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 36 | {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 37 | EndGlobalSection 38 | GlobalSection(SolutionProperties) = preSolution 39 | HideSolutionNode = FALSE 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /deps/minhook/dll_resources/MinHook.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | MH_Initialize 3 | MH_Uninitialize 4 | 5 | MH_CreateHook 6 | MH_CreateHookApi 7 | MH_CreateHookApiEx 8 | MH_RemoveHook 9 | MH_EnableHook 10 | MH_DisableHook 11 | MH_QueueEnableHook 12 | MH_QueueDisableHook 13 | MH_ApplyQueued 14 | MH_StatusToString 15 | -------------------------------------------------------------------------------- /deps/minhook/dll_resources/MinHook.rc: -------------------------------------------------------------------------------- 1 | 1 VERSIONINFO 2 | FILEVERSION 1,3,3,0 3 | PRODUCTVERSION 1,3,3,0 4 | FILEFLAGSMASK 0x17L 5 | #ifdef _DEBUG 6 | FILEFLAGS 0x1L 7 | #else 8 | FILEFLAGS 0x0L 9 | #endif 10 | FILEOS 0x4L 11 | FILETYPE 0x2L 12 | FILESUBTYPE 0x0L 13 | BEGIN 14 | BLOCK "StringFileInfo" 15 | BEGIN 16 | BLOCK "040904b0" 17 | BEGIN 18 | VALUE "CompanyName", "Tsuda Kageyu" 19 | VALUE "FileDescription", "MinHook - The Minimalistic API Hook Library for x64/x86" 20 | VALUE "FileVersion", "1.3.3.0" 21 | VALUE "InternalName", "MinHookD" 22 | VALUE "LegalCopyright", "Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved." 23 | VALUE "LegalTrademarks", "Tsuda Kageyu" 24 | VALUE "ProductName", "MinHook DLL" 25 | VALUE "ProductVersion", "1.3.3.0" 26 | END 27 | END 28 | BLOCK "VarFileInfo" 29 | BEGIN 30 | VALUE "Translation", 0x409, 1200 31 | END 32 | END 33 | -------------------------------------------------------------------------------- /deps/minhook/src/buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MinHook - The Minimalistic API Hooking Library for x64/x86 3 | * Copyright (C) 2009-2017 Tsuda Kageyu. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 20 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #pragma once 30 | 31 | // Size of each memory slot. 32 | #if defined(_M_X64) || defined(__x86_64__) 33 | #define MEMORY_SLOT_SIZE 64 34 | #else 35 | #define MEMORY_SLOT_SIZE 32 36 | #endif 37 | 38 | VOID InitializeBuffer(VOID); 39 | VOID UninitializeBuffer(VOID); 40 | LPVOID AllocateBuffer(LPVOID pOrigin); 41 | VOID FreeBuffer(LPVOID pBuffer); 42 | BOOL IsExecutableAddress(LPVOID pAddress); 43 | -------------------------------------------------------------------------------- /deps/minhook/src/hde/hde32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Hacker Disassembler Engine 32 3 | * Copyright (c) 2006-2009, Vyacheslav Patkov. 4 | * All rights reserved. 5 | * 6 | * hde32.h: C/C++ header file 7 | * 8 | */ 9 | 10 | #ifndef _HDE32_H_ 11 | #define _HDE32_H_ 12 | 13 | /* stdint.h - C99 standard header 14 | * http://en.wikipedia.org/wiki/stdint.h 15 | * 16 | * if your compiler doesn't contain "stdint.h" header (for 17 | * example, Microsoft Visual C++), you can download file: 18 | * http://www.azillionmonkeys.com/qed/pstdint.h 19 | * and change next line to: 20 | * #include "pstdint.h" 21 | */ 22 | #include "pstdint.h" 23 | 24 | #define F_MODRM 0x00000001 25 | #define F_SIB 0x00000002 26 | #define F_IMM8 0x00000004 27 | #define F_IMM16 0x00000008 28 | #define F_IMM32 0x00000010 29 | #define F_DISP8 0x00000020 30 | #define F_DISP16 0x00000040 31 | #define F_DISP32 0x00000080 32 | #define F_RELATIVE 0x00000100 33 | #define F_2IMM16 0x00000800 34 | #define F_ERROR 0x00001000 35 | #define F_ERROR_OPCODE 0x00002000 36 | #define F_ERROR_LENGTH 0x00004000 37 | #define F_ERROR_LOCK 0x00008000 38 | #define F_ERROR_OPERAND 0x00010000 39 | #define F_PREFIX_REPNZ 0x01000000 40 | #define F_PREFIX_REPX 0x02000000 41 | #define F_PREFIX_REP 0x03000000 42 | #define F_PREFIX_66 0x04000000 43 | #define F_PREFIX_67 0x08000000 44 | #define F_PREFIX_LOCK 0x10000000 45 | #define F_PREFIX_SEG 0x20000000 46 | #define F_PREFIX_ANY 0x3f000000 47 | 48 | #define PREFIX_SEGMENT_CS 0x2e 49 | #define PREFIX_SEGMENT_SS 0x36 50 | #define PREFIX_SEGMENT_DS 0x3e 51 | #define PREFIX_SEGMENT_ES 0x26 52 | #define PREFIX_SEGMENT_FS 0x64 53 | #define PREFIX_SEGMENT_GS 0x65 54 | #define PREFIX_LOCK 0xf0 55 | #define PREFIX_REPNZ 0xf2 56 | #define PREFIX_REPX 0xf3 57 | #define PREFIX_OPERAND_SIZE 0x66 58 | #define PREFIX_ADDRESS_SIZE 0x67 59 | 60 | #pragma pack(push,1) 61 | 62 | typedef struct { 63 | uint8_t len; 64 | uint8_t p_rep; 65 | uint8_t p_lock; 66 | uint8_t p_seg; 67 | uint8_t p_66; 68 | uint8_t p_67; 69 | uint8_t opcode; 70 | uint8_t opcode2; 71 | uint8_t modrm; 72 | uint8_t modrm_mod; 73 | uint8_t modrm_reg; 74 | uint8_t modrm_rm; 75 | uint8_t sib; 76 | uint8_t sib_scale; 77 | uint8_t sib_index; 78 | uint8_t sib_base; 79 | union { 80 | uint8_t imm8; 81 | uint16_t imm16; 82 | uint32_t imm32; 83 | } imm; 84 | union { 85 | uint8_t disp8; 86 | uint16_t disp16; 87 | uint32_t disp32; 88 | } disp; 89 | uint32_t flags; 90 | } hde32s; 91 | 92 | #pragma pack(pop) 93 | 94 | #ifdef __cplusplus 95 | extern "C" { 96 | #endif 97 | 98 | /* __cdecl */ 99 | unsigned int hde32_disasm(const void *code, hde32s *hs); 100 | 101 | #ifdef __cplusplus 102 | } 103 | #endif 104 | 105 | #endif /* _HDE32_H_ */ 106 | -------------------------------------------------------------------------------- /deps/minhook/src/hde/hde64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Hacker Disassembler Engine 64 3 | * Copyright (c) 2008-2009, Vyacheslav Patkov. 4 | * All rights reserved. 5 | * 6 | * hde64.h: C/C++ header file 7 | * 8 | */ 9 | 10 | #ifndef _HDE64_H_ 11 | #define _HDE64_H_ 12 | 13 | /* stdint.h - C99 standard header 14 | * http://en.wikipedia.org/wiki/stdint.h 15 | * 16 | * if your compiler doesn't contain "stdint.h" header (for 17 | * example, Microsoft Visual C++), you can download file: 18 | * http://www.azillionmonkeys.com/qed/pstdint.h 19 | * and change next line to: 20 | * #include "pstdint.h" 21 | */ 22 | #include "pstdint.h" 23 | 24 | #define F_MODRM 0x00000001 25 | #define F_SIB 0x00000002 26 | #define F_IMM8 0x00000004 27 | #define F_IMM16 0x00000008 28 | #define F_IMM32 0x00000010 29 | #define F_IMM64 0x00000020 30 | #define F_DISP8 0x00000040 31 | #define F_DISP16 0x00000080 32 | #define F_DISP32 0x00000100 33 | #define F_RELATIVE 0x00000200 34 | #define F_ERROR 0x00001000 35 | #define F_ERROR_OPCODE 0x00002000 36 | #define F_ERROR_LENGTH 0x00004000 37 | #define F_ERROR_LOCK 0x00008000 38 | #define F_ERROR_OPERAND 0x00010000 39 | #define F_PREFIX_REPNZ 0x01000000 40 | #define F_PREFIX_REPX 0x02000000 41 | #define F_PREFIX_REP 0x03000000 42 | #define F_PREFIX_66 0x04000000 43 | #define F_PREFIX_67 0x08000000 44 | #define F_PREFIX_LOCK 0x10000000 45 | #define F_PREFIX_SEG 0x20000000 46 | #define F_PREFIX_REX 0x40000000 47 | #define F_PREFIX_ANY 0x7f000000 48 | 49 | #define PREFIX_SEGMENT_CS 0x2e 50 | #define PREFIX_SEGMENT_SS 0x36 51 | #define PREFIX_SEGMENT_DS 0x3e 52 | #define PREFIX_SEGMENT_ES 0x26 53 | #define PREFIX_SEGMENT_FS 0x64 54 | #define PREFIX_SEGMENT_GS 0x65 55 | #define PREFIX_LOCK 0xf0 56 | #define PREFIX_REPNZ 0xf2 57 | #define PREFIX_REPX 0xf3 58 | #define PREFIX_OPERAND_SIZE 0x66 59 | #define PREFIX_ADDRESS_SIZE 0x67 60 | 61 | #pragma pack(push,1) 62 | 63 | typedef struct { 64 | uint8_t len; 65 | uint8_t p_rep; 66 | uint8_t p_lock; 67 | uint8_t p_seg; 68 | uint8_t p_66; 69 | uint8_t p_67; 70 | uint8_t rex; 71 | uint8_t rex_w; 72 | uint8_t rex_r; 73 | uint8_t rex_x; 74 | uint8_t rex_b; 75 | uint8_t opcode; 76 | uint8_t opcode2; 77 | uint8_t modrm; 78 | uint8_t modrm_mod; 79 | uint8_t modrm_reg; 80 | uint8_t modrm_rm; 81 | uint8_t sib; 82 | uint8_t sib_scale; 83 | uint8_t sib_index; 84 | uint8_t sib_base; 85 | union { 86 | uint8_t imm8; 87 | uint16_t imm16; 88 | uint32_t imm32; 89 | uint64_t imm64; 90 | } imm; 91 | union { 92 | uint8_t disp8; 93 | uint16_t disp16; 94 | uint32_t disp32; 95 | } disp; 96 | uint32_t flags; 97 | } hde64s; 98 | 99 | #pragma pack(pop) 100 | 101 | #ifdef __cplusplus 102 | extern "C" { 103 | #endif 104 | 105 | /* __cdecl */ 106 | unsigned int hde64_disasm(const void *code, hde64s *hs); 107 | 108 | #ifdef __cplusplus 109 | } 110 | #endif 111 | 112 | #endif /* _HDE64_H_ */ 113 | -------------------------------------------------------------------------------- /deps/minhook/src/hde/pstdint.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MinHook - The Minimalistic API Hooking Library for x64/x86 3 | * Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 16 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | // Integer types for HDE. 32 | typedef INT8 int8_t; 33 | typedef INT16 int16_t; 34 | typedef INT32 int32_t; 35 | typedef INT64 int64_t; 36 | typedef UINT8 uint8_t; 37 | typedef UINT16 uint16_t; 38 | typedef UINT32 uint32_t; 39 | typedef UINT64 uint64_t; 40 | -------------------------------------------------------------------------------- /deps/minhook/src/hde/table32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Hacker Disassembler Engine 32 C 3 | * Copyright (c) 2008-2009, Vyacheslav Patkov. 4 | * All rights reserved. 5 | * 6 | */ 7 | 8 | #define C_NONE 0x00 9 | #define C_MODRM 0x01 10 | #define C_IMM8 0x02 11 | #define C_IMM16 0x04 12 | #define C_IMM_P66 0x10 13 | #define C_REL8 0x20 14 | #define C_REL32 0x40 15 | #define C_GROUP 0x80 16 | #define C_ERROR 0xff 17 | 18 | #define PRE_ANY 0x00 19 | #define PRE_NONE 0x01 20 | #define PRE_F2 0x02 21 | #define PRE_F3 0x04 22 | #define PRE_66 0x08 23 | #define PRE_67 0x10 24 | #define PRE_LOCK 0x20 25 | #define PRE_SEG 0x40 26 | #define PRE_ALL 0xff 27 | 28 | #define DELTA_OPCODES 0x4a 29 | #define DELTA_FPU_REG 0xf1 30 | #define DELTA_FPU_MODRM 0xf8 31 | #define DELTA_PREFIXES 0x130 32 | #define DELTA_OP_LOCK_OK 0x1a1 33 | #define DELTA_OP2_LOCK_OK 0x1b9 34 | #define DELTA_OP_ONLY_MEM 0x1cb 35 | #define DELTA_OP2_ONLY_MEM 0x1da 36 | 37 | unsigned char hde32_table[] = { 38 | 0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3, 39 | 0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f, 40 | 0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3, 41 | 0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa, 42 | 0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90, 43 | 0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f, 44 | 0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d, 45 | 0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59, 46 | 0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59, 47 | 0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0, 48 | 0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01, 49 | 0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11, 50 | 0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8, 51 | 0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca, 52 | 0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff, 53 | 0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03, 54 | 0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00, 55 | 0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00, 56 | 0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 57 | 0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 58 | 0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f, 59 | 0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a, 60 | 0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a, 61 | 0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a, 62 | 0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06, 63 | 0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06, 64 | 0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, 65 | 0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08, 66 | 0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01, 67 | 0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba, 68 | 0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00, 69 | 0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00, 70 | 0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07, 71 | 0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf, 72 | 0xe7,0x08,0x00,0xf0,0x02,0x00 73 | }; 74 | -------------------------------------------------------------------------------- /deps/minhook/src/hde/table64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Hacker Disassembler Engine 64 C 3 | * Copyright (c) 2008-2009, Vyacheslav Patkov. 4 | * All rights reserved. 5 | * 6 | */ 7 | 8 | #define C_NONE 0x00 9 | #define C_MODRM 0x01 10 | #define C_IMM8 0x02 11 | #define C_IMM16 0x04 12 | #define C_IMM_P66 0x10 13 | #define C_REL8 0x20 14 | #define C_REL32 0x40 15 | #define C_GROUP 0x80 16 | #define C_ERROR 0xff 17 | 18 | #define PRE_ANY 0x00 19 | #define PRE_NONE 0x01 20 | #define PRE_F2 0x02 21 | #define PRE_F3 0x04 22 | #define PRE_66 0x08 23 | #define PRE_67 0x10 24 | #define PRE_LOCK 0x20 25 | #define PRE_SEG 0x40 26 | #define PRE_ALL 0xff 27 | 28 | #define DELTA_OPCODES 0x4a 29 | #define DELTA_FPU_REG 0xfd 30 | #define DELTA_FPU_MODRM 0x104 31 | #define DELTA_PREFIXES 0x13c 32 | #define DELTA_OP_LOCK_OK 0x1ae 33 | #define DELTA_OP2_LOCK_OK 0x1c6 34 | #define DELTA_OP_ONLY_MEM 0x1d8 35 | #define DELTA_OP2_ONLY_MEM 0x1e7 36 | 37 | unsigned char hde64_table[] = { 38 | 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, 39 | 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, 40 | 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, 41 | 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, 42 | 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, 43 | 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, 44 | 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, 45 | 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, 46 | 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, 47 | 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, 48 | 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, 49 | 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, 50 | 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, 51 | 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, 52 | 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, 53 | 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, 54 | 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, 55 | 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, 56 | 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, 57 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, 58 | 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, 59 | 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, 60 | 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, 61 | 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, 62 | 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, 63 | 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, 64 | 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, 65 | 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, 66 | 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, 67 | 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, 68 | 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, 69 | 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, 70 | 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, 71 | 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, 72 | 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, 73 | 0x00,0xf0,0x02,0x00 74 | }; 75 | -------------------------------------------------------------------------------- /deps/minhook/src/trampoline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MinHook - The Minimalistic API Hooking Library for x64/x86 3 | * Copyright (C) 2009-2017 Tsuda Kageyu. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 20 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #pragma once 30 | 31 | #pragma pack(push, 1) 32 | 33 | // Structs for writing x86/x64 instructions. 34 | 35 | // 8-bit relative jump. 36 | typedef struct _JMP_REL_SHORT 37 | { 38 | UINT8 opcode; // EB xx: JMP +2+xx 39 | UINT8 operand; 40 | } JMP_REL_SHORT, *PJMP_REL_SHORT; 41 | 42 | // 32-bit direct relative jump/call. 43 | typedef struct _JMP_REL 44 | { 45 | UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx 46 | UINT32 operand; // Relative destination address 47 | } JMP_REL, *PJMP_REL, CALL_REL; 48 | 49 | // 64-bit indirect absolute jump. 50 | typedef struct _JMP_ABS 51 | { 52 | UINT8 opcode0; // FF25 00000000: JMP [+6] 53 | UINT8 opcode1; 54 | UINT32 dummy; 55 | UINT64 address; // Absolute destination address 56 | } JMP_ABS, *PJMP_ABS; 57 | 58 | // 64-bit indirect absolute call. 59 | typedef struct _CALL_ABS 60 | { 61 | UINT8 opcode0; // FF15 00000002: CALL [+6] 62 | UINT8 opcode1; 63 | UINT32 dummy0; 64 | UINT8 dummy1; // EB 08: JMP +10 65 | UINT8 dummy2; 66 | UINT64 address; // Absolute destination address 67 | } CALL_ABS; 68 | 69 | // 32-bit direct relative conditional jumps. 70 | typedef struct _JCC_REL 71 | { 72 | UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx 73 | UINT8 opcode1; 74 | UINT32 operand; // Relative destination address 75 | } JCC_REL; 76 | 77 | // 64bit indirect absolute conditional jumps that x64 lacks. 78 | typedef struct _JCC_ABS 79 | { 80 | UINT8 opcode; // 7* 0E: J** +16 81 | UINT8 dummy0; 82 | UINT8 dummy1; // FF25 00000000: JMP [+6] 83 | UINT8 dummy2; 84 | UINT32 dummy3; 85 | UINT64 address; // Absolute destination address 86 | } JCC_ABS; 87 | 88 | #pragma pack(pop) 89 | 90 | typedef struct _TRAMPOLINE 91 | { 92 | LPVOID pTarget; // [In] Address of the target function. 93 | LPVOID pDetour; // [In] Address of the detour function. 94 | LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function. 95 | 96 | #if defined(_M_X64) || defined(__x86_64__) 97 | LPVOID pRelay; // [Out] Address of the relay function. 98 | #endif 99 | BOOL patchAbove; // [Out] Should use the hot patch area? 100 | UINT nIP; // [Out] Number of the instruction boundaries. 101 | UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function. 102 | UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function. 103 | } TRAMPOLINE, *PTRAMPOLINE; 104 | 105 | BOOL CreateTrampolineFunction(PTRAMPOLINE ct); 106 | -------------------------------------------------------------------------------- /deps/stb/stb_sprintf.cpp: -------------------------------------------------------------------------------- 1 | #define STB_SPRINTF_IMPLEMENTATION 2 | #include "stb_sprintf.h" 3 | -------------------------------------------------------------------------------- /profiling.txt: -------------------------------------------------------------------------------- 1 | # 1920x1080 2 | # 60 fps veryfast crf 15 3 | # mosample mult 60 exposure 0.5 4 | 5 | # Single threaded 6 | Total work time: 5010353 7 | Download: 1235 8 | Mosample: 95 9 | 10 | # Write thread 11 | Total work time: 4352741 12 | Download: 1173 13 | Write: 244 14 | Mosample: 87 15 | 16 | # Write thread, less updating of mosample constant buffer 17 | Total work time: 4181972 18 | Download: 1168 19 | Write: 237 20 | Mosample: 82 21 | 22 | # Legacy work buffer 23 | Total work time: 4308762 24 | Download: 1184 25 | Write: 290 26 | Mosample: 85 27 | 28 | # Velo v3 (with border) 29 | Total work time: 4166655 30 | Download: 934 31 | Write: 271 32 | Mosample: 83 33 | 34 | # Velo quads 35 | Total work time: 4076607 36 | Download: 1095 37 | Write: 229 38 | Mosample: 82 39 | -------------------------------------------------------------------------------- /publish.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM We use zip because more often than not people don't have 7zip or equivalent. Windows has built in support for opening zip. 4 | REM You need 7z in your PATH to run this. 5 | 6 | setlocal 7 | 8 | where /Q devenv || ( 9 | echo This must be run in the Visual Studio Developer Command Prompt. In Visual Studio 2022, you can use Tools -^> Command Line -^> Developer Command Prompt 10 | exit /b 11 | ) 12 | 13 | devenv svr.sln /Rebuild Release 14 | 15 | mkdir publish_temp\svr 16 | 17 | copy /Y ".\bin\svr_game.dll" "publish_temp\svr\" 18 | copy /Y ".\bin\svr_game64.dll" "publish_temp\svr\" 19 | copy /Y ".\bin\svr_standalone.dll" "publish_temp\svr\" 20 | copy /Y ".\bin\svr_standalone64.dll" "publish_temp\svr\" 21 | copy /Y ".\bin\svr_launcher.exe" "publish_temp\svr\" 22 | copy /Y ".\bin\svr_launcher64.exe" "publish_temp\svr\" 23 | copy /Y ".\bin\svr_encoder.exe" "publish_temp\svr\" 24 | copy /Y ".\bin\svr_shared.dll" "publish_temp\svr\" 25 | copy /Y ".\bin\svr_shared64.dll" "publish_temp\svr\" 26 | copy /Y ".\bin\avcodec-59.dll" "publish_temp\svr\" 27 | copy /Y ".\bin\avdevice-59.dll" "publish_temp\svr\" 28 | copy /Y ".\bin\avfilter-8.dll" "publish_temp\svr\" 29 | copy /Y ".\bin\avformat-59.dll" "publish_temp\svr\" 30 | copy /Y ".\bin\avutil-57.dll" "publish_temp\svr\" 31 | copy /Y ".\bin\postproc-56.dll" "publish_temp\svr\" 32 | copy /Y ".\bin\swresample-4.dll" "publish_temp\svr\" 33 | xcopy /Q /E ".\bin\data\" "publish_temp\svr\data\" 34 | copy /Y ".\update.cmd" "publish_temp\svr\" 35 | copy /Y ".\README.MD" "publish_temp\svr\" 36 | mkdir ".\publish_temp\svr\movies" 37 | del /S /Q ".\publish_temp\svr\data\SVR_LOG.TXT" 38 | del /S /Q ".\publish_temp\svr\data\ENCODER_LOG.TXT" 39 | 40 | cd publish_temp 41 | 42 | 7z a -bd -aoa -bb0 -tzip -y svr.zip svr 43 | move /Y svr.zip ..\svr.zip 44 | 45 | cd .. 46 | rmdir /S /Q publish_temp 47 | -------------------------------------------------------------------------------- /shaders/downsample.hlsl: -------------------------------------------------------------------------------- 1 | // Downsample from 128 bpp to 32 bpp. 2 | 3 | Texture2D source_texture : register(t0); 4 | RWTexture2D dest_texture : register(u0); 5 | 6 | float4 from_linear(float4 v) 7 | { 8 | return pow(max(v, 0.0f), 1.0f / 2.2f); 9 | } 10 | 11 | // This must be synchronized with the compute shader Dispatch call in CPU code! 12 | [numthreads(8, 8, 1)] 13 | void main(uint3 dtid : SV_DispatchThreadID) 14 | { 15 | uint2 pos = dtid.xy; 16 | float4 source_pix = from_linear(source_texture.Load(dtid)); // Must go back from linear space used in mosample. 17 | dest_texture[pos] = source_pix; 18 | } 19 | -------------------------------------------------------------------------------- /shaders/motion_sample.hlsl: -------------------------------------------------------------------------------- 1 | cbuffer mosample_buffer_0 : register(b0) 2 | { 3 | float mosample_weight; 4 | }; 5 | 6 | Texture2D source_texture : register(t0); 7 | RWTexture2D dest_texture : register(u0); 8 | 9 | float4 to_linear(float4 v) 10 | { 11 | return pow(max(v, 0.0f), 2.2f); 12 | } 13 | 14 | // This must be synchronized with the compute shader Dispatch call in CPU code! 15 | [numthreads(8, 8, 1)] 16 | void main(uint3 dtid : SV_DispatchThreadID) 17 | { 18 | // Must be blending in linear space! The mosample texture is high precision so this will not burn. 19 | float4 source_pix = to_linear(source_texture.Load(dtid)); 20 | float4 new_pix = dest_texture[dtid.xy] + source_pix * mosample_weight; 21 | dest_texture[dtid.xy] = new_pix; 22 | } 23 | -------------------------------------------------------------------------------- /shaders/tex2vid.hlsl: -------------------------------------------------------------------------------- 1 | // This file is intended to be compiled into many resulting shaders. 2 | // Purpose of these are to convert the program pixel format to a video pixel format. 3 | 4 | #define AVCOL_SPC_BT709 1 5 | // #define AVCOL_SPC_BT470BG 1 6 | 7 | // -------------------------------------------------------------------------------------------------------------------- 8 | 9 | // Output texture is based in UINT (0 to 255). 10 | // Input texture based in BGRA unorm format (0.0 to 1.0). 11 | Texture2D input_texture : register(t0); 12 | 13 | // -------------------------------------------------------------------------------------------------------------------- 14 | 15 | uint3 convert_rgb_to_yuv(float3 rgb) 16 | { 17 | rgb = rgb * 255.0f; 18 | 19 | // For meme reasons you appear to need to divide by 255.0 / 219.0. 20 | // This number comes from the partial MPEG range. We don't add 16.0 / 255.0 to this. 21 | rgb /= 1.164383; 22 | 23 | uint3 ret; 24 | 25 | #if AVCOL_SPC_BT709 26 | ret.x = 16 + (rgb.x * +0.212600) + (rgb.y * +0.715200) + (rgb.z * +0.072200); 27 | ret.y = 128 + (rgb.x * -0.114572) + (rgb.y * -0.385428) + (rgb.z * +0.500000); 28 | ret.z = 128 + (rgb.x * +0.500000) + (rgb.y * -0.454153) + (rgb.z * -0.045847); 29 | #elif AVCOL_SPC_BT470BG 30 | ret.x = 16 + (rgb.x * +0.299000) + (rgb.y * +0.587000) + (rgb.z * +0.114000); 31 | ret.y = 128 + (rgb.x * -0.168736) + (rgb.y * -0.331264) + (rgb.z * +0.500000); 32 | ret.z = 128 + (rgb.x * +0.500000) + (rgb.y * -0.418688) + (rgb.z * -0.081312); 33 | #endif 34 | 35 | return ret; 36 | } 37 | 38 | // -------------------------------------------------------------------------------------------------------------------- 39 | 40 | #if AV_PIX_FMT_NV12 41 | // Used by H264. 42 | 43 | // The first plane is as large as the source material. 44 | // The second plane is half the size and has U and V interleaved. 45 | 46 | RWTexture2D output_texture_y : register(u0); 47 | RWTexture2D output_texture_uv : register(u1); 48 | 49 | void proc(uint3 dtid) 50 | { 51 | float4 pix = input_texture.Load(dtid); 52 | uint3 yuv = convert_rgb_to_yuv(pix.xyz); 53 | output_texture_y[dtid.xy] = yuv.x; 54 | output_texture_uv[dtid.xy >> 1] = uint2(yuv.yz); 55 | } 56 | 57 | #endif 58 | 59 | // -------------------------------------------------------------------------------------------------------------------- 60 | 61 | #if AV_PIX_FMT_YUV422P 62 | 63 | // The first plane is as large as the source material. 64 | // The second and third planes are half in size horizontally. 65 | // Used by dnxhr. 66 | 67 | RWTexture2D output_texture_y : register(u0); 68 | RWTexture2D output_texture_u : register(u1); 69 | RWTexture2D output_texture_v : register(u2); 70 | 71 | void proc(uint3 dtid) 72 | { 73 | float4 pix = input_texture.Load(dtid); 74 | uint3 yuv = convert_rgb_to_yuv(pix.xyz); 75 | output_texture_y[dtid.xy] = yuv.x; 76 | output_texture_u[int2(dtid.x >> 1, dtid.y)] = yuv.y; 77 | output_texture_v[int2(dtid.x >> 1, dtid.y)] = yuv.z; 78 | } 79 | 80 | #endif 81 | 82 | // -------------------------------------------------------------------------------------------------------------------- 83 | 84 | #if AV_PIX_FMT_YUV444P 85 | 86 | // Every plane is as big as the source material. 87 | 88 | RWTexture2D output_texture_y : register(u0); 89 | RWTexture2D output_texture_u : register(u1); 90 | RWTexture2D output_texture_v : register(u2); 91 | 92 | void proc(uint3 dtid) 93 | { 94 | float4 pix = input_texture.Load(dtid); 95 | uint3 yuv = convert_rgb_to_yuv(pix.xyz); 96 | output_texture_y[dtid.xy] = yuv.x; 97 | output_texture_u[dtid.xy] = yuv.y; 98 | output_texture_v[dtid.xy] = yuv.z; 99 | } 100 | 101 | #endif 102 | 103 | // -------------------------------------------------------------------------------------------------------------------- 104 | 105 | // This must be synchronized with the compute shader Dispatch call in CPU code! 106 | [numthreads(8, 8, 1)] 107 | void main(uint3 dtid : SV_DispatchThreadID) 108 | { 109 | proc(dtid); 110 | } 111 | -------------------------------------------------------------------------------- /src/svr_common/encoder_shared.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | 4 | // Shared stuff between 32-bit svr_game and 64-bit svr_encoder. 5 | 6 | // All Windows handles only use 32 bits of data, so we can safely refer to them in here as u32 with _h in the name. 7 | // https://learn.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication 8 | 9 | const s32 ENCODER_MAX_SAMPLES = 4096; // How many samples can be stored at most in the buffer placed at audio_buffer_offset. 10 | 11 | // Identifiers used by the DXGI lock for synchronizing with the shared texture. 12 | // You need to specify which device to give access to, so that's what these are. 13 | const s32 ENCODER_GAME_ID = 0; 14 | const s32 ENCODER_PROC_ID = 1; 15 | 16 | using EncoderSharedEvent = s32; 17 | 18 | enum /* EncoderSharedEvent */ 19 | { 20 | ENCODER_EVENT_NONE, 21 | ENCODER_EVENT_START, // Movie parameters will be setup. This event can fail. 22 | ENCODER_EVENT_STOP, // Rendering will stop. This event cannot fail. 23 | ENCODER_EVENT_NEW_VIDEO, // Texture at game_texture_h will have new data. This event can fail. 24 | ENCODER_EVENT_NEW_AUDIO, // New samples will be placed at audio_buffer_offset. This event can fail. 25 | }; 26 | 27 | struct EncoderSharedMovieParams 28 | { 29 | char dest_file[256]; 30 | 31 | // Incoming data specs: 32 | s32 video_height; 33 | s32 video_width; 34 | s32 audio_channels; 35 | s32 audio_hz; 36 | s32 audio_bits; 37 | 38 | // Output data specs: 39 | // These are verified by svr_game already, so svr_encoder can read from them safely. 40 | char video_encoder[32]; 41 | char audio_encoder[32]; 42 | char x264_preset[32]; 43 | char dnxhr_profile[32]; 44 | s32 video_fps; 45 | s32 x264_crf; 46 | bool x264_intra; 47 | bool use_audio; 48 | }; 49 | 50 | // Memory that is shared between the processes. 51 | struct EncoderSharedMem 52 | { 53 | EncoderSharedMovieParams movie_params; // Movie parameters and profile stuff set by svr_game on ENCODER_EVENT_START. 54 | 55 | // Shared handle to the latest game texture in the B8G8R8A8 format. Updated on ENCODER_EVENT_NEW_VIDEO. 56 | u32 game_texture_h; 57 | 58 | // Pointer types have different sizes in 32-bit and 64-bit so we have to store the offsets from the base 59 | // of the shared memory instead. The audio samples here are updated on ENCODER_EVENT_NEW_AUDIO. 60 | s32 audio_buffer_offset; 61 | 62 | s32 waiting_audio_samples; // Set by svr_game to how many audio samples are waiting at audio_buffer_offset. Updated on ENCODER_EVENT_NEW_AUDIO. 63 | 64 | u32 game_wake_event_h; // Event set by svr_encoder to wake svr_game up. 65 | u32 encoder_wake_event_h; // Event set by svr_game to wake svr_encoder up. 66 | u32 game_pid; // Game process id. Used by svr_encoder to know if the game exits so we don't get stuck. 67 | 68 | EncoderSharedEvent event_type; // Set by svr_game to let svr_encoder know what to do when woken up. Updated on all events. 69 | 70 | s32 error; // Set to 1 by svr_encoder on any error. A message will be written to error_message. 71 | char error_message[512]; // Any encoding error will be written here by svr_encoder when error is set to 1. 72 | }; 73 | -------------------------------------------------------------------------------- /src/svr_common/svr_alloc.cpp: -------------------------------------------------------------------------------- 1 | #include "svr_alloc.h" 2 | #include 3 | #include 4 | 5 | void* svr_alloc(s32 size) 6 | { 7 | return malloc(size); 8 | } 9 | 10 | void* svr_zalloc(s32 size) 11 | { 12 | void* m = svr_alloc(size); 13 | memset(m, 0, size); 14 | return m; 15 | } 16 | 17 | void* svr_realloc(void* p, s32 size) 18 | { 19 | return realloc(p, size); 20 | } 21 | 22 | wchar* svr_dup_wstr(const wchar* source) 23 | { 24 | return wcsdup(source); 25 | } 26 | 27 | char* svr_dup_str(const char* source) 28 | { 29 | return strdup(source); 30 | } 31 | 32 | void* svr_align_alloc(s32 size, s32 align) 33 | { 34 | return _aligned_malloc(size, align); 35 | } 36 | 37 | void svr_free(void* addr) 38 | { 39 | free(addr); 40 | } 41 | 42 | void svr_align_free(void* addr, s32 align) 43 | { 44 | _aligned_free(addr); 45 | } 46 | -------------------------------------------------------------------------------- /src/svr_common/svr_alloc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | 4 | void* svr_alloc(s32 size); 5 | void* svr_zalloc(s32 size); // Zero init alloc. 6 | void* svr_realloc(void* p, s32 size); 7 | wchar* svr_dup_wstr(const wchar* source); 8 | char* svr_dup_str(const char* source); 9 | void* svr_align_alloc(s32 size, s32 align); 10 | void svr_free(void* addr); 11 | void svr_align_free(void* addr, s32 align); 12 | 13 | // Easier to type when you need to allocate structures. 14 | #define SVR_ZALLOC(T) (T*)svr_zalloc(sizeof(T)) 15 | #define SVR_ZALLOC_NUM(T, NUM) (T*)svr_zalloc(sizeof(T) * NUM) 16 | 17 | #define SVR_ALLOCA(T) (T*)_alloca(sizeof(T)) 18 | #define SVR_ALLOCA_NUM(T, NUM) (T*)_alloca(sizeof(T) * NUM) 19 | -------------------------------------------------------------------------------- /src/svr_common/svr_array.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ size={size} capacity={capacity} mem={mem} grow_align={grow_align} }} 5 | 6 | size 7 | capacity 8 | 9 | size 10 | mem 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/svr_common/svr_atom.cpp: -------------------------------------------------------------------------------- 1 | #include "svr_atom.h" 2 | #include 3 | #include 4 | 5 | // Following implementation in MSVC STL xatomic. 6 | 7 | void svr_atom_store(SvrAtom32* atom, s32 value) 8 | { 9 | _ReadWriteBarrier(); 10 | atom->v = value; 11 | } 12 | 13 | s32 svr_atom_load(SvrAtom32* atom) 14 | { 15 | s32 ret = atom->v; 16 | _ReadWriteBarrier(); 17 | return ret; 18 | } 19 | 20 | void svr_atom_and(SvrAtom32* atom, s32 value) 21 | { 22 | InterlockedAnd((LONG*)&atom->v, (LONG)value); 23 | } 24 | 25 | void svr_atom_or(SvrAtom32* atom, s32 value) 26 | { 27 | InterlockedOr((LONG*)&atom->v, (LONG)value); 28 | } 29 | 30 | bool svr_atom_cmpxchg(SvrAtom32* atom, s32* expr, s32 value) 31 | { 32 | s32 old_expr = *expr; 33 | 34 | s32 prev = (s32)InterlockedCompareExchange((LONG*)&atom->v, (LONG)value, (LONG)old_expr); 35 | 36 | if (prev == old_expr) 37 | { 38 | return true; 39 | } 40 | 41 | *expr = prev; 42 | return false; 43 | } 44 | 45 | s32 svr_atom_add(SvrAtom32* atom, s32 num) 46 | { 47 | return (s32)InterlockedExchangeAdd((LONG*)&atom->v, (LONG)num); 48 | } 49 | 50 | s32 svr_atom_sub(SvrAtom32* atom, s32 num) 51 | { 52 | return svr_atom_add(atom, 0 - num); 53 | } 54 | 55 | void svr_atom_store(SvrAtom64* atom, s64 value) 56 | { 57 | _ReadWriteBarrier(); 58 | atom->v = value; 59 | } 60 | 61 | s64 svr_atom_load(SvrAtom64* atom) 62 | { 63 | s64 ret = atom->v; 64 | _ReadWriteBarrier(); 65 | return ret; 66 | } 67 | 68 | void svr_atom_and(SvrAtom64* atom, s64 value) 69 | { 70 | InterlockedAnd64(&atom->v, (LONG64)value); 71 | } 72 | 73 | void svr_atom_or(SvrAtom64* atom, s64 value) 74 | { 75 | InterlockedOr64(&atom->v, (LONG64)value); 76 | } 77 | 78 | bool svr_atom_cmpxchg(SvrAtom64* atom, s64* expr, s64 value) 79 | { 80 | s64 old_expr = *expr; 81 | 82 | s64 prev = (s64)InterlockedCompareExchange64((volatile LONG64*)&atom->v, (LONG64)value, (LONG64)old_expr); 83 | 84 | if (prev == old_expr) 85 | { 86 | return true; 87 | } 88 | 89 | *expr = prev; 90 | return false; 91 | } 92 | 93 | s64 svr_atom_add(SvrAtom64* atom, s64 num) 94 | { 95 | return (s64)InterlockedExchangeAdd64((volatile LONG64*)&atom->v, (LONG64)num); 96 | } 97 | 98 | s64 svr_atom_sub(SvrAtom64* atom, s64 num) 99 | { 100 | return svr_atom_add(atom, 0 - num); 101 | } 102 | 103 | void svr_notify_atom_changed(SvrAtom32* atom) 104 | { 105 | // This creates a full memory barrier. Wake all waiting threads. 106 | WakeByAddressAll(&atom->v); 107 | } 108 | 109 | void svr_notify_atom_changed(SvrAtom64* atom) 110 | { 111 | // This creates a full memory barrier. Wake all waiting threads. 112 | WakeByAddressAll(&atom->v); 113 | } 114 | 115 | void svr_wait_until_atom_is(SvrAtom32* atom, s32 target_value) 116 | { 117 | s32 captured_value = svr_atom_load(atom); 118 | 119 | while (captured_value != target_value) 120 | { 121 | WaitOnAddress(&atom->v, &captured_value, sizeof(target_value), INFINITE); // Awake when value differs. 122 | captured_value = svr_atom_load(atom); 123 | } 124 | } 125 | 126 | void svr_wait_until_atom_is(SvrAtom64* atom, s64 target_value) 127 | { 128 | s64 captured_value = svr_atom_load(atom); 129 | 130 | while (captured_value != target_value) 131 | { 132 | WaitOnAddress(&atom->v, &captured_value, sizeof(target_value), INFINITE); // Awake when value differs. 133 | captured_value = svr_atom_load(atom); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/svr_common/svr_atom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | 4 | // Atomic operations for x86. 5 | 6 | struct SvrAtom32 7 | { 8 | s32 v; 9 | }; 10 | 11 | struct SvrAtom64 12 | { 13 | s64 v; 14 | }; 15 | 16 | // svr_atom_store sets with release order. 17 | // svr_atom_load reads with acquire order. 18 | 19 | void svr_atom_store(SvrAtom32* atom, s32 value); 20 | s32 svr_atom_load(SvrAtom32* atom); 21 | void svr_atom_and(SvrAtom32* atom, s32 value); 22 | void svr_atom_or(SvrAtom32* atom, s32 value); 23 | bool svr_atom_cmpxchg(SvrAtom32* atom, s32* expr, s32 value); 24 | s32 svr_atom_add(SvrAtom32* atom, s32 num); 25 | s32 svr_atom_sub(SvrAtom32* atom, s32 num); 26 | 27 | void svr_atom_store(SvrAtom64* atom, s64 value); 28 | s64 svr_atom_load(SvrAtom64* atom); 29 | void svr_atom_and(SvrAtom64* atom, s64 value); 30 | void svr_atom_or(SvrAtom64* atom, s64 value); 31 | bool svr_atom_cmpxchg(SvrAtom64* atom, s64* expr, s64 value); 32 | s64 svr_atom_add(SvrAtom64* atom, s64 num); 33 | s64 svr_atom_sub(SvrAtom64* atom, s64 num); 34 | 35 | // Functions to wait on atoms. Makes it super easy to synchronize between threads. 36 | 37 | // Call this to wake waiting threads if anyone is waiting on this atom. 38 | void svr_notify_atom_changed(SvrAtom32* atom); 39 | void svr_notify_atom_changed(SvrAtom64* atom); 40 | 41 | // Wait on atom. Writer must use notify function above to wake waiting threads. 42 | void svr_wait_until_atom_is(SvrAtom32* atom, s32 target_value); 43 | void svr_wait_until_atom_is(SvrAtom64* atom, s64 target_value); 44 | -------------------------------------------------------------------------------- /src/svr_common/svr_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "stb_sprintf.h" 6 | 7 | using s8 = int8_t; 8 | using u8 = uint8_t; 9 | using s16 = int16_t; 10 | using u16 = uint16_t; 11 | using s32 = int32_t; 12 | using u32 = uint32_t; 13 | using s64 = int64_t; 14 | using u64 = uint64_t; 15 | using wchar = wchar_t; 16 | 17 | #define SVR_ARRAY_SIZE(A) (s32)(sizeof(A) / sizeof(A[0])) 18 | 19 | #define SVR_FROM_MB(V) (V / 1024LL / 1024LL) 20 | 21 | #define SVR_CAT_PART(A, B) A ## B 22 | #define SVR_CAT(A, B) SVR_CAT_PART(A, B) 23 | 24 | #define SVR_CPU_CACHE_SIZE 64 25 | 26 | // For data separation between threads in the same structure. 27 | #define SVR_THREAD_PADDING() u8 SVR_CAT(thread_padding_, __LINE__)[SVR_CPU_CACHE_SIZE] 28 | #define SVR_STRUCT_PADDING(S) u8 SVR_CAT(struct_padding_, __LINE__)[S] 29 | 30 | #define SVR_STR_CAT1(X) #X 31 | #define SVR_STR_CAT(X) SVR_STR_CAT1(X) 32 | #define SVR_FILE_LOCATION __FILE__ ":" SVR_STR_CAT(__LINE__) 33 | 34 | #define SVR_ALLOCA(T) (T*)_alloca(sizeof(T)) 35 | #define SVR_ALLOCA_NUM(T, NUM) (T*)_alloca(sizeof(T) * NUM) 36 | 37 | // Format to buffer with size restriction. 38 | #define SVR_SNPRINTF(BUF, FORMAT, ...) stbsp_snprintf((BUF), SVR_ARRAY_SIZE((BUF)), FORMAT, __VA_ARGS__) 39 | #define SVR_VSNPRINTF(BUF, FORMAT, VA) stbsp_vsnprintf((BUF), SVR_ARRAY_SIZE((BUF)), FORMAT, VA) 40 | 41 | #define SVR_COPY_STRING(SOURCE, DEST) svr_copy_string((SOURCE), (DEST), SVR_ARRAY_SIZE((DEST))) 42 | 43 | #define SVR_BIT(N) (1 << (N)) 44 | 45 | #ifdef _WIN64 46 | #define SVR_IS_X64() true 47 | #define SVR_IS_X86() false 48 | #else 49 | #define SVR_IS_X64() false 50 | #define SVR_IS_X86() true 51 | #endif 52 | 53 | #ifdef _WIN64 54 | #define SVR_ARCH_STRING "x64" 55 | #else 56 | #define SVR_ARCH_STRING "x86" 57 | #endif 58 | 59 | struct SvrVec2I 60 | { 61 | s32 x; 62 | s32 y; 63 | }; 64 | 65 | struct SvrVec4I 66 | { 67 | s32 x; 68 | s32 y; 69 | s32 z; 70 | s32 w; 71 | }; 72 | 73 | struct SvrVec3 74 | { 75 | float x; 76 | float y; 77 | float z; 78 | }; 79 | 80 | template 81 | inline T svr_max(T a, T b) 82 | { 83 | return a > b ? a : b; 84 | } 85 | 86 | template 87 | inline T svr_min(T a, T b) 88 | { 89 | return a < b ? a : b; 90 | } 91 | 92 | template 93 | inline void svr_clamp(T* v, T min, T max) 94 | { 95 | // This generates better code than using branches. 96 | *v = svr_min(svr_max(*v, min), max); 97 | } 98 | 99 | // Release a COM based object. 100 | void svr_release(struct IUnknown* p); 101 | 102 | // Maybe release a COM based object. 103 | template 104 | inline void svr_maybe_release(T** ptr) 105 | { 106 | if (*ptr) 107 | { 108 | svr_release(*ptr); 109 | } 110 | 111 | *ptr = NULL; 112 | } 113 | 114 | // Maybe release a HANDLE based object. 115 | void svr_maybe_close_handle(void** h); 116 | 117 | void svr_maybe_free(void** addr); 118 | 119 | // Aligns up. 120 | inline s32 svr_align32(s32 value, s32 alignment) 121 | { 122 | return (value + alignment - 1) & ~(alignment - 1); 123 | } 124 | 125 | // Aligns up. 126 | inline s64 svr_align64(s64 value, s64 alignment) 127 | { 128 | return (value + alignment - 1) & ~(alignment - 1); 129 | } 130 | 131 | s32 svr_copy_string(const char* source, char* dest, s32 dest_chars); 132 | 133 | // Temporary buffer formatting. 134 | const char* svr_va(const char* format, ...); 135 | 136 | bool svr_starts_with(const char* str, const char* prefix); 137 | bool svr_ends_with(const char* str, const char* suffix); 138 | 139 | s32 svr_to_utf16(const char* value, s32 value_length, wchar* buf, s32 buf_chars); 140 | 141 | bool svr_is_sorted(s32* idxs, s32 num); 142 | bool svr_are_idxs_unique(s32* idxs, s32 num); 143 | void svr_check_all_mask(bool* mask, s32 num, bool* all_false, bool* all_true); 144 | 145 | using SvrReadFileFlags = u32; 146 | 147 | enum /* SvrReadFileFlags */ 148 | { 149 | SVR_READ_FILE_FLAGS_NEW_LINE = 1 << 0, // End with a new line. 150 | }; 151 | 152 | char* svr_read_file_as_string(const char* path, SvrReadFileFlags flags); 153 | 154 | const char* svr_read_line(const char* start, char* dest, s32 dest_size); 155 | 156 | s32 svr_is_newline(const char* seq); 157 | bool svr_is_whitespace(char c); 158 | const char* svr_advance_until_after_whitespace(const char* text); 159 | const char* svr_advance_until_whitespace(const char* text); 160 | const char* svr_advance_until_char(const char* text, char c); 161 | const char* svr_advance_quote(const char* text); 162 | const char* svr_advance_string(bool quoted, const char* text); 163 | const char* svr_extract_string(const char* text, char* dest, s32 dest_size); 164 | 165 | void svr_unescape_path(const char* buf, char* dest, s32 dest_size); 166 | 167 | bool svr_idx_in_range(s32 idx, s32 size); 168 | 169 | struct SvrSplitTime 170 | { 171 | s32 hours; 172 | s32 minutes; 173 | s32 seconds; 174 | s32 millis; 175 | }; 176 | 177 | // Split input in microseconds to individual components. 178 | SvrSplitTime svr_split_time(s64 us); 179 | 180 | // Scale an integer from one range to another. 181 | // This will round to the nearest result, example: 182 | // svr_rescale(16667, 1000, 1000000) results in 17. 183 | // svr_rescale(16444, 1000, 1000000) results in 16. 184 | s64 svr_rescale(s64 a, s64 b, s64 c); 185 | 186 | bool svr_check_all_true(bool* opts, s32 num); 187 | bool svr_check_one_true(bool* opts, s32 num); 188 | s32 svr_count_num_true(bool* opts, s32 num); 189 | s32 svr_count_set_bits(u32 bits); 190 | 191 | bool svr_does_file_exist(const char* path); 192 | 193 | void svr_trim_right(char* buf, s32 length); 194 | -------------------------------------------------------------------------------- /src/svr_common/svr_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | 4 | const s32 SVR_VERSION = 43; 5 | -------------------------------------------------------------------------------- /src/svr_common/svr_fifo.cpp: -------------------------------------------------------------------------------- 1 | #include "svr_fifo.h" 2 | #include "svr_alloc.h" 3 | #include 4 | #include 5 | 6 | // This is based on https://ffmpeg.org/doxygen/trunk/libavutil_2fifo_8c_source.html by the FFmpeg developers! 7 | 8 | // By default the FIFO can be auto-grown to 1MB. 9 | #define AUTO_GROW_DEFAULT_BYTES (1024 * 1024) 10 | 11 | struct SvrDynFifo 12 | { 13 | u8* buffer; 14 | s32 elem_size; // Size of each item. 15 | s32 nb_elems; // Item capacity of buffer. 16 | s32 offset_r; // Read offset. 17 | s32 offset_w; // Write offset. 18 | s32 is_empty; // To distinguish the case if the read and write offsets are the same. 19 | s32 auto_grow_limit; 20 | }; 21 | 22 | SvrDynFifo* svr_fifo_alloc(s32 nb_elems, s32 elem_size) 23 | { 24 | void* buffer = NULL; 25 | 26 | if (nb_elems) 27 | { 28 | buffer = svr_realloc(NULL, nb_elems * elem_size); 29 | } 30 | 31 | SvrDynFifo* f = SVR_ZALLOC(SvrDynFifo); 32 | f->buffer = (u8*)buffer; 33 | f->nb_elems = nb_elems; 34 | f->elem_size = elem_size; 35 | f->is_empty = 1; 36 | f->auto_grow_limit = svr_max(AUTO_GROW_DEFAULT_BYTES / elem_size, 1); 37 | 38 | return f; 39 | } 40 | 41 | s32 svr_fifo_can_read(SvrDynFifo* f) 42 | { 43 | if (f->offset_w <= f->offset_r && !f->is_empty) 44 | { 45 | return f->nb_elems - f->offset_r + f->offset_w; 46 | } 47 | 48 | return f->offset_w - f->offset_r; 49 | } 50 | 51 | s32 svr_fifo_can_write(SvrDynFifo* f) 52 | { 53 | return f->nb_elems - svr_fifo_can_read(f); 54 | } 55 | 56 | s32 svr_fifo_grow(SvrDynFifo* f, s32 inc) 57 | { 58 | if (inc > INT32_MAX - f->nb_elems) 59 | { 60 | return -1; 61 | } 62 | 63 | u8* tmp = (u8*)svr_realloc(f->buffer, (f->nb_elems + inc) * f->elem_size); 64 | 65 | f->buffer = tmp; 66 | 67 | // Move the data from the beginning of the ring buffer to the newly allocated space. 68 | if (f->offset_w <= f->offset_r && !f->is_empty) 69 | { 70 | s32 copy = svr_min(inc, f->offset_w); 71 | 72 | memcpy(tmp + f->nb_elems * f->elem_size, tmp, copy * f->elem_size); 73 | 74 | if (copy < f->offset_w) 75 | { 76 | memmove(tmp, tmp + copy * f->elem_size, (f->offset_w - copy) * f->elem_size); 77 | f->offset_w -= copy; 78 | } 79 | 80 | else 81 | { 82 | f->offset_w = copy == inc ? 0 : f->nb_elems + copy; 83 | } 84 | } 85 | 86 | f->nb_elems += inc; 87 | 88 | return 0; 89 | } 90 | 91 | s32 svr_fifo_check_space(SvrDynFifo* f, s32 to_write) 92 | { 93 | s32 can_write = svr_fifo_can_write(f); 94 | s32 need_grow = to_write > can_write ? to_write - can_write : 0; 95 | 96 | if (!need_grow) 97 | { 98 | return 0; 99 | } 100 | 101 | s32 can_grow = f->auto_grow_limit > f->nb_elems ? f->auto_grow_limit - f->nb_elems : 0; 102 | 103 | if (need_grow <= can_grow) 104 | { 105 | // Allocate a bit more than necessary, if we can. 106 | s32 inc = (need_grow < can_grow / 2) ? need_grow * 2 : can_grow; 107 | return svr_fifo_grow(f, inc); 108 | } 109 | 110 | return -1; 111 | } 112 | 113 | s32 svr_fifo_write_common(SvrDynFifo* f, u8* buf, s32* nb_elems) 114 | { 115 | s32 to_write = *nb_elems; 116 | s32 offset_w; 117 | s32 ret = 0; 118 | 119 | ret = svr_fifo_check_space(f, to_write); 120 | 121 | if (ret < 0) 122 | { 123 | return ret; 124 | } 125 | 126 | offset_w = f->offset_w; 127 | 128 | while (to_write > 0) 129 | { 130 | s32 len = svr_min(f->nb_elems - offset_w, to_write); 131 | u8* write_ptr = f->buffer + offset_w * f->elem_size; 132 | 133 | memcpy(write_ptr, buf, len * f->elem_size); 134 | buf += len * f->elem_size; 135 | 136 | offset_w += len; 137 | 138 | if (offset_w >= f->nb_elems) 139 | { 140 | offset_w = 0; 141 | } 142 | 143 | to_write -= len; 144 | } 145 | 146 | f->offset_w = offset_w; 147 | 148 | if (*nb_elems != to_write) 149 | { 150 | f->is_empty = 0; 151 | } 152 | 153 | *nb_elems -= to_write; 154 | 155 | return ret; 156 | } 157 | 158 | s32 svr_fifo_write(SvrDynFifo* f, void* buf, s32 nb_elems) 159 | { 160 | return svr_fifo_write_common(f, (u8*)buf, &nb_elems); 161 | } 162 | 163 | s32 svr_fifo_peek_common(SvrDynFifo* f, u8* buf, s32* nb_elems) 164 | { 165 | s32 to_read = *nb_elems; 166 | s32 offset_r = f->offset_r; 167 | s32 can_read = svr_fifo_can_read(f); 168 | s32 ret = 0; 169 | 170 | if (to_read > can_read) 171 | { 172 | *nb_elems = 0; 173 | return -1; 174 | } 175 | 176 | if (offset_r >= f->nb_elems) 177 | { 178 | offset_r -= f->nb_elems; 179 | } 180 | 181 | while (to_read > 0) 182 | { 183 | s32 len = svr_min(f->nb_elems - offset_r, to_read); 184 | u8* read_ptr = f->buffer + offset_r * f->elem_size; 185 | 186 | memcpy(buf, read_ptr, len * f->elem_size); 187 | buf += len * f->elem_size; 188 | 189 | offset_r += len; 190 | 191 | if (offset_r >= f->nb_elems) 192 | { 193 | offset_r = 0; 194 | } 195 | 196 | to_read -= len; 197 | } 198 | 199 | *nb_elems -= to_read; 200 | 201 | return ret; 202 | } 203 | 204 | s32 svr_fifo_read(SvrDynFifo* f, void* buf, s32 nb_elems) 205 | { 206 | s32 ret = svr_fifo_peek_common(f, (u8*)buf, &nb_elems); 207 | svr_fifo_drain(f, nb_elems); 208 | return ret; 209 | } 210 | 211 | void svr_fifo_drain(SvrDynFifo* f, s32 size) 212 | { 213 | s32 cur_size = svr_fifo_can_read(f); 214 | 215 | assert(cur_size >= size); 216 | 217 | if (cur_size == size) 218 | { 219 | f->is_empty = 1; 220 | } 221 | 222 | if (f->offset_r >= f->nb_elems - size) 223 | { 224 | f->offset_r -= f->nb_elems - size; 225 | } 226 | 227 | else 228 | { 229 | f->offset_r += size; 230 | } 231 | } 232 | 233 | void svr_fifo_reset(SvrDynFifo* f) 234 | { 235 | f->offset_r = 0; 236 | f->offset_w = 0; 237 | f->is_empty = 1; 238 | } 239 | 240 | void svr_fifo_free(SvrDynFifo* f) 241 | { 242 | if (f->buffer) 243 | { 244 | svr_free(f->buffer); 245 | f->buffer = NULL; 246 | } 247 | 248 | svr_free(f); 249 | } 250 | -------------------------------------------------------------------------------- /src/svr_common/svr_fifo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | 4 | // Fast dynamic FIFO queue. 5 | // Suitable for byte streams and structures. 6 | // Based on https://ffmpeg.org/doxygen/trunk/libavutil_2fifo_8c_source.html by the FFmpeg developers! 7 | // This cannot be a template because it makes MSVC produce really slow code (25x slower). Could not figure out why. 8 | 9 | struct SvrDynFifo; 10 | 11 | // Allocates a new FIFO. 12 | SvrDynFifo* svr_fifo_alloc(s32 nb_elems, s32 elem_size); 13 | 14 | // Returns how many items you can read right now. 15 | s32 svr_fifo_can_read(SvrDynFifo* f); 16 | 17 | // Pushes items at the back. 18 | s32 svr_fifo_write(SvrDynFifo* f, void* buf, s32 nb_elems); 19 | 20 | // Pops items from the front. 21 | s32 svr_fifo_read(SvrDynFifo* f, void* buf, s32 nb_elems); 22 | 23 | // Pops items from the front without reading. 24 | void svr_fifo_drain(SvrDynFifo* f, s32 size); 25 | 26 | // Clears the FIFO. 27 | void svr_fifo_reset(SvrDynFifo* f); 28 | 29 | // Frees the FIFO. 30 | void svr_fifo_free(SvrDynFifo* f); 31 | -------------------------------------------------------------------------------- /src/svr_common/svr_ini.cpp: -------------------------------------------------------------------------------- 1 | #include "svr_ini.h" 2 | #include "svr_alloc.h" 3 | #include 4 | 5 | using SvrIniLineType = s32; 6 | 7 | enum /* SvrIniLineType */ 8 | { 9 | SVR_INI_LINE_NONE, 10 | SVR_INI_LINE_KV, 11 | }; 12 | 13 | // Fast categorization of a line so we can parse it further. 14 | SvrIniLineType svr_ini_categorize_line(const char* line) 15 | { 16 | const char* ptr = svr_advance_until_after_whitespace(line); 17 | 18 | if (*ptr == 0) 19 | { 20 | return SVR_INI_LINE_NONE; // Blanks are no good. 21 | } 22 | 23 | if (*ptr == '#') 24 | { 25 | return SVR_INI_LINE_NONE; // Comments are no good. 26 | } 27 | 28 | if (svr_is_newline(ptr)) 29 | { 30 | return SVR_INI_LINE_NONE; // Blanks are no good. 31 | } 32 | 33 | return SVR_INI_LINE_KV; 34 | } 35 | 36 | void svr_ini_parse_line(SvrIniSection* priv, const char* line, SvrIniLineType type) 37 | { 38 | const char* ptr = svr_advance_until_after_whitespace(line); // Go past indentation. 39 | 40 | switch (type) 41 | { 42 | // Key values have two values. 43 | case SVR_INI_LINE_KV: 44 | { 45 | SvrIniKeyValue* kv = svr_ini_parse_expression(ptr); 46 | 47 | if (kv) 48 | { 49 | priv->kvs.push(kv); 50 | } 51 | 52 | break; 53 | } 54 | } 55 | } 56 | 57 | SvrIniSection* svr_ini_load(const char* path) 58 | { 59 | char* file_mem = svr_read_file_as_string(path, 0); 60 | 61 | if (file_mem == NULL) 62 | { 63 | return NULL; 64 | } 65 | 66 | SvrIniSection* priv = SVR_ZALLOC(SvrIniSection); 67 | 68 | char line[8192]; 69 | 70 | const char* prev_str = file_mem; 71 | 72 | while (true) 73 | { 74 | const char* next_str = svr_read_line(prev_str, line, SVR_ARRAY_SIZE(line)); 75 | 76 | SvrIniLineType type = svr_ini_categorize_line(line); 77 | 78 | if (type != SVR_INI_LINE_NONE) 79 | { 80 | svr_ini_parse_line(priv, line, type); 81 | } 82 | 83 | prev_str = next_str; 84 | 85 | if (*next_str == 0) 86 | { 87 | break; 88 | } 89 | } 90 | 91 | svr_free(file_mem); 92 | 93 | return priv; 94 | } 95 | 96 | void svr_ini_free(SvrIniSection* priv) 97 | { 98 | svr_ini_free_kvs(&priv->kvs); 99 | svr_free(priv); 100 | } 101 | 102 | void svr_ini_free_kv(SvrIniKeyValue* kv) 103 | { 104 | svr_free(kv->key); 105 | svr_free(kv->value); 106 | svr_free(kv); 107 | } 108 | 109 | void svr_ini_free_kvs(SvrDynArray* kvs) 110 | { 111 | for (s32 i = 0; i < kvs->size; i++) 112 | { 113 | svr_ini_free_kv(kvs->at(i)); 114 | } 115 | 116 | kvs->free(); 117 | } 118 | 119 | SvrIniKeyValue* svr_ini_section_find_kv(SvrIniSection* priv, const char* key) 120 | { 121 | for (s32 i = 0; i < priv->kvs.size; i++) 122 | { 123 | SvrIniKeyValue* kv = priv->kvs[i]; 124 | 125 | if (!strcmpi(kv->key, key)) 126 | { 127 | return kv; 128 | } 129 | } 130 | 131 | return NULL; 132 | } 133 | 134 | SvrIniKeyValue* svr_ini_parse_expression(const char* expr) 135 | { 136 | // At most, one line can have a key and a value. 137 | char key_name[512]; 138 | key_name[0] = 0; 139 | 140 | const char* ptr = svr_advance_until_after_whitespace(expr); 141 | 142 | const char* next_ptr = svr_advance_until_char(ptr, '='); // Read content. 143 | 144 | if (*next_ptr == 0) 145 | { 146 | return NULL; // There only a key. 147 | } 148 | 149 | s32 dist = next_ptr - ptr; // Content length. 150 | 151 | if (dist == 0) 152 | { 153 | return NULL; // There is only an equal sign and nothing else. 154 | } 155 | 156 | StringCchCopyNA(key_name, SVR_ARRAY_SIZE(key_name), ptr, dist); 157 | 158 | ptr = next_ptr; 159 | 160 | if (*ptr != '=') 161 | { 162 | return NULL; // There must not be any space before the equal sign. 163 | } 164 | 165 | ptr++; // Go past equal sign. 166 | 167 | if (*ptr == 0) 168 | { 169 | return NULL; // Value is missing. 170 | } 171 | 172 | next_ptr = svr_advance_until_after_whitespace(ptr); // Go past blanks. 173 | 174 | if (ptr != next_ptr) 175 | { 176 | return NULL; // There must not be a space after the equal sign. 177 | } 178 | 179 | SvrIniKeyValue* kv = SVR_ZALLOC(SvrIniKeyValue); 180 | kv->key = svr_dup_str(key_name); 181 | kv->value = svr_dup_str(ptr); 182 | 183 | return kv; 184 | } 185 | 186 | void svr_ini_parse_command_input(const char* input, SvrDynArray* dest) 187 | { 188 | const char* ptr = svr_advance_until_after_whitespace(input); 189 | 190 | while (*ptr != 0) 191 | { 192 | char expr[1024]; 193 | expr[0] = 0; 194 | 195 | const char* next_ptr = svr_extract_string(ptr, expr, SVR_ARRAY_SIZE(expr)); 196 | 197 | SvrIniKeyValue* kv = svr_ini_parse_expression(expr); 198 | 199 | if (kv) 200 | { 201 | dest->push(kv); 202 | } 203 | 204 | ptr = svr_advance_until_after_whitespace(next_ptr); 205 | } 206 | } 207 | 208 | const char* svr_ini_find_command_value(SvrDynArray* kvs, const char* key) 209 | { 210 | for (s32 i = 0; i < kvs->size; i++) 211 | { 212 | SvrIniKeyValue* kv = kvs->at(i); 213 | 214 | if (!strcmpi(kv->key, key)) 215 | { 216 | return kv->value; 217 | } 218 | } 219 | 220 | return NULL; 221 | } 222 | -------------------------------------------------------------------------------- /src/svr_common/svr_ini.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_array.h" 4 | 5 | // We use ini now instead of json for two reasons: First, json is overly complicated to parse and libraries are overly complicated. Second, users get confused with the formatting rules 6 | // and cases that include escaping a sequence of characters. 7 | 8 | // This only supports a flat structure of keyvalues. Sections are not supported. 9 | 10 | struct SvrIniKeyValue 11 | { 12 | char* key; 13 | char* value; 14 | }; 15 | 16 | struct SvrIniSection 17 | { 18 | SvrDynArray kvs; 19 | }; 20 | 21 | SvrIniSection* svr_ini_load(const char* path); 22 | 23 | void svr_ini_free(SvrIniSection* priv); 24 | void svr_ini_free_kv(SvrIniKeyValue* kv); 25 | void svr_ini_free_kvs(SvrDynArray* kvs); 26 | 27 | // Find a keyvalue inside a section. 28 | // Duplicate keyvalues are allowed, but this will only return the first. 29 | // You can iterate over the kvs array if you need to handle duplicates. 30 | SvrIniKeyValue* svr_ini_section_find_kv(SvrIniSection* priv, const char* key); 31 | 32 | // Parse an INI style expression. 33 | // Returns NULL if the expression could not be parsed. 34 | // Result must be freed with svr_ini_free_kv. 35 | SvrIniKeyValue* svr_ini_parse_expression(const char* expr); 36 | 37 | // Parses an INI style command input. 38 | // This is a series of INI style expressions on a single line. 39 | // Places the results into dest. 40 | // The destination array must be freed with svr_ini_free_kvs. 41 | void svr_ini_parse_command_input(const char* input, SvrDynArray* dest); 42 | 43 | // Find the value of a key. 44 | // Returns NULL if the key is not found. 45 | const char* svr_ini_find_command_value(SvrDynArray* kvs, const char* key); 46 | -------------------------------------------------------------------------------- /src/svr_common/svr_locked_array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_array.h" 4 | #include 5 | 6 | // Lock based dynamic array. 7 | // Safe for several threads to push and pull. 8 | 9 | template 10 | struct SvrLockedArray 11 | { 12 | SvrDynArray items; 13 | SRWLOCK lock; 14 | 15 | inline void init(s32 init_capacity) 16 | { 17 | items.init(init_capacity); 18 | } 19 | 20 | inline void free() 21 | { 22 | items.free(); 23 | } 24 | 25 | // Pushes to the back. 26 | inline void push(T* item) 27 | { 28 | AcquireSRWLockExclusive(&lock); 29 | items.push(*item); 30 | ReleaseSRWLockExclusive(&lock); 31 | } 32 | 33 | // Pops from the back. 34 | inline bool pull(T* item) 35 | { 36 | bool ret = false; 37 | 38 | AcquireSRWLockExclusive(&lock); 39 | 40 | if (items.size == 0) 41 | { 42 | // Nothing to pull. 43 | goto rexit; 44 | } 45 | 46 | *item = items[items.size - 1]; 47 | items.size--; 48 | 49 | ret = true; 50 | 51 | rexit: 52 | ReleaseSRWLockExclusive(&lock); 53 | return ret; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/svr_common/svr_locked_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_queue.h" 4 | #include 5 | 6 | // Lock based queue. 7 | // Safe for several threads to push and pull. 8 | // During profiling during production, this was equally fast as SvrAsyncQueue and SvrAsyncStream. 9 | // This also has no chance of achieving the potential circular queue overflow problem which is difficult to handle without a huge mess. 10 | // Even if you manage to handle the overflow problem, you now have a bottleneck problem instead where the writer is clearly faster than the reader. 11 | // The things we store in these are not extremely large, so we just keep on growing since the order is very important. 12 | 13 | template 14 | struct SvrLockedQueue 15 | { 16 | SvrDynQueue items; 17 | SRWLOCK lock; 18 | 19 | inline void init(s32 init_capacity) 20 | { 21 | items.init(init_capacity); 22 | } 23 | 24 | inline void free() 25 | { 26 | items.free(); 27 | } 28 | 29 | // Pushes to the back. 30 | inline void push(T* item) 31 | { 32 | AcquireSRWLockExclusive(&lock); 33 | items.push(item); 34 | ReleaseSRWLockExclusive(&lock); 35 | } 36 | 37 | // Pops from the front. 38 | inline bool pull(T* item) 39 | { 40 | bool ret = false; 41 | 42 | AcquireSRWLockExclusive(&lock); 43 | 44 | if (items.size() == 0) 45 | { 46 | // Nothing to pull. 47 | goto rexit; 48 | } 49 | 50 | items.pull(item); 51 | 52 | ret = true; 53 | 54 | rexit: 55 | ReleaseSRWLockExclusive(&lock); 56 | return ret; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/svr_common/svr_prof.cpp: -------------------------------------------------------------------------------- 1 | #include "svr_prof.h" 2 | #include 3 | #include 4 | 5 | LARGE_INTEGER prof_timer_freq; 6 | 7 | s64 svr_prof_get_real_time() 8 | { 9 | assert(prof_timer_freq.QuadPart != 0); 10 | 11 | LARGE_INTEGER cur_time; 12 | QueryPerformanceCounter(&cur_time); 13 | 14 | s64 ret = cur_time.QuadPart * 1000000; 15 | ret = ret / prof_timer_freq.QuadPart; 16 | 17 | return ret; 18 | } 19 | 20 | void svr_prof_init() 21 | { 22 | QueryPerformanceFrequency(&prof_timer_freq); 23 | } 24 | 25 | void svr_prof_start(SvrProf* prof) 26 | { 27 | prof->start = svr_prof_get_real_time(); 28 | } 29 | 30 | void svr_prof_end(SvrProf* prof) 31 | { 32 | prof->runs++; 33 | prof->total += (svr_prof_get_real_time() - prof->start); 34 | } 35 | 36 | void svr_prof_reset(SvrProf* prof) 37 | { 38 | prof->runs = 0; 39 | prof->total = 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/svr_common/svr_prof.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | 4 | struct SvrProf 5 | { 6 | s64 start; 7 | s64 runs; 8 | s64 total; 9 | }; 10 | 11 | void svr_prof_init(); 12 | s64 svr_prof_get_real_time(); // Returns microseconds. 13 | 14 | void svr_prof_start(SvrProf* prof); 15 | void svr_prof_end(SvrProf* prof); 16 | void svr_prof_reset(SvrProf* prof); 17 | -------------------------------------------------------------------------------- /src/svr_common/svr_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_fifo.h" 4 | 5 | // Dynamic FIFO queue. 6 | 7 | template 8 | struct SvrDynQueue 9 | { 10 | SvrDynFifo* fifo; 11 | 12 | inline void init(s32 init_capacity) 13 | { 14 | fifo = svr_fifo_alloc(init_capacity, sizeof(T)); 15 | } 16 | 17 | inline void free() 18 | { 19 | if (fifo) 20 | { 21 | svr_fifo_free(fifo); 22 | fifo = NULL; 23 | } 24 | } 25 | 26 | // Push a single item to the back. 27 | inline void push(T* item) 28 | { 29 | push_range(item, 1); 30 | } 31 | 32 | // Pop a single item frm the front. 33 | inline bool pull(T* item) 34 | { 35 | return pull_range(item, 1); 36 | } 37 | 38 | // Push many items to the back. 39 | inline void push_range(T* items, s32 num) 40 | { 41 | svr_fifo_write(fifo, items, num); 42 | } 43 | 44 | // Pull many items from the front. 45 | inline bool pull_range(T* dest, s32 num) 46 | { 47 | if (num > svr_fifo_can_read(fifo)) 48 | { 49 | return false; 50 | } 51 | 52 | svr_fifo_read(fifo, dest, num); 53 | return true; 54 | } 55 | 56 | inline s32 size() 57 | { 58 | return svr_fifo_can_read(fifo); 59 | } 60 | 61 | inline void clear() 62 | { 63 | svr_fifo_reset(fifo); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/svr_common/svr_standalone_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | 4 | // Used by the launcher as parameter for the init exports in svr_standalone.dll. 5 | struct SvrGameInitData 6 | { 7 | const char* svr_path; // Does not end with a slash. 8 | u32 unused; 9 | }; 10 | 11 | using SvrGameInitFuncType = void(__cdecl*)(SvrGameInitData* init_data); 12 | -------------------------------------------------------------------------------- /src/svr_common/svr_vdf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_array.h" 4 | 5 | struct SvrVdfKeyValue 6 | { 7 | char* key; 8 | char* value; 9 | }; 10 | 11 | struct SvrVdfSection 12 | { 13 | char* name; 14 | SvrDynArray kvs; 15 | SvrDynArray sections; 16 | }; 17 | 18 | // Load a VDF formatted file from a path. 19 | SvrVdfSection* svr_vdf_load(const char* path); 20 | 21 | // Call when no longer needed. 22 | void svr_vdf_free(SvrVdfSection* priv); 23 | 24 | // Checks if a section is the root section. 25 | bool svr_vdf_section_is_root(SvrVdfSection* section); 26 | 27 | // Find a nested section. 28 | // A control index can be passed in to start searching from a given index. 29 | // It is allowed to have several sections with the same name, so use the control index 30 | // to iterate over sections with identical names. 31 | SvrVdfSection* svr_vdf_section_find_section(SvrVdfSection* priv, const char* name, s32* control_idx); 32 | 33 | // Find a keyvalue inside a section. 34 | // The root section cannot contain keyvalues. 35 | // Identical keyvalues are not allowed, so there is no control index here. 36 | SvrVdfKeyValue* svr_vdf_section_find_kv(SvrVdfSection* priv, const char* key); 37 | 38 | // Find a keyvalue from a section path. 39 | // The last key should be the name of a keyvalue, while the previous keys should be the names of sections. 40 | SvrVdfKeyValue* svr_vdf_section_find_kv_path(SvrVdfSection* priv, const char** keys, s32 num); 41 | 42 | // Try to find a key and return the value, or return default if it doesn't exist. 43 | const char* svr_vdf_section_find_value_or(SvrVdfSection* priv, const char* key, const char* def); 44 | -------------------------------------------------------------------------------- /src/svr_encoder/encoder_audio.cpp: -------------------------------------------------------------------------------- 1 | #include "encoder_priv.h" 2 | 3 | // Conversion from game audio format to audio encoder format. 4 | 5 | bool EncoderState::audio_init() 6 | { 7 | return true; 8 | } 9 | 10 | void EncoderState::audio_free_static() 11 | { 12 | } 13 | 14 | void EncoderState::audio_free_dynamic() 15 | { 16 | if (audio_output_buffers[0]) 17 | { 18 | av_freep(&audio_output_buffers[0]); 19 | } 20 | 21 | swr_free(&audio_swr); 22 | 23 | if (audio_fifo) 24 | { 25 | av_audio_fifo_free(audio_fifo); 26 | audio_fifo = NULL; 27 | } 28 | } 29 | 30 | bool EncoderState::audio_start() 31 | { 32 | bool ret = false; 33 | 34 | if (!audio_create_resampler()) 35 | { 36 | goto rfail; 37 | } 38 | 39 | if (!audio_create_fifo()) 40 | { 41 | goto rfail; 42 | } 43 | 44 | ret = true; 45 | goto rexit; 46 | 47 | rfail: 48 | 49 | rexit: 50 | return ret; 51 | } 52 | 53 | bool EncoderState::audio_create_resampler() 54 | { 55 | bool ret = false; 56 | s32 res; 57 | 58 | audio_input_hz = movie_params.audio_hz; 59 | audio_output_hz = movie_params.audio_hz; 60 | 61 | // Set from encoder if it requires a set sample rate. 62 | if (render_audio_info->hz != 0) 63 | { 64 | audio_output_hz = render_audio_info->hz; 65 | } 66 | 67 | audio_num_channels = movie_params.audio_channels; 68 | 69 | AVChannelLayout channel_layout; 70 | av_channel_layout_default(&channel_layout, movie_params.audio_channels); 71 | 72 | AVSampleFormat input_format; // Sample format we get from svr_game. 73 | 74 | switch (movie_params.audio_bits) 75 | { 76 | case 16: 77 | { 78 | input_format = AV_SAMPLE_FMT_S16; // Interleaved samples. 79 | break; 80 | } 81 | 82 | default: 83 | { 84 | error("ERROR: Number of bits for audio not supported: %d\n", movie_params.audio_bits); 85 | goto rfail; 86 | } 87 | } 88 | 89 | // If the input is matching the output, we can just copy directly over and we don't need to do any of this. Nice! 90 | 91 | if (input_format == render_audio_info->sample_format) 92 | { 93 | if (audio_input_hz == audio_output_hz) 94 | { 95 | ret = true; 96 | goto rexit; 97 | } 98 | } 99 | 100 | audio_output_size = 0; 101 | 102 | for (s32 i = 0; i < AUDIO_MAX_CHANS; i++) 103 | { 104 | audio_output_buffers[i] = NULL; 105 | } 106 | 107 | res = swr_alloc_set_opts2(&audio_swr, &channel_layout, render_audio_info->sample_format, audio_output_hz, &channel_layout, input_format, audio_input_hz, 0, NULL); 108 | 109 | if (res < 0) 110 | { 111 | error("ERROR: Could not create audio resampler (%d)\n", res); 112 | goto rfail; 113 | } 114 | 115 | res = swr_init(audio_swr); 116 | 117 | if (res < 0) 118 | { 119 | error("ERROR: Could not initialize audio resampler (%d)\n", res); 120 | goto rfail; 121 | } 122 | 123 | ret = true; 124 | goto rexit; 125 | 126 | rfail: 127 | 128 | rexit: 129 | av_channel_layout_uninit(&channel_layout); 130 | return ret; 131 | } 132 | 133 | bool EncoderState::audio_create_fifo() 134 | { 135 | bool ret = false; 136 | 137 | audio_fifo = av_audio_fifo_alloc(render_audio_info->sample_format, audio_num_channels, render_audio_ctx->frame_size * 2); 138 | 139 | if (audio_fifo == NULL) 140 | { 141 | error("ERROR: Could not create audio fifo"); 142 | goto rfail; 143 | } 144 | 145 | ret = true; 146 | goto rexit; 147 | 148 | rfail: 149 | 150 | rexit: 151 | return ret; 152 | } 153 | 154 | // Copies the audio samples into the fifo. 155 | // The samples get converted if needed. 156 | void EncoderState::audio_convert_to_codec_samples(RenderAudioThreadInput* buffer) 157 | { 158 | // Try to avoid extra procesing if we can. 159 | // If we have matching input and output parameters, just copy over and return. 160 | 161 | if (audio_need_conversion()) 162 | { 163 | // The delay are queued samples that are needed when resampling, because the resampling algorithm 164 | // requires future samples for interpolation. This may be 16 samples for example, that will be used to interpolate 165 | // with future samples we give it. Those samples are accounted for here, so they will not be forgotten about. 166 | // This is why you cannot get exact amount of samples back that you give in, as then you would notice the harsh 167 | // change in the curve where the interpolation breaks. If no resampling is needed (if the sample rate matches what we want) 168 | // then there will not be any queued samples. 169 | // We must always fill at least one paint buffer. If we end up getting more samples than anticipated, then those will be stored in the channel. 170 | s64 delay = swr_get_delay(audio_swr, audio_input_hz); 171 | 172 | // Don't use more than necessary here, because we should not be receiving tons of more samples than we need. 173 | s64 estimated = av_rescale_rnd(delay + (int64_t)buffer->num_samples, audio_output_hz, audio_input_hz, AV_ROUND_UP); 174 | 175 | // Grow buffer. 176 | if (estimated > audio_output_size) 177 | { 178 | if (audio_output_buffers[0]) 179 | { 180 | av_freep(&audio_output_buffers[0]); 181 | } 182 | 183 | av_samples_alloc(audio_output_buffers, NULL, audio_num_channels, estimated, render_audio_info->sample_format, 0); 184 | 185 | audio_output_size = estimated; 186 | } 187 | 188 | // For the first call, we may get less samples than written because during resampling, additional samples 189 | // need to be kept for the interpolation. We will call again with the number of samples we are missing to exactly fill out one paint buffer. 190 | s32 num_output_samples = swr_convert(audio_swr, audio_output_buffers, audio_output_size, (const uint8_t**)&buffer->mem, buffer->num_samples); 191 | 192 | // Some encoders have a restriction that they only work with a fixed amount of samples. 193 | // We can get less samples from the game so we have to queue them up and only copy to a frame when we have enough. 194 | av_audio_fifo_write(audio_fifo, (void**)audio_output_buffers, num_output_samples); 195 | } 196 | 197 | // Matching parameters, just copy over. 198 | else 199 | { 200 | // Some encoders have a restriction that they only work with a fixed amount of samples. 201 | // We can get less samples from the game so we have to queue them up and only copy to a frame when we have enough. 202 | av_audio_fifo_write(audio_fifo, (void**)&buffer->mem, buffer->num_samples); 203 | } 204 | } 205 | 206 | void EncoderState::audio_copy_samples_to_frame(AVFrame* dest_frame, s32 num_samples) 207 | { 208 | assert(num_samples <= audio_num_queued_samples()); 209 | av_audio_fifo_read(audio_fifo, (void**)dest_frame->data, num_samples); 210 | } 211 | 212 | s32 EncoderState::audio_num_queued_samples() 213 | { 214 | s32 num_samples = av_audio_fifo_size(audio_fifo); 215 | return num_samples; 216 | } 217 | 218 | bool EncoderState::audio_need_conversion() 219 | { 220 | return audio_swr; 221 | } 222 | -------------------------------------------------------------------------------- /src/svr_encoder/encoder_dnxhr.cpp: -------------------------------------------------------------------------------- 1 | #include "encoder_priv.h" 2 | 3 | // References: 4 | // ffmpeg -h encoder=dnxhd 5 | // https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/libavcodec/dnxhdenc.c 6 | // https://resources.avid.com/SupportFiles/attach/HighRes_WorkflowsGuide.pdf 7 | 8 | void EncoderState::render_setup_dnxhr() 9 | { 10 | // In the profile ini we just write hq, lb or sq, but ffmpeg needs them to be prefixed with dnxhr_. 11 | av_opt_set(render_video_ctx->priv_data, "profile", svr_va("dnxhr_%s", movie_params.dnxhr_profile), 0); 12 | 13 | render_video_ctx->thread_type = FF_THREAD_SLICE; // Crashes without this. 14 | } 15 | -------------------------------------------------------------------------------- /src/svr_encoder/encoder_libx264.cpp: -------------------------------------------------------------------------------- 1 | #include "encoder_priv.h" 2 | 3 | // References: 4 | // ffmpeg -h encoder=libx264 5 | // https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/libavcodec/libx264.c 6 | // https://raw.githubusercontent.com/mirror/x264/master/x264.c 7 | 8 | void EncoderState::render_setup_libx264() 9 | { 10 | av_opt_set(render_video_ctx->priv_data, "preset", movie_params.x264_preset, 0); 11 | av_opt_set(render_video_ctx->priv_data, "crf", svr_va("%d", movie_params.x264_crf), 0); 12 | 13 | if (movie_params.x264_intra) 14 | { 15 | av_opt_set(render_video_ctx->priv_data, "x264-params", "keyint=1", 0); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/svr_encoder/encoder_main.cpp: -------------------------------------------------------------------------------- 1 | #include "encoder_priv.h" 2 | 3 | EncoderState encoder_state; 4 | 5 | void av_log_callback(void* avcl, int level, const char* fmt, va_list vl) 6 | { 7 | // Change this comparison if you need to see more detailed output. 8 | if (level > AV_LOG_WARNING) 9 | { 10 | return; 11 | } 12 | 13 | char buf[4096]; 14 | SVR_VSNPRINTF(buf, fmt, vl); 15 | 16 | const char* format = NULL; 17 | 18 | // Some messages from FFmpeg will not end with a newline. We require that every message ends with a newline. 19 | if (!svr_ends_with(buf, "\n")) 20 | { 21 | format = "ffmpeg: %s\n"; 22 | } 23 | 24 | else 25 | { 26 | format = "ffmpeg: %s"; 27 | } 28 | 29 | svr_log(format, buf); 30 | 31 | #ifdef SVR_DEBUG 32 | if (IsDebuggerPresent()) 33 | { 34 | OutputDebugStringA(svr_va(format, buf)); 35 | } 36 | #endif 37 | } 38 | 39 | int main(int argc, char** argv) 40 | { 41 | #ifdef SVR_DEBUG 42 | _set_error_mode(_OUT_TO_MSGBOX); // Must be called so we can actually use assert because Microsoft messed it up in console builds. 43 | #endif 44 | 45 | svr_init_log("data\\ENCODER_LOG.txt", false); 46 | 47 | if (argc != 2) 48 | { 49 | svr_log("ERROR: Encoder has not been started properly. This program can not be started manually\n"); 50 | return 1; 51 | } 52 | 53 | // For release this should be disabled. 54 | av_log_set_callback(av_log_callback); 55 | av_log_set_level(AV_LOG_WARNING); 56 | 57 | SYSTEMTIME lt; 58 | GetLocalTime(<); 59 | 60 | svr_log("SVR " SVR_ARCH_STRING " version %d (%02d/%02d/%04d %02d:%02d:%02d)\n", SVR_VERSION, lt.wDay, lt.wMonth, lt.wYear, lt.wHour, lt.wMinute, lt.wSecond); 61 | svr_log("For more information see https://github.com/crashfort/SourceDemoRender\n"); 62 | 63 | // We inherit handles when creating this process, so we can just read the handle address directly. 64 | // The encoder is 64-bit and the game is 32-bit, but all handles only have 32 bits significant, so this is safe. 65 | HANDLE shared_mem_h = (HANDLE)(u32)strtoul(argv[1], NULL, 10); 66 | 67 | encoder_state.init(shared_mem_h); 68 | encoder_state.event_loop(); 69 | encoder_state.free_static(); 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /src/svr_encoder/encoder_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "encoder_shared.h" 4 | #include "svr_log.h" 5 | #include "svr_alloc.h" 6 | #include "svr_locked_array.h" 7 | #include "svr_locked_queue.h" 8 | #include "svr_atom.h" 9 | #include "svr_defs.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | extern "C" 18 | { 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | } 28 | 29 | #include "encoder_state.h" 30 | -------------------------------------------------------------------------------- /src/svr_encoder/encoder_render_threads.cpp: -------------------------------------------------------------------------------- 1 | #include "encoder_priv.h" 2 | 3 | DWORD CALLBACK render_frame_thread_proc(LPVOID param) 4 | { 5 | SetThreadDescription(GetCurrentThread(), L"RENDER FRAME THREAD"); 6 | 7 | EncoderState* encoder_ptr = (EncoderState*)param; 8 | encoder_ptr->render_frame_proc(); 9 | 10 | return 0; // Not used. 11 | } 12 | 13 | DWORD CALLBACK render_packet_thread_proc(LPVOID param) 14 | { 15 | SetThreadDescription(GetCurrentThread(), L"RENDER PACKET THREAD"); 16 | 17 | EncoderState* encoder_ptr = (EncoderState*)param; 18 | encoder_ptr->render_packet_proc(); 19 | 20 | return 0; // Not used. 21 | } 22 | 23 | DWORD CALLBACK render_audio_thread_proc(LPVOID param) 24 | { 25 | SetThreadDescription(GetCurrentThread(), L"RENDER AUDIO THREAD"); 26 | 27 | EncoderState* encoder_ptr = (EncoderState*)param; 28 | encoder_ptr->render_audio_proc(); 29 | 30 | return 0; // Not used. 31 | } 32 | 33 | bool EncoderState::render_start_threads() 34 | { 35 | render_frame_thread_h = CreateThread(NULL, 0, render_frame_thread_proc, this, 0, NULL); 36 | render_packet_thread_h = CreateThread(NULL, 0, render_packet_thread_proc, this, 0, NULL); 37 | 38 | if (audio_need_conversion()) 39 | { 40 | render_audio_thread_h = CreateThread(NULL, 0, render_audio_thread_proc, this, 0, NULL); 41 | } 42 | 43 | return true; 44 | } 45 | 46 | // In frame thread. 47 | void EncoderState::render_frame_proc() 48 | { 49 | bool run = true; 50 | 51 | while (run) 52 | { 53 | WaitForSingleObject(render_frame_wake_event_h, INFINITE); 54 | 55 | // Exit thread on external error. 56 | if (svr_atom_load(&render_started) == 0) 57 | { 58 | break; 59 | } 60 | 61 | RenderFrameThreadInput input = {}; 62 | 63 | while (render_frame_queue.pull(&input)) 64 | { 65 | if (input.frame == NULL) 66 | { 67 | run = false; // Stop on flush frame. 68 | } 69 | 70 | s32 res = avcodec_send_frame(input.ctx, input.frame); 71 | 72 | // Recycle frames. 73 | // We don't want to allocate big frames if we don't have to. 74 | // Flush frame must not be reused. 75 | if (input.frame) 76 | { 77 | if (input.type == AVMEDIA_TYPE_VIDEO) 78 | { 79 | render_recycled_video_frames.push(&input.frame); 80 | } 81 | 82 | if (input.type == AVMEDIA_TYPE_AUDIO) 83 | { 84 | render_recycled_audio_frames.push(&input.frame); 85 | } 86 | } 87 | 88 | if (res < 0) 89 | { 90 | SVR_SNPRINTF(render_frame_thread_message, "ERROR: Could not send raw frame to encoder (%d)\n", res); 91 | goto rfail; 92 | } 93 | 94 | while (res == 0) 95 | { 96 | AVPacket* packet = av_packet_alloc(); 97 | 98 | res = avcodec_receive_packet(input.ctx, packet); 99 | 100 | // This will return AVERROR(EAGAIN) when we need to send more data. 101 | // This will return AVERROR_EOF when we are sending a flush frame. 102 | if (res == AVERROR(EAGAIN) || res == AVERROR_EOF) 103 | { 104 | // TODO Should not allocate and then free the packet like this. 105 | av_packet_free(&packet); 106 | break; 107 | } 108 | 109 | if (res < 0) 110 | { 111 | SVR_SNPRINTF(render_frame_thread_message, "ERROR: Could not receive packet from encoder (%d)\n", res); 112 | av_packet_free(&packet); 113 | goto rfail; 114 | } 115 | 116 | if (res == 0) 117 | { 118 | packet->pts = av_rescale_q(packet->pts, input.ctx->time_base, input.stream->time_base); 119 | packet->dts = av_rescale_q(packet->dts, input.ctx->time_base, input.stream->time_base); 120 | packet->duration = av_rescale_q(packet->duration, input.ctx->time_base, input.stream->time_base); 121 | packet->stream_index = input.stream->index; 122 | 123 | // Send to packet thread. 124 | render_packet_queue.push(&packet); 125 | SetEvent(render_packet_wake_event_h); // Notify packet thread. 126 | } 127 | } 128 | } 129 | } 130 | 131 | goto rexit; 132 | 133 | rfail: 134 | svr_atom_store(&render_frame_thread_status, 0); 135 | 136 | rexit: 137 | return; 138 | } 139 | 140 | // In packet thread. 141 | void EncoderState::render_packet_proc() 142 | { 143 | bool run = true; 144 | 145 | while (run) 146 | { 147 | WaitForSingleObject(render_packet_wake_event_h, INFINITE); 148 | 149 | // Exit thread on external error. 150 | if (svr_atom_load(&render_started) == 0) 151 | { 152 | break; 153 | } 154 | 155 | AVPacket* packet = NULL; 156 | 157 | while (render_packet_queue.pull(&packet)) 158 | { 159 | if (packet == NULL) 160 | { 161 | run = false; // Stop on flush packet. 162 | } 163 | 164 | s32 res = av_interleaved_write_frame(render_output_context, packet); 165 | 166 | av_packet_free(&packet); 167 | 168 | if (res < 0) 169 | { 170 | SVR_SNPRINTF(render_packet_thread_message, "ERROR: Could not write encoded packet to container (%d)\n", res); 171 | goto rfail; 172 | } 173 | } 174 | } 175 | 176 | goto rexit; 177 | 178 | rfail: 179 | svr_atom_store(&render_packet_thread_status, 0); 180 | 181 | rexit: 182 | return; 183 | } 184 | 185 | // In audio thread. 186 | void EncoderState::render_audio_proc() 187 | { 188 | bool run = true; 189 | 190 | while (run) 191 | { 192 | WaitForSingleObject(render_audio_wake_event_h, INFINITE); 193 | 194 | // Exit thread on external error. 195 | if (svr_atom_load(&render_started) == 0) 196 | { 197 | break; 198 | } 199 | 200 | RenderAudioThreadInput buffer = {}; 201 | 202 | while (render_audio_queue.pull(&buffer)) 203 | { 204 | if (buffer.mem == NULL) 205 | { 206 | run = false; // Stop on flush buffer. 207 | break; 208 | } 209 | 210 | render_give_audio_thread_input(&buffer); 211 | 212 | render_recycled_audio_buffers.push(&buffer); // Give back the audio buffer. 213 | } 214 | } 215 | 216 | goto rexit; 217 | 218 | rfail: 219 | svr_atom_store(&render_audio_thread_status, 0); 220 | 221 | rexit: 222 | return; 223 | } 224 | -------------------------------------------------------------------------------- /src/svr_encoder/encoder_state.cpp: -------------------------------------------------------------------------------- 1 | #include "encoder_priv.h" 2 | 3 | bool EncoderState::init(HANDLE in_shared_mem_h) 4 | { 5 | bool ret = false; 6 | 7 | main_thread_id = GetCurrentThreadId(); 8 | 9 | shared_mem_h = in_shared_mem_h; 10 | 11 | // At this point, the shared memory will already have some data already filled in. 12 | shared_mem_ptr = (EncoderSharedMem*)MapViewOfFile(shared_mem_h, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); 13 | 14 | if (shared_mem_ptr == NULL) 15 | { 16 | error("ERROR: Could not view encoder shared memory (%lu)\n", GetLastError()); 17 | goto rfail; 18 | } 19 | 20 | game_wake_event_h = (HANDLE)shared_mem_ptr->game_wake_event_h; 21 | encoder_wake_event_h = (HANDLE)shared_mem_ptr->encoder_wake_event_h; 22 | shared_audio_buffer = (u8*)shared_mem_ptr + shared_mem_ptr->audio_buffer_offset; 23 | 24 | game_process = OpenProcess(SYNCHRONIZE, FALSE, shared_mem_ptr->game_pid); 25 | 26 | if (game_process == NULL) 27 | { 28 | error("ERROR: Could not open game process (%lu)\n", GetLastError()); 29 | goto rfail; 30 | } 31 | 32 | if (!vid_init()) 33 | { 34 | goto rfail; 35 | } 36 | 37 | if (!audio_init()) 38 | { 39 | goto rfail; 40 | } 41 | 42 | if (!render_init()) 43 | { 44 | goto rfail; 45 | } 46 | 47 | ret = true; 48 | goto rexit; 49 | 50 | rfail: 51 | free_static(); 52 | 53 | rexit: 54 | return ret; 55 | } 56 | 57 | void EncoderState::start_event() 58 | { 59 | svr_log("Starting encoder\n"); 60 | 61 | // The movie parameters in the shared memory won't change after this point, but we 62 | // want to have our own copy either way. 63 | movie_params = shared_mem_ptr->movie_params; 64 | 65 | if (!render_start()) 66 | { 67 | goto rfail; 68 | } 69 | 70 | if (!vid_start()) 71 | { 72 | goto rfail; 73 | } 74 | 75 | if (movie_params.use_audio) 76 | { 77 | if (!audio_start()) 78 | { 79 | goto rfail; 80 | } 81 | } 82 | 83 | if (render_video_info) 84 | { 85 | svr_log("Using video encoder %s\n", render_video_info->profile_name); 86 | } 87 | 88 | if (render_audio_info) 89 | { 90 | svr_log("Using audio encoder %s\n", render_audio_info->profile_name); 91 | } 92 | 93 | goto rexit; 94 | 95 | rfail: 96 | free_dynamic(); 97 | 98 | rexit: 99 | return; 100 | } 101 | 102 | void EncoderState::stop_event() 103 | { 104 | svr_log("Ending encoder\n"); 105 | 106 | free_dynamic(); 107 | } 108 | 109 | void EncoderState::new_video_frame_event() 110 | { 111 | if (!render_receive_video()) 112 | { 113 | free_dynamic(); 114 | } 115 | } 116 | 117 | void EncoderState::new_audio_samples_event() 118 | { 119 | if (!render_receive_audio()) 120 | { 121 | free_dynamic(); 122 | } 123 | } 124 | 125 | // Event reading from svr_game. 126 | void EncoderState::event_loop() 127 | { 128 | svr_log("Encoder ready\n"); 129 | 130 | HANDLE handles[] = 131 | { 132 | game_process, 133 | encoder_wake_event_h, 134 | }; 135 | 136 | while (true) 137 | { 138 | DWORD waited = WaitForMultipleObjects(SVR_ARRAY_SIZE(handles), handles, FALSE, INFINITE); 139 | HANDLE waited_h = handles[waited - WAIT_OBJECT_0]; 140 | 141 | // Game exited or crashed or something. 142 | // If we were recording, we did not get the stop command, so just stop as if we got it. 143 | // This will exit this process too. 144 | if (waited_h == game_process) 145 | { 146 | if (svr_atom_load(&render_started)) 147 | { 148 | svr_log("Game exited without telling the encoder, ending movie\n"); 149 | 150 | stop_event(); 151 | } 152 | 153 | break; 154 | } 155 | 156 | // We are woken up here because svr_game wants us to do something. 157 | // Any code in here needs to be fast because the game is frozen at this point. 158 | // Forward relevant stuff to the actual encoder thread. 159 | if (waited_h == encoder_wake_event_h) 160 | { 161 | // Clear out any error from previous calls. 162 | shared_mem_ptr->error = 0; 163 | shared_mem_ptr->error_message[0] = 0; 164 | 165 | switch (shared_mem_ptr->event_type) 166 | { 167 | case ENCODER_EVENT_START: 168 | { 169 | start_event(); 170 | break; 171 | } 172 | 173 | case ENCODER_EVENT_STOP: 174 | { 175 | stop_event(); 176 | break; 177 | } 178 | 179 | case ENCODER_EVENT_NEW_VIDEO: 180 | { 181 | new_video_frame_event(); 182 | break; 183 | } 184 | 185 | case ENCODER_EVENT_NEW_AUDIO: 186 | { 187 | new_audio_samples_event(); 188 | break; 189 | } 190 | } 191 | 192 | // Notify svr_game that we handled this event. 193 | // We go back to sleep after this, which puts us in a known paused state. 194 | SetEvent(game_wake_event_h); 195 | } 196 | } 197 | 198 | svr_log("Encoder finished\n"); 199 | } 200 | 201 | void EncoderState::free_static() 202 | { 203 | svr_maybe_close_handle(&game_process); 204 | svr_maybe_close_handle(&shared_mem_h); 205 | 206 | if (shared_mem_ptr) 207 | { 208 | UnmapViewOfFile(shared_mem_ptr); 209 | shared_mem_ptr = NULL; 210 | shared_audio_buffer = NULL; 211 | } 212 | 213 | svr_maybe_close_handle(&game_wake_event_h); 214 | svr_maybe_close_handle(&encoder_wake_event_h); 215 | 216 | render_free_static(); 217 | vid_free_static(); 218 | audio_free_static(); 219 | } 220 | 221 | void EncoderState::free_dynamic() 222 | { 223 | render_free_dynamic(); 224 | vid_free_dynamic(); 225 | audio_free_dynamic(); 226 | } 227 | 228 | void EncoderState::error(const char* format, ...) 229 | { 230 | // Must only be called by the main thread because the shared memory can only be written by the main thread. 231 | assert(GetCurrentThreadId() == main_thread_id); 232 | 233 | // Set this early so we don't try to flush the encoders or something. 234 | // If we have an error then we must stop right now, and not try to process any more data. 235 | svr_atom_store(&render_started, 0); 236 | 237 | va_list va; 238 | va_start(va, format); 239 | SVR_VSNPRINTF(shared_mem_ptr->error_message, format, va); 240 | va_end(va); 241 | 242 | // Set error which svr_game will read after we return. 243 | // Error message has been written to the shared memory through the error function. 244 | shared_mem_ptr->error = 1; 245 | } 246 | -------------------------------------------------------------------------------- /src/svr_encoder/unity_encoder.cpp: -------------------------------------------------------------------------------- 1 | #include "encoder_priv.h" 2 | #include "encoder_main.cpp" 3 | #include "encoder_state.cpp" 4 | #include "encoder_audio.cpp" 5 | #include "encoder_video.cpp" 6 | #include "encoder_render.cpp" 7 | #include "encoder_dnxhr.cpp" 8 | #include "encoder_libx264.cpp" 9 | #include "encoder_render_threads.cpp" 10 | -------------------------------------------------------------------------------- /src/svr_game/proc_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_defs.h" 4 | #include "svr_log.h" 5 | #include "svr_console.h" 6 | #include "svr_queue.h" 7 | #include "encoder_shared.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "svr_prof.h" 18 | #include 19 | #include "svr_api.h" 20 | #include "svr_ini.h" 21 | #include "svr_alloc.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "proc_state.h" 30 | #include "proc_profile_opts.h" 31 | -------------------------------------------------------------------------------- /src/svr_game/proc_profile.cpp: -------------------------------------------------------------------------------- 1 | #include "proc_priv.h" 2 | 3 | // Profile loading. 4 | 5 | // Names for ini. 6 | OptStrIntMapping VELO_FONT_WEIGHT_TABLE[] = 7 | { 8 | OptStrIntMapping { "thin", DWRITE_FONT_WEIGHT_THIN }, 9 | OptStrIntMapping { "extralight", DWRITE_FONT_WEIGHT_EXTRA_LIGHT }, 10 | OptStrIntMapping { "light", DWRITE_FONT_WEIGHT_LIGHT }, 11 | OptStrIntMapping { "semilight", DWRITE_FONT_WEIGHT_SEMI_LIGHT }, 12 | OptStrIntMapping { "normal", DWRITE_FONT_WEIGHT_NORMAL }, 13 | OptStrIntMapping { "medium", DWRITE_FONT_WEIGHT_MEDIUM }, 14 | OptStrIntMapping { "semibold", DWRITE_FONT_WEIGHT_SEMI_BOLD }, 15 | OptStrIntMapping { "bold", DWRITE_FONT_WEIGHT_BOLD }, 16 | OptStrIntMapping { "extrabold", DWRITE_FONT_WEIGHT_EXTRA_BOLD }, 17 | OptStrIntMapping { "black", DWRITE_FONT_WEIGHT_BLACK }, 18 | OptStrIntMapping { "extrablack", DWRITE_FONT_WEIGHT_EXTRA_BLACK }, 19 | }; 20 | 21 | // Names for ini. 22 | OptStrIntMapping VELO_FONT_STYLE_TABLE[] = 23 | { 24 | OptStrIntMapping { "normal", DWRITE_FONT_STYLE_NORMAL }, 25 | OptStrIntMapping { "italic", DWRITE_FONT_STYLE_ITALIC }, 26 | OptStrIntMapping { "extraitalic", DWRITE_FONT_STYLE_OBLIQUE }, 27 | }; 28 | 29 | // Names for ini. 30 | OptStrIntMapping VELO_ANCHOR_TABLE[] = 31 | { 32 | OptStrIntMapping { "left", VELO_ANCHOR_LEFT }, 33 | OptStrIntMapping { "center", VELO_ANCHOR_CENTER }, 34 | OptStrIntMapping { "right", VELO_ANCHOR_RIGHT }, 35 | }; 36 | 37 | // Names for ini. 38 | OptStrIntMapping VELO_LENGTH_TABLE[] = 39 | { 40 | OptStrIntMapping { "xy", VELO_LENGTH_XY }, 41 | OptStrIntMapping { "xyz", VELO_LENGTH_XYZ }, 42 | OptStrIntMapping { "z", VELO_LENGTH_Z }, 43 | }; 44 | 45 | // Names for ini. 46 | // Should be synchronized with encoder_render.cpp. 47 | const char* VIDEO_ENCODER_TABLE[] = 48 | { 49 | "libx264", 50 | "libx264_444", 51 | "dnxhr", 52 | }; 53 | 54 | // Names for ini. 55 | // Should be synchronized with encoder_render.cpp. 56 | const char* AUDIO_ENCODER_TABLE[] = 57 | { 58 | "aac", 59 | }; 60 | 61 | // Names for ini and ffmpeg. 62 | const char* X264_PRESET_TABLE[] = 63 | { 64 | "ultrafast", 65 | "superfast", 66 | "veryfast", 67 | "faster", 68 | "fast", 69 | "medium", 70 | "slow", 71 | "slower", 72 | "veryslow", 73 | "placebo", 74 | }; 75 | 76 | // Names for ini. 77 | // Should be synchronized with encoder_render.cpp. 78 | const char* DNXHR_PROFILE_TABLE[] = 79 | { 80 | "lb", 81 | "sq", 82 | "hq", 83 | }; 84 | 85 | bool ProcState::movie_init() 86 | { 87 | return true; 88 | } 89 | 90 | void ProcState::movie_free_static() 91 | { 92 | } 93 | 94 | void ProcState::movie_free_dynamic() 95 | { 96 | } 97 | 98 | bool ProcState::movie_start() 99 | { 100 | return true; 101 | } 102 | 103 | void ProcState::movie_end() 104 | { 105 | } 106 | 107 | void ProcState::movie_setup_params() 108 | { 109 | D3D11_TEXTURE2D_DESC tex_desc; 110 | svr_game_texture.tex->GetDesc(&tex_desc); 111 | 112 | movie_width = tex_desc.Width; 113 | movie_height = tex_desc.Height; 114 | } 115 | 116 | // A required profile must have all variables set to a proper value. This is used with the default profile. 117 | bool ProcState::movie_load_profile(const char* name, bool required) 118 | { 119 | char full_profile_path[MAX_PATH]; 120 | SVR_SNPRINTF(full_profile_path, "%s\\data\\profiles\\%s.ini", svr_resource_path, name); 121 | 122 | bool ret = false; 123 | 124 | SvrIniSection* ini_root = svr_ini_load(full_profile_path); 125 | 126 | if (ini_root == NULL) 127 | { 128 | svr_console_msg_and_log("ERROR: Could not load profile %s\n", full_profile_path); 129 | goto rfail; 130 | } 131 | 132 | ret = true; 133 | 134 | ret &= OPT_S32(ini_root, "video_fps", 1, 1000, &movie_profile.video_fps); 135 | ret &= OPT_STR_LIST(ini_root, "video_encoder", VIDEO_ENCODER_TABLE, &movie_profile.video_encoder); 136 | ret &= OPT_S32(ini_root, "video_x264_crf", 0, 52, &movie_profile.video_x264_crf); 137 | ret &= OPT_STR_LIST(ini_root, "video_x264_preset", X264_PRESET_TABLE, &movie_profile.video_x264_preset); 138 | ret &= OPT_BOOL(ini_root, "video_x264_intra", &movie_profile.video_x264_intra); 139 | ret &= OPT_STR_LIST(ini_root, "video_dnxhr_profile", DNXHR_PROFILE_TABLE, &movie_profile.video_dnxhr_profile); 140 | ret &= OPT_BOOL(ini_root, "audio_enabled", &movie_profile.audio_enabled); 141 | ret &= OPT_STR_LIST(ini_root, "audio_encoder", AUDIO_ENCODER_TABLE, &movie_profile.audio_encoder); 142 | 143 | ret &= OPT_BOOL(ini_root, "motion_blur_enabled", &movie_profile.mosample_enabled); 144 | ret &= OPT_S32(ini_root, "motion_blur_fps_mult", 2, INT32_MAX, &movie_profile.mosample_mult); 145 | ret &= OPT_FLOAT(ini_root, "motion_blur_exposure", 0.0f, 1.0f, &movie_profile.mosample_exposure); 146 | 147 | ret &= OPT_BOOL(ini_root, "velo_enabled", &movie_profile.velo_enabled); 148 | ret &= OPT_STR(ini_root, "velo_font", &movie_profile.velo_font); 149 | ret &= OPT_S32(ini_root, "velo_font_size", 16, 192, &movie_profile.velo_font_size); 150 | ret &= OPT_COLOR(ini_root, "velo_color", &movie_profile.velo_font_color); 151 | ret &= OPT_COLOR(ini_root, "velo_border_color", &movie_profile.velo_font_border_color); 152 | ret &= OPT_S32(ini_root, "velo_border_size", 0, 192, &movie_profile.velo_font_border_size); 153 | ret &= OPT_STR_MAP(ini_root, "velo_font_style", VELO_FONT_STYLE_TABLE, (s32*)&movie_profile.velo_font_style); 154 | ret &= OPT_STR_MAP(ini_root, "velo_font_weight", VELO_FONT_WEIGHT_TABLE, (s32*)&movie_profile.velo_font_weight); 155 | ret &= OPT_VEC2(ini_root, "velo_align", &movie_profile.velo_align); 156 | ret &= OPT_STR_MAP(ini_root, "velo_anchor", VELO_ANCHOR_TABLE, &movie_profile.velo_anchor); 157 | ret &= OPT_STR_MAP(ini_root, "velo_length", VELO_LENGTH_TABLE, &movie_profile.velo_length); 158 | 159 | if (!required) 160 | { 161 | ret = true; 162 | } 163 | 164 | goto rexit; 165 | 166 | rfail: 167 | 168 | rexit: 169 | if (ini_root) 170 | { 171 | svr_ini_free(ini_root); 172 | } 173 | 174 | return ret; 175 | } 176 | -------------------------------------------------------------------------------- /src/svr_game/proc_profile_opts.cpp: -------------------------------------------------------------------------------- 1 | #include "proc_priv.h" 2 | 3 | bool opt_atoi_in_range(SvrIniKeyValue* kv, s32 min, s32 max, s32* dest) 4 | { 5 | if (kv == NULL) 6 | { 7 | return false; 8 | } 9 | 10 | s32 v = atoi(kv->value); 11 | 12 | if (v < min || v > max) 13 | { 14 | s32 new_v = v; 15 | svr_clamp(&new_v, min, max); 16 | 17 | svr_console_msg_and_log("Option %s out of range (min is %d, max is %d, value is %d) setting to %d\n", kv->key, min, max, v, new_v); 18 | 19 | v = new_v; 20 | } 21 | 22 | *dest = v; 23 | return true; 24 | } 25 | 26 | bool opt_atof_in_range(SvrIniKeyValue* kv, float min, float max, float* dest) 27 | { 28 | if (kv == NULL) 29 | { 30 | return false; 31 | } 32 | 33 | float v = atof(kv->value); 34 | 35 | if (v < min || v > max) 36 | { 37 | float new_v = v; 38 | svr_clamp(&new_v, min, max); 39 | 40 | svr_console_msg_and_log("Option %s out of range (min is %0.2f, max is %0.2f, value is %0.2f) setting to %0.2f\n", kv->key, min, max, v, new_v); 41 | 42 | v = new_v; 43 | } 44 | 45 | *dest = v; 46 | return true; 47 | } 48 | 49 | bool opt_str_or(SvrIniKeyValue* kv, char** dest) 50 | { 51 | if (kv == NULL) 52 | { 53 | return false; 54 | } 55 | 56 | if (*dest) 57 | { 58 | svr_free(*dest); 59 | *dest = NULL; 60 | } 61 | 62 | *dest = svr_dup_str(kv->value); 63 | return true; 64 | } 65 | 66 | bool opt_str_in_list_or(SvrIniKeyValue* kv, const char** list, s32 num, const char** dest) 67 | { 68 | if (kv == NULL) 69 | { 70 | return false; 71 | } 72 | 73 | for (s32 i = 0; i < num; i++) 74 | { 75 | if (!strcmp(list[i], kv->value)) 76 | { 77 | *dest = list[i]; 78 | return true; 79 | } 80 | } 81 | 82 | char opts[1024]; 83 | opts[0] = 0; 84 | 85 | for (s32 i = 0; i < num; i++) 86 | { 87 | StringCchCatA(opts, SVR_ARRAY_SIZE(opts), list[i]); 88 | 89 | if (i != num - 1) 90 | { 91 | StringCchCatA(opts, SVR_ARRAY_SIZE(opts), ", "); 92 | } 93 | } 94 | 95 | svr_console_msg_and_log("Option %s has incorrect value (value is %s, options are %s)\n", kv->key, kv->value, opts); 96 | 97 | return false; 98 | } 99 | 100 | bool opt_map_str_in_list_or(SvrIniKeyValue* kv, OptStrIntMapping* mappings, s32 num, s32* dest) 101 | { 102 | if (kv == NULL) 103 | { 104 | return false; 105 | } 106 | 107 | for (s32 i = 0; i < num; i++) 108 | { 109 | OptStrIntMapping* m = &mappings[i]; 110 | 111 | if (!strcmp(m->name, kv->value)) 112 | { 113 | *dest = m->value; 114 | return true; 115 | } 116 | } 117 | 118 | char opts[1024]; 119 | opts[0] = 0; 120 | 121 | for (s32 i = 0; i < num; i++) 122 | { 123 | OptStrIntMapping* m = &mappings[i]; 124 | StringCchCatA(opts, SVR_ARRAY_SIZE(opts), m->name); 125 | 126 | if (i != num - 1) 127 | { 128 | StringCchCatA(opts, SVR_ARRAY_SIZE(opts), ", "); 129 | } 130 | } 131 | 132 | svr_console_msg_and_log("Option %s has incorrect value (value is %s, options are %s)\n", kv->key, kv->value, opts); 133 | 134 | return false; 135 | } 136 | 137 | bool opt_make_vec2_or(SvrIniKeyValue* kv, SvrVec2I* dest) 138 | { 139 | SvrVec2I ret; 140 | 141 | if (kv == NULL) 142 | { 143 | return false; 144 | } 145 | 146 | s32 num = sscanf(kv->value, "%d %d", &ret.x, &ret.y); 147 | 148 | if (num != 2) 149 | { 150 | ret = SvrVec2I { 0, 0 }; 151 | svr_console_msg_and_log("Option %s has incorrect formatting. It should be in the format of . Setting to 0 0\n", kv->key); 152 | } 153 | 154 | *dest = ret; 155 | return true; 156 | } 157 | 158 | bool opt_make_color_or(SvrIniKeyValue* kv, SvrVec4I* dest) 159 | { 160 | SvrVec4I ret; 161 | 162 | if (kv == NULL) 163 | { 164 | return false; 165 | } 166 | 167 | s32 num = sscanf(kv->value, "%d %d %d %d", &ret.x, &ret.y, &ret.z, &ret.w); 168 | 169 | svr_clamp(&ret.x, 0, 255); 170 | svr_clamp(&ret.y, 0, 255); 171 | svr_clamp(&ret.z, 0, 255); 172 | svr_clamp(&ret.w, 0, 255); 173 | 174 | if (num != 4) 175 | { 176 | ret = SvrVec4I { 255, 255, 255, 255 }; 177 | svr_console_msg_and_log("Option %s has incorrect formatting. It should be a color in the format of 255 255 255 255 (RGBA). Setting to 255 255 255 255\n", kv->key); 178 | } 179 | 180 | *dest = ret; 181 | return true; 182 | } 183 | -------------------------------------------------------------------------------- /src/svr_game/proc_profile_opts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Stuff to make it easier to read from the profile. 4 | 5 | struct OptStrIntMapping 6 | { 7 | const char* name; 8 | s32 value; 9 | }; 10 | 11 | bool opt_atoi_in_range(SvrIniKeyValue* kv, s32 min, s32 max, s32* dest); 12 | bool opt_atof_in_range(SvrIniKeyValue* kv, float min, float max, float* dest); 13 | bool opt_str_or(SvrIniKeyValue* kv, char** dest); 14 | bool opt_str_in_list_or(SvrIniKeyValue* kv, const char** list, s32 num, const char** dest); 15 | bool opt_map_str_in_list_or(SvrIniKeyValue* kv, OptStrIntMapping* mappings, s32 num, s32* dest); 16 | bool opt_make_vec2_or(SvrIniKeyValue* kv, SvrVec2I* dest); 17 | bool opt_make_color_or(SvrIniKeyValue* kv, SvrVec4I* dest); 18 | 19 | #define OPT_S32(INI, NAME, MIN, MAX, DEST) opt_atoi_in_range(svr_ini_section_find_kv(INI, NAME), MIN, MAX, DEST) 20 | #define OPT_FLOAT(INI, NAME, MIN, MAX, DEST) opt_atof_in_range(svr_ini_section_find_kv(INI, NAME), MIN, MAX, DEST) 21 | #define OPT_BOOL(INI, NAME, DEST) opt_atoi_in_range(svr_ini_section_find_kv(INI, NAME), 0, 1, DEST) 22 | #define OPT_STR(INI, NAME, DEST) opt_str_or(svr_ini_section_find_kv(INI, NAME), DEST) 23 | #define OPT_COLOR(INI, NAME, DEST) opt_make_color_or(svr_ini_section_find_kv(INI, NAME), DEST) 24 | #define OPT_VEC2(INI, NAME, DEST) opt_make_vec2_or(svr_ini_section_find_kv(INI, NAME), DEST) 25 | #define OPT_STR_LIST(INI, NAME, LIST, DEST) opt_str_in_list_or(svr_ini_section_find_kv(INI, NAME), LIST, SVR_ARRAY_SIZE(LIST), DEST) 26 | #define OPT_STR_MAP(INI, NAME, MAP, DEST) opt_map_str_in_list_or(svr_ini_section_find_kv(INI, NAME), MAP, SVR_ARRAY_SIZE(MAP), DEST) 27 | -------------------------------------------------------------------------------- /src/svr_game/proc_state.cpp: -------------------------------------------------------------------------------- 1 | #include "proc_priv.h" 2 | 3 | bool ProcState::init(const char* in_resource_path, ID3D11Device* in_d3d11_device) 4 | { 5 | bool ret = false; 6 | 7 | SVR_COPY_STRING(in_resource_path, svr_resource_path); 8 | 9 | if (!vid_init(in_d3d11_device)) 10 | { 11 | goto rfail; 12 | } 13 | 14 | if (!velo_init()) 15 | { 16 | goto rfail; 17 | } 18 | 19 | if (!mosample_init()) 20 | { 21 | goto rfail; 22 | } 23 | 24 | if (!encoder_init()) 25 | { 26 | goto rfail; 27 | } 28 | 29 | ret = true; 30 | goto rexit; 31 | 32 | rfail: 33 | free_static(); 34 | 35 | rexit: 36 | 37 | return ret; 38 | } 39 | 40 | void ProcState::new_video_frame() 41 | { 42 | // If we are using mosample, we will have to accumulate enough frames before we can start sending. 43 | // Mosample will internally send the frames when they are ready. 44 | if (movie_profile.mosample_enabled) 45 | { 46 | mosample_new_video_frame(); 47 | } 48 | 49 | // No mosample, just send the frame over directly. 50 | else 51 | { 52 | vid_d3d11_context->CopyResource(encoder_share_tex, svr_game_texture.tex); 53 | process_finished_shared_tex(); 54 | } 55 | } 56 | 57 | void ProcState::new_audio_samples(SvrWaveSample* samples, s32 num_samples) 58 | { 59 | encoder_send_audio_samples(samples, num_samples); 60 | } 61 | 62 | bool ProcState::is_velo_enabled() 63 | { 64 | return movie_profile.velo_enabled; 65 | } 66 | 67 | bool ProcState::is_audio_enabled() 68 | { 69 | return movie_profile.audio_enabled; 70 | } 71 | 72 | // Call this when you have written everything you need to encoder_share_tex. 73 | void ProcState::process_finished_shared_tex() 74 | { 75 | // Now is the time to draw the velo if we have it. 76 | if (movie_profile.velo_enabled) 77 | { 78 | velo_draw(); 79 | } 80 | 81 | encoder_send_shared_tex(); 82 | } 83 | 84 | bool ProcState::start(const char* dest_file, const char* profile, ProcGameTexture* game_texture, SvrAudioParams* audio_params) 85 | { 86 | bool ret = false; 87 | 88 | svr_game_texture = *game_texture; 89 | svr_audio_params = *audio_params; 90 | 91 | // Build output video path. 92 | 93 | SVR_SNPRINTF(movie_path, "%s\\movies\\", svr_resource_path); 94 | CreateDirectoryA(movie_path, NULL); 95 | SVR_SNPRINTF(movie_path, "%s\\movies\\%s", svr_resource_path, dest_file); 96 | 97 | movie_setup_params(); 98 | 99 | // Must load the profiles first! 100 | // The default profile is the base profile, and other profiles can override individual options. 101 | 102 | if (!movie_load_profile("default", true)) 103 | { 104 | goto rfail; 105 | } 106 | 107 | if (profile) 108 | { 109 | if (profile[0]) 110 | { 111 | if (!movie_load_profile(profile, false)) 112 | { 113 | goto rfail; 114 | } 115 | } 116 | } 117 | 118 | if (!vid_start()) 119 | { 120 | goto rfail; 121 | } 122 | 123 | if (!mosample_start()) 124 | { 125 | goto rfail; 126 | } 127 | 128 | if (!velo_start()) 129 | { 130 | goto rfail; 131 | } 132 | 133 | if (!encoder_start()) 134 | { 135 | goto rfail; 136 | } 137 | 138 | ret = true; 139 | goto rexit; 140 | 141 | rfail: 142 | free_dynamic(); 143 | 144 | rexit: 145 | return ret; 146 | } 147 | 148 | void ProcState::end() 149 | { 150 | encoder_end(); 151 | mosample_end(); 152 | velo_end(); 153 | vid_end(); 154 | 155 | svr_game_texture = {}; 156 | } 157 | 158 | void ProcState::free_static() 159 | { 160 | encoder_free_static(); 161 | mosample_free_static(); 162 | velo_free_static(); 163 | vid_free_static(); 164 | } 165 | 166 | void ProcState::free_dynamic() 167 | { 168 | encoder_free_dynamic(); 169 | mosample_free_dynamic(); 170 | velo_free_dynamic(); 171 | vid_free_dynamic(); 172 | } 173 | 174 | s32 ProcState::get_game_rate() 175 | { 176 | if (movie_profile.mosample_enabled) 177 | { 178 | return movie_profile.video_fps * movie_profile.mosample_mult; 179 | } 180 | 181 | return movie_profile.video_fps; 182 | } 183 | -------------------------------------------------------------------------------- /src/svr_game/proc_video.cpp: -------------------------------------------------------------------------------- 1 | #include "proc_priv.h" 2 | 3 | const s32 VID_SHADER_SIZE = 8192; // Max size one shader can be when loading. 4 | 5 | bool ProcState::vid_init(ID3D11Device* d3d11_device) 6 | { 7 | bool ret = false; 8 | HRESULT hr; 9 | 10 | vid_d3d11_device = d3d11_device; 11 | vid_d3d11_device->AddRef(); 12 | 13 | vid_d3d11_device->GetImmediateContext(&vid_d3d11_context); 14 | 15 | if (!vid_create_d2d1()) 16 | { 17 | goto rfail; 18 | } 19 | 20 | if (!vid_create_dwrite()) 21 | { 22 | goto rfail; 23 | } 24 | 25 | vid_shader_mem = svr_alloc(VID_SHADER_SIZE); 26 | 27 | ret = true; 28 | goto rexit; 29 | 30 | rfail: 31 | 32 | rexit: 33 | return ret; 34 | } 35 | 36 | bool ProcState::vid_create_d2d1() 37 | { 38 | bool ret = false; 39 | HRESULT hr; 40 | 41 | IDXGIDevice* dxgi_device = NULL; 42 | vid_d3d11_device->QueryInterface(IID_PPV_ARGS(&dxgi_device)); 43 | 44 | hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_PPV_ARGS(&vid_d2d1_factory)); 45 | 46 | if (FAILED(hr)) 47 | { 48 | svr_log("ERROR: D2D1CreateFactory returned %#x\n", hr); 49 | goto rfail; 50 | } 51 | 52 | hr = vid_d2d1_factory->CreateDevice(dxgi_device, &vid_d2d1_device); 53 | 54 | if (FAILED(hr)) 55 | { 56 | svr_log("ERROR: ID2D1Factory::CreateDevice returned %#x\n", hr); 57 | goto rfail; 58 | } 59 | 60 | hr = vid_d2d1_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &vid_d2d1_context); 61 | 62 | if (FAILED(hr)) 63 | { 64 | svr_log("ERROR: ID2D1Device::CreateDeviceContext returned %#x\n", hr); 65 | goto rfail; 66 | } 67 | 68 | vid_d2d1_context->CreateSolidColorBrush({}, &vid_d2d1_solid_brush); 69 | 70 | ret = true; 71 | goto rexit; 72 | 73 | rfail: 74 | 75 | rexit: 76 | svr_maybe_release(&dxgi_device); 77 | return ret; 78 | } 79 | 80 | bool ProcState::vid_create_dwrite() 81 | { 82 | bool ret = false; 83 | HRESULT hr; 84 | 85 | hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&vid_dwrite_factory); 86 | 87 | if (FAILED(hr)) 88 | { 89 | svr_log("ERROR: Could not create DirectWrite factory (#x)\n", hr); 90 | goto rfail; 91 | } 92 | 93 | ret = true; 94 | goto rexit; 95 | 96 | rfail: 97 | 98 | rexit: 99 | return ret; 100 | } 101 | 102 | void ProcState::vid_free_static() 103 | { 104 | svr_maybe_release(&vid_d3d11_device); 105 | svr_maybe_release(&vid_d3d11_context); 106 | 107 | svr_maybe_release(&vid_d2d1_factory); 108 | svr_maybe_release(&vid_d2d1_device); 109 | svr_maybe_release(&vid_d2d1_context); 110 | svr_maybe_release(&vid_dwrite_factory); 111 | svr_maybe_release(&vid_d2d1_solid_brush); 112 | 113 | svr_maybe_free((void**)&vid_shader_mem); 114 | } 115 | 116 | void ProcState::vid_free_dynamic() 117 | { 118 | } 119 | 120 | bool ProcState::vid_load_shader(const char* name) 121 | { 122 | bool ret = false; 123 | 124 | char path[MAX_PATH]; 125 | SVR_SNPRINTF(path, "%s\\data\\shaders\\%s", svr_resource_path, name); 126 | 127 | HANDLE h = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 128 | 129 | if (h == INVALID_HANDLE_VALUE) 130 | { 131 | svr_log("ERROR: Could not load shader %s (%lu)\n", name, GetLastError()); 132 | goto rfail; 133 | } 134 | 135 | DWORD shader_size; 136 | ReadFile(h, vid_shader_mem, VID_SHADER_SIZE, &shader_size, NULL); 137 | 138 | vid_shader_size = shader_size; 139 | 140 | ret = true; 141 | goto rexit; 142 | 143 | rfail: 144 | 145 | rexit: 146 | if (h != INVALID_HANDLE_VALUE) 147 | { 148 | CloseHandle(h); 149 | } 150 | 151 | return ret; 152 | } 153 | 154 | bool ProcState::vid_create_shader(const char* name, void** shader, D3D11_SHADER_TYPE type) 155 | { 156 | bool ret = false; 157 | HRESULT hr; 158 | 159 | if (!vid_load_shader(name)) 160 | { 161 | goto rfail; 162 | } 163 | 164 | switch (type) 165 | { 166 | case D3D11_COMPUTE_SHADER: 167 | { 168 | hr = vid_d3d11_device->CreateComputeShader(vid_shader_mem, vid_shader_size, NULL, (ID3D11ComputeShader**)shader); 169 | break; 170 | } 171 | 172 | case D3D11_PIXEL_SHADER: 173 | { 174 | hr = vid_d3d11_device->CreatePixelShader(vid_shader_mem, vid_shader_size, NULL, (ID3D11PixelShader**)shader); 175 | break; 176 | } 177 | 178 | case D3D11_VERTEX_SHADER: 179 | { 180 | hr = vid_d3d11_device->CreateVertexShader(vid_shader_mem, vid_shader_size, NULL, (ID3D11VertexShader**)shader); 181 | break; 182 | } 183 | } 184 | 185 | if (FAILED(hr)) 186 | { 187 | svr_log("ERROR: Could not create shader %s (%#x)\n", name, hr); 188 | goto rfail; 189 | } 190 | 191 | ret = true; 192 | goto rexit; 193 | 194 | rfail: 195 | 196 | rexit: 197 | return ret; 198 | } 199 | 200 | bool ProcState::vid_create_shaders_list(ProcShader* shaders, s32 num) 201 | { 202 | bool ret = true; 203 | 204 | for (s32 i = 0; i < num; i++) 205 | { 206 | ProcShader* s = &shaders[i]; 207 | 208 | if (!vid_create_shader(s->name, s->dest, s->type)) 209 | { 210 | ret = false; 211 | break; 212 | } 213 | } 214 | 215 | return ret; 216 | } 217 | 218 | void ProcState::vid_update_constant_buffer(ID3D11Buffer* buffer, void* data, UINT size) 219 | { 220 | D3D11_MAPPED_SUBRESOURCE mapped; 221 | HRESULT hr = vid_d3d11_context->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped); 222 | assert(SUCCEEDED(hr)); 223 | 224 | memcpy(mapped.pData, data, size); 225 | 226 | vid_d3d11_context->Unmap(buffer, 0); 227 | } 228 | 229 | void ProcState::vid_clear_rtv(ID3D11RenderTargetView* rtv, float r, float g, float b, float a) 230 | { 231 | float clear_color[] = { r, g, b, a }; 232 | vid_d3d11_context->ClearRenderTargetView(rtv, clear_color); 233 | } 234 | 235 | s32 ProcState::vid_get_num_cs_threads(s32 unit) 236 | { 237 | // Thread group divisor constant must match the thread count in the compute shaders! 238 | return svr_align32(unit, 8) >> 3; 239 | } 240 | 241 | bool ProcState::vid_start() 242 | { 243 | return true; 244 | } 245 | 246 | void ProcState::vid_end() 247 | { 248 | } 249 | 250 | D2D1_COLOR_F ProcState::vid_fill_d2d1_color(SvrVec4I color) 251 | { 252 | D2D1_COLOR_F ret; 253 | ret.r = color.x / 255.0f; 254 | ret.g = color.y / 255.0f; 255 | ret.b = color.z / 255.0f; 256 | ret.a = color.w / 255.0f; 257 | return ret; 258 | } 259 | 260 | D2D1_POINT_2F ProcState::vid_fill_d2d1_pt(SvrVec2I p) 261 | { 262 | return D2D1::Point2F(p.x, p.y); 263 | } 264 | -------------------------------------------------------------------------------- /src/svr_game/unity_game.cpp: -------------------------------------------------------------------------------- 1 | #include "proc_priv.h" 2 | #include "proc_encoder.cpp" 3 | #include "proc_mosample.cpp" 4 | #include "proc_state.cpp" 5 | #include "proc_velo.cpp" 6 | #include "proc_video.cpp" 7 | #include "proc_profile.cpp" 8 | #include "proc_profile_opts.cpp" 9 | #include "svr_api.cpp" 10 | -------------------------------------------------------------------------------- /src/svr_launcher/launcher_ipc.cpp: -------------------------------------------------------------------------------- 1 | #include "launcher_priv.h" 2 | 3 | // The structure that will be located in the started process. 4 | // It is used as a parameter to the below function. 5 | struct IpcStructure 6 | { 7 | // Windows API functions. 8 | decltype(LoadLibraryA)* LoadLibraryA; 9 | decltype(GetProcAddress)* GetProcAddress; 10 | decltype(SetDllDirectoryA)* SetDllDirectoryA; 11 | 12 | u32 unused; 13 | 14 | char library_name[256]; // The path to the library to load. 15 | char export_name[256]; // The export function to call. 16 | char svr_path[256]; // The path of the SVR directory. Does not end with a slash. 17 | }; 18 | 19 | // The code that will run in the started process. 20 | // See below for the actual code for these generated bytes. 21 | #ifdef _WIN64 22 | const u8 IPC_REMOTE_FUNC_BYTES[] = 23 | { 24 | 0x48, 0x89, 0x5c, 0x24, 0x08, 25 | 0x57, 26 | 0x48, 0x83, 0xec, 0x30, 27 | 0x48, 0x8b, 0xf9, 28 | 0x48, 0x8d, 0x99, 0x1c, 0x02, 29 | 0x00, 0x00, 30 | 0x48, 0x8b, 0xcb, 31 | 0xff, 0x57, 0x10, 32 | 0x48, 0x8b, 0x07, 33 | 0x48, 0x8d, 0x4f, 0x1c, 34 | 0xff, 0xd0, 35 | 0x4c, 0x8b, 0x47, 0x08, 36 | 0x48, 0x8d, 0x97, 0x1c, 0x01, 37 | 0x00, 0x00, 38 | 0x48, 0x8b, 0xc8, 39 | 0x41, 0xff, 0xd0, 40 | 0x8b, 0x4f, 0x18, 41 | 0x89, 0x4c, 0x24, 0x28, 42 | 0x48, 0x8d, 0x4c, 0x24, 0x20, 43 | 0x48, 0x89, 0x5c, 0x24, 0x20, 44 | 0xff, 0xd0, 45 | 0x33, 0xc9, 46 | 0xff, 0x57, 0x10, 47 | 0x48, 0x8b, 0x5c, 0x24, 0x40, 48 | 0x48, 0x83, 0xc4, 0x30, 49 | 0x5f, 50 | 0xc3, 51 | }; 52 | #else 53 | const u8 IPC_REMOTE_FUNC_BYTES[] = 54 | { 55 | 0x55, 56 | 0x8b, 0xec, 57 | 0x83, 0xec, 0x0c, 58 | 0x56, 59 | 0x57, 60 | 0x8b, 0x7d, 0x08, 61 | 0x8b, 0x47, 0x08, 62 | 0x8d, 0xb7, 0x10, 0x02, 0x00, 63 | 0x00, 64 | 0x56, 65 | 0xff, 0xd0, 66 | 0x8b, 0x0f, 67 | 0x8d, 0x47, 0x10, 68 | 0x50, 69 | 0xff, 0xd1, 70 | 0x8b, 0x57, 0x04, 71 | 0x8d, 0x8f, 0x10, 0x01, 0x00, 72 | 0x00, 73 | 0x51, 74 | 0x50, 75 | 0xff, 0xd2, 76 | 0x8b, 0x4f, 0x0c, 77 | 0x89, 0x4d, 0xf8, 78 | 0x8d, 0x4d, 0xf4, 79 | 0x51, 80 | 0x89, 0x75, 0xf4, 81 | 0xff, 0xd0, 82 | 0x8b, 0x47, 0x08, 83 | 0x83, 0xc4, 0x04, 84 | 0x6a, 0x00, 85 | 0xff, 0xd0, 86 | 0x5f, 87 | 0x5e, 88 | 0x8b, 0xe5, 89 | 0x5d, 90 | 0xc2, 0x04, 0x00, 91 | }; 92 | #endif 93 | 94 | // You can use code listing in Visual Studio to generate the machine code for these functions: 95 | // Using the property pages UI for unity_launcher.cpp, go to C/C++ -> Output Files -> Assembler Output and set it to Assembly With Machine Code (/FaC). 96 | // Then compile the file and find the .cod file in the build directory. Open the file, find this function, and extract the bytes. 97 | // For the function to actually be compiled in, it must be referenced. Enable the generation code in main for this and build in Release. 98 | // Note that MSVC sometimes doesn't always create this file or it is sometimes not updated properly. Rebuild the project until it works. 99 | 100 | // This is the function that will be injected into the target process. 101 | // The instructions IPC_REMOTE_FUNC_BYTES above is the result of this function. 102 | VOID CALLBACK ipc_remote_func(ULONG_PTR param) 103 | { 104 | IpcStructure* data = (IpcStructure*)param; 105 | 106 | // There is no error handling here as there's no practical way to report 107 | // stuff back within this limited environment. 108 | // There have not been cases of these api functions failing with proper input 109 | // so let's stick with the simplest working solution for now. 110 | 111 | // Add our resource path as searchable to resolve library dependencies. 112 | data->SetDllDirectoryA(data->svr_path); 113 | 114 | // We need to call the right export in svr_standalone.dll. 115 | 116 | HMODULE module = data->LoadLibraryA(data->library_name); 117 | SvrGameInitFuncType init_func = (SvrGameInitFuncType)data->GetProcAddress(module, data->export_name); 118 | 119 | SvrGameInitData init_data; 120 | init_data.svr_path = data->svr_path; 121 | init_data.unused = 0; 122 | 123 | init_func(&init_data); 124 | 125 | // Restore the default search order. 126 | data->SetDllDirectoryA(NULL); 127 | } 128 | 129 | void LauncherState::ipc_setup_in_remote_process(LauncherGame* game, HANDLE process, HANDLE thread) 130 | { 131 | // Allocate a sufficient enough size in the target process. 132 | // It needs to be able to contain all function bytes and the structure containing variable length strings. 133 | // The virtual memory that we allocated should not be freed as it will be used 134 | // as reference for future use within the application itself. 135 | void* remote_mem = VirtualAllocEx(process, NULL, 2048, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 136 | 137 | if (remote_mem == NULL) 138 | { 139 | DWORD code = GetLastError(); 140 | TerminateProcess(process, 1); 141 | svr_log("VirtualAllocEx failed with code %lu\n", code); 142 | launcher_error("Could not initialize standalone SVR. If you use an antivirus, add exception or disable."); 143 | } 144 | 145 | SIZE_T written = 0; 146 | u8* write_ptr = (u8*)remote_mem; 147 | 148 | void* remote_func_addr = write_ptr; 149 | WriteProcessMemory(process, write_ptr, IPC_REMOTE_FUNC_BYTES, sizeof(IPC_REMOTE_FUNC_BYTES), &written); 150 | write_ptr += written; 151 | 152 | // All addresses here must match up in the context of the target process, not our own. 153 | // The operating system api functions will always be located in the same address of every 154 | // process so those do not have to be adjusted. 155 | IpcStructure structure; 156 | structure.LoadLibraryA = LoadLibraryA; 157 | structure.GetProcAddress = GetProcAddress; 158 | structure.SetDllDirectoryA = SetDllDirectoryA; 159 | structure.unused = 0; 160 | 161 | #ifdef _WIN64 162 | SVR_COPY_STRING(svr_va("%s\\svr_standalone64.dll", working_dir), structure.library_name); 163 | #else 164 | SVR_COPY_STRING(svr_va("%s\\svr_standalone.dll", working_dir), structure.library_name); 165 | #endif 166 | 167 | SVR_COPY_STRING("svr_init_from_launcher", structure.export_name); 168 | SVR_COPY_STRING(working_dir, structure.svr_path); 169 | 170 | void* remote_structure_addr = write_ptr; 171 | WriteProcessMemory(process, write_ptr, &structure, sizeof(structure), &written); 172 | write_ptr += written; 173 | 174 | // Queue up our procedural function to run instantly on the main thread when the process is resumed. 175 | if (!QueueUserAPC((PAPCFUNC)remote_func_addr, thread, (ULONG_PTR)remote_structure_addr)) 176 | { 177 | DWORD code = GetLastError(); 178 | TerminateProcess(process, 1); 179 | svr_log("QueueUserAPC failed with code %lu\n", code); 180 | launcher_error("Could not initialize standalone SVR. If you use an antivirus, add exception or disable."); 181 | } 182 | } 183 | 184 | void ipc_generate_bytes() 185 | { 186 | IpcStructure structure = {}; 187 | structure.LoadLibraryA = LoadLibraryA; 188 | structure.GetProcAddress = GetProcAddress; 189 | structure.SetDllDirectoryA = SetDllDirectoryA; 190 | 191 | // It is important to use QueueUserAPC here to produce the correct output. 192 | // Calling remote_func directly will produce uniquely optimized code which cannot 193 | // work in another process. 194 | QueueUserAPC(ipc_remote_func, GetCurrentThread(), (ULONG_PTR)&structure); 195 | 196 | // Used to signal the thread so the queued function will run. 197 | SleepEx(0, TRUE); 198 | } 199 | -------------------------------------------------------------------------------- /src/svr_launcher/launcher_main.cpp: -------------------------------------------------------------------------------- 1 | #include "launcher_priv.h" 2 | 3 | LauncherState launcher_state; 4 | 5 | int main(int argc, char** argv) 6 | { 7 | #ifdef SVR_DEBUG 8 | _set_error_mode(_OUT_TO_MSGBOX); // Must be called so we can actually use assert because Microsoft messed it up in console builds. 9 | #endif 10 | 11 | // Enable this to generate the machine code for remote_func (see comments at that function). 12 | #if 0 13 | void ipc_generate_bytes(); 14 | ipc_generate_bytes(); 15 | return 0; 16 | #endif 17 | 18 | // For standalone mode, the launcher creates the log file that the game then appends to. 19 | svr_init_log("data\\SVR_LOG.txt", false); 20 | 21 | launcher_state.init(); 22 | 23 | // Autostarting a game works by giving the id. 24 | if (argc == 2) 25 | { 26 | const char* id = argv[1]; 27 | return launcher_state.autostart_game(id); 28 | } 29 | 30 | return launcher_state.show_start_menu(); 31 | } 32 | -------------------------------------------------------------------------------- /src/svr_launcher/launcher_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_defs.h" 4 | #include "svr_standalone_common.h" 5 | #include 6 | #include 7 | #include 8 | #include "svr_log.h" 9 | #include "svr_vdf.h" 10 | #include "svr_ini.h" 11 | #include "svr_array.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "svr_alloc.h" 18 | #include 19 | 20 | #include "launcher_state.h" 21 | -------------------------------------------------------------------------------- /src/svr_launcher/launcher_state.cpp: -------------------------------------------------------------------------------- 1 | #include "launcher_priv.h" 2 | 3 | void LauncherState::init() 4 | { 5 | GetCurrentDirectoryA(MAX_PATH, working_dir); 6 | 7 | // Enable to show system information and stuff on start. 8 | #if 1 9 | if (!IsWindows10OrGreater()) 10 | { 11 | launcher_error("Windows 10 or later is needed to use SVR."); 12 | } 13 | 14 | SYSTEMTIME lt; 15 | GetLocalTime(<); 16 | 17 | launcher_log("SVR " SVR_ARCH_STRING " version %d (%02d/%02d/%04d %02d:%02d:%02d)\n", SVR_VERSION, lt.wDay, lt.wMonth, lt.wYear, lt.wHour, lt.wMinute, lt.wSecond); 18 | launcher_log("This is a standalone version of SVR. Interoperability with other applications may not work\n"); 19 | launcher_log("For more information see https://github.com/crashfort/SourceDemoRender\n"); 20 | 21 | sys_show_windows_version(); 22 | sys_show_processor(); 23 | sys_show_available_memory(); 24 | sys_check_hw_caps(); 25 | #endif 26 | 27 | if (steam_find_path()) 28 | { 29 | steam_find_libraries(); 30 | } 31 | 32 | load_games(); 33 | 34 | svr_log("Found %d games\n", game_list.size); 35 | } 36 | 37 | // Will put both to console and to file. 38 | // Use printf for other messages that should not be shown in file. 39 | // Use svr_log for messages that should not be shown on screen. 40 | void LauncherState::launcher_log(const char* format, ...) 41 | { 42 | va_list va; 43 | va_start(va, format); 44 | svr_log_v(format, va); 45 | vprintf(format, va); 46 | va_end(va); 47 | } 48 | 49 | __declspec(noreturn) void LauncherState::launcher_error(const char* format, ...) 50 | { 51 | char message[1024]; 52 | 53 | va_list va; 54 | va_start(va, format); 55 | SVR_VSNPRINTF(message, format, va); 56 | va_end(va); 57 | 58 | svr_log("!!! LAUNCHER ERROR: %s\n", message); 59 | 60 | MessageBoxA(NULL, message, "SVR", MB_TASKMODAL | MB_ICONERROR | MB_OK); 61 | 62 | ExitProcess(1); 63 | } 64 | 65 | // Prompt user for an input number. 66 | s32 LauncherState::get_choice_from_user(s32 min, s32 max) 67 | { 68 | s32 selection = -1; 69 | 70 | while (selection == -1) 71 | { 72 | char buf[32]; 73 | char* res = fgets(buf, SVR_ARRAY_SIZE(buf), stdin); 74 | 75 | if (res == NULL) 76 | { 77 | // Can get here from Ctrl+C. 78 | return -1; 79 | } 80 | 81 | selection = atoi(buf); 82 | 83 | if (selection <= min || selection > max) 84 | { 85 | selection = -1; 86 | continue; 87 | } 88 | } 89 | 90 | return selection - 1; // Numbers displayed are 1 based. 91 | } 92 | -------------------------------------------------------------------------------- /src/svr_launcher/launcher_state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct LauncherGame 4 | { 5 | char* file_name; // File name of ini. 6 | char* display_name; // Display name. 7 | char* path; // Path to executable. 8 | char* args; // Extra stuff to put in the start args. 9 | }; 10 | 11 | struct LauncherState 12 | { 13 | // ----------------------------------------------- 14 | // Program state: 15 | 16 | // Our directory where we are running from. The game needs to know this. 17 | // This does not end with a slash. 18 | char working_dir[MAX_PATH]; 19 | 20 | SvrDynArray game_list; 21 | 22 | void init(); 23 | 24 | void launcher_log(const char* format, ...); 25 | __declspec(noreturn) void launcher_error(const char* format, ...); 26 | s32 get_choice_from_user(s32 min, s32 max); 27 | s32 start_game(LauncherGame* game); 28 | s32 autostart_game(const char* id); 29 | void load_games(); 30 | bool parse_game(const char* file, LauncherGame* dest); 31 | void free_game(LauncherGame* game); 32 | s32 show_start_menu(); 33 | bool exe_is_right_arch(const char* path); 34 | 35 | // ----------------------------------------------- 36 | // Steam state: 37 | 38 | // Path to the main Steam installation. This does not end with a slash. 39 | // The libraries are in steam_library_paths. 40 | char steam_path[MAX_PATH]; 41 | 42 | // A Steam library can be installed anywhere, we have to iterate over all of them to see where a game is located. 43 | // These do not end with a slash. 44 | SvrDynArray steam_library_paths; 45 | 46 | bool steam_find_path(); 47 | bool steam_find_libraries(); 48 | char* steam_get_game_path_in_any_library(const char* game_steam_path); 49 | 50 | // ----------------------------------------------- 51 | // System state: 52 | 53 | void sys_show_windows_version(); 54 | void sys_show_processor(); 55 | void sys_show_available_memory(); 56 | void sys_check_hw_caps(); 57 | 58 | // ----------------------------------------------- 59 | // IPC state: 60 | 61 | void ipc_setup_in_remote_process(LauncherGame* game, HANDLE process, HANDLE thread); 62 | }; 63 | -------------------------------------------------------------------------------- /src/svr_launcher/launcher_steam.cpp: -------------------------------------------------------------------------------- 1 | #include "launcher_priv.h" 2 | 3 | bool LauncherState::steam_find_path() 4 | { 5 | HKEY steam_hkey = NULL; 6 | 7 | if (RegOpenKeyExA(HKEY_CURRENT_USER, "SOFTWARE\\Valve\\Steam", 0, KEY_READ, &steam_hkey) != 0) 8 | { 9 | launcher_log("Steam is not installed."); 10 | return false; 11 | } 12 | 13 | DWORD steam_path_size = MAX_PATH; 14 | 15 | // These have to exist otherwise Steam wouldn't work. 16 | 17 | RegGetValueA(steam_hkey, NULL, "SteamPath", RRF_RT_REG_SZ, NULL, steam_path, &steam_path_size); 18 | 19 | for (DWORD i = 0; i < steam_path_size; i++) 20 | { 21 | if (steam_path[i] == '/') 22 | { 23 | steam_path[i] = '\\'; 24 | } 25 | } 26 | 27 | RegCloseKey(steam_hkey); 28 | return true; 29 | } 30 | 31 | bool LauncherState::steam_find_libraries() 32 | { 33 | bool ret = false; 34 | 35 | char full_vdf_path[MAX_PATH]; 36 | SVR_SNPRINTF(full_vdf_path, "%s\\steamapps\\libraryfolders.vdf", steam_path); 37 | 38 | SvrVdfSection* vdf_root = svr_vdf_load(full_vdf_path); 39 | 40 | if (vdf_root == NULL) 41 | { 42 | launcher_log("No Steam libraries could be found."); 43 | goto rfail; 44 | } 45 | 46 | SvrVdfSection* libraries_folders_section = svr_vdf_section_find_section(vdf_root, "libraryfolders", NULL); 47 | 48 | if (libraries_folders_section == NULL) 49 | { 50 | goto rfail; 51 | } 52 | 53 | for (s32 lib_idx = 0; lib_idx < libraries_folders_section->sections.size; lib_idx++) 54 | { 55 | SvrVdfSection* lib_section = libraries_folders_section->sections[lib_idx]; 56 | SvrVdfKeyValue* path_kv = svr_vdf_section_find_kv(lib_section, "path"); 57 | 58 | if (path_kv == NULL) 59 | { 60 | continue; 61 | } 62 | 63 | // Paths in vdf will be escaped, we need to unescape. 64 | 65 | char new_path[MAX_PATH]; 66 | new_path[0] = 0; 67 | 68 | svr_unescape_path(path_kv->value, new_path, SVR_ARRAY_SIZE(new_path)); 69 | 70 | char* full_path = svr_dup_str(svr_va("%s\\steamapps", new_path)); 71 | 72 | steam_library_paths.push(full_path); 73 | } 74 | 75 | ret = true; 76 | 77 | rfail: 78 | rexit: 79 | if (vdf_root) 80 | { 81 | svr_vdf_free(vdf_root); 82 | } 83 | 84 | return ret; 85 | } 86 | 87 | // Find the library and path where a game is installed. 88 | // The game_steam_path parameter should be the steam_path parameter in the ini file, which is relative to the steamapps folder. 89 | // Returns NULL if the game is not installed in the system. 90 | char* LauncherState::steam_get_game_path_in_any_library(const char* game_steam_path) 91 | { 92 | char path[MAX_PATH]; 93 | 94 | for (s32 i = 0; i < steam_library_paths.size; i++) 95 | { 96 | SVR_SNPRINTF(path, "%s\\%s", steam_library_paths[i], game_steam_path); 97 | 98 | if (svr_does_file_exist(path)) 99 | { 100 | // Game is in this library. We now have the full path. 101 | 102 | char* ret = svr_dup_str(path); 103 | return ret; 104 | } 105 | } 106 | 107 | return NULL; 108 | } 109 | -------------------------------------------------------------------------------- /src/svr_launcher/launcher_sys.cpp: -------------------------------------------------------------------------------- 1 | #include "launcher_priv.h" 2 | 3 | void LauncherState::sys_show_windows_version() 4 | { 5 | HKEY hkey; 6 | 7 | if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hkey) != 0) 8 | { 9 | return; 10 | } 11 | 12 | const s32 REG_BUF_SIZE = 64; 13 | 14 | char product_name[REG_BUF_SIZE]; 15 | char current_build[REG_BUF_SIZE]; 16 | char release_id[REG_BUF_SIZE]; 17 | 18 | DWORD product_name_size = REG_BUF_SIZE; 19 | DWORD current_build_size = REG_BUF_SIZE; 20 | DWORD release_id_size = REG_BUF_SIZE; 21 | 22 | RegGetValueA(hkey, NULL, "ProductName", RRF_RT_REG_SZ, NULL, product_name, &product_name_size); 23 | RegGetValueA(hkey, NULL, "CurrentBuild", RRF_RT_REG_SZ, NULL, current_build, ¤t_build_size); 24 | RegGetValueA(hkey, NULL, "ReleaseId", RRF_RT_REG_SZ, NULL, release_id, &release_id_size); 25 | 26 | // Will show like Windows 10 Enterprise version 2004 build 19041. 27 | char winver[192]; 28 | SVR_SNPRINTF(winver, "%s version %d build %d", product_name, atoi(release_id), strtol(current_build, NULL, 10)); 29 | 30 | svr_log("Using operating system %s\n", winver); 31 | } 32 | 33 | void LauncherState::sys_show_processor() 34 | { 35 | HKEY hkey; 36 | 37 | if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hkey) != 0) 38 | { 39 | return; 40 | } 41 | 42 | const s32 REG_BUF_SIZE = 128; 43 | 44 | char name[REG_BUF_SIZE]; 45 | DWORD name_size = REG_BUF_SIZE; 46 | 47 | RegGetValueA(hkey, NULL, "ProcessorNameString", RRF_RT_REG_SZ, NULL, name, &name_size); 48 | 49 | // The value will have a lot of extra spaces at the end. 50 | svr_trim_right(name, strlen(name)); 51 | 52 | svr_log("Using processor %s (%lu cpus)\n", name, GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)); 53 | } 54 | 55 | void LauncherState::sys_show_available_memory() 56 | { 57 | MEMORYSTATUSEX mem; 58 | mem.dwLength = sizeof(MEMORYSTATUSEX); 59 | 60 | GlobalMemoryStatusEx(&mem); 61 | 62 | svr_log("The system has %lld mb of memory (%lld mb usable)\n", SVR_FROM_MB(mem.ullTotalPhys), SVR_FROM_MB(mem.ullAvailPhys)); 63 | } 64 | 65 | // We cannot store this result so it has to be done every start. 66 | void LauncherState::sys_check_hw_caps() 67 | { 68 | ID3D11Device* d3d11_device = NULL; 69 | ID3D11DeviceContext* d3d11_context = NULL; 70 | 71 | UINT device_create_flags = D3D11_CREATE_DEVICE_SINGLETHREADED; 72 | 73 | // Use a lower feature level here than needed (we actually use 12_0) in order to get a better description 74 | // of the adapter below, and also to more accurately query the hw caps. 75 | const D3D_FEATURE_LEVEL MINIMUM_DEVICE_LEVEL = D3D_FEATURE_LEVEL_11_0; 76 | 77 | const D3D_FEATURE_LEVEL DEVICE_LEVELS[] = 78 | { 79 | MINIMUM_DEVICE_LEVEL 80 | }; 81 | 82 | HRESULT hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, device_create_flags, DEVICE_LEVELS, 1, D3D11_SDK_VERSION, &d3d11_device, NULL, &d3d11_context); 83 | 84 | if (FAILED(hr)) 85 | { 86 | svr_log("D3D11CreateDevice failed with code %#x\n", hr); 87 | launcher_error("HW support could not be queried. Is there a graphics adapter in the system?"); 88 | } 89 | 90 | IDXGIDevice* dxgi_device; 91 | d3d11_device->QueryInterface(IID_PPV_ARGS(&dxgi_device)); 92 | 93 | IDXGIAdapter* dxgi_adapter; 94 | dxgi_device->GetAdapter(&dxgi_adapter); 95 | 96 | DXGI_ADAPTER_DESC dxgi_adapter_desc; 97 | dxgi_adapter->GetDesc(&dxgi_adapter_desc); 98 | 99 | // Useful for future troubleshooting. 100 | // Use https://www.pcilookup.com/ to see more information about device and vendor ids. 101 | svr_log("Using graphics device %x by vendor %x\n", dxgi_adapter_desc.DeviceId, dxgi_adapter_desc.VendorId); 102 | 103 | // We use this format for motion sample. 104 | 105 | D3D11_FEATURE_DATA_FORMAT_SUPPORT2 fmt_support2; 106 | fmt_support2.InFormat = DXGI_FORMAT_R32G32B32A32_FLOAT; 107 | d3d11_device->CheckFeatureSupport(D3D11_FEATURE_FORMAT_SUPPORT2, &fmt_support2, sizeof(D3D11_FEATURE_DATA_FORMAT_SUPPORT2)); 108 | 109 | bool has_typed_uav_load = fmt_support2.OutFormatSupport2 & D3D11_FORMAT_SUPPORT2_UAV_TYPED_LOAD; 110 | bool has_typed_uav_store = fmt_support2.OutFormatSupport2 & D3D11_FORMAT_SUPPORT2_UAV_TYPED_STORE; 111 | bool has_typed_uav_support = has_typed_uav_load && has_typed_uav_store; 112 | 113 | if (!has_typed_uav_support) 114 | { 115 | launcher_error("This system does not meet the requirements to use SVR."); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/svr_launcher/svr_launcher.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SVR Launcher 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/svr_launcher/unity_launcher.cpp: -------------------------------------------------------------------------------- 1 | #include "launcher_priv.h" 2 | #include "launcher_main.cpp" 3 | #include "launcher_ipc.cpp" 4 | #include "launcher_start.cpp" 5 | #include "launcher_state.cpp" 6 | #include "launcher_steam.cpp" 7 | #include "launcher_sys.cpp" 8 | -------------------------------------------------------------------------------- /src/svr_shared/svr_console.cpp: -------------------------------------------------------------------------------- 1 | #include "svr_common.h" 2 | #include "svr_console.h" 3 | #include "svr_log.h" 4 | #include 5 | 6 | using GameMsgFn = void(__cdecl*)(const char* format, ...); 7 | 8 | GameMsgFn svr_console_msg_fn; 9 | 10 | void svr_console_init() 11 | { 12 | HMODULE module = GetModuleHandleA("tier0.dll"); 13 | 14 | if (module == NULL) 15 | { 16 | return; 17 | } 18 | 19 | svr_console_msg_fn = (GameMsgFn)GetProcAddress(module, "Msg"); 20 | } 21 | 22 | void svr_console_msg(const char* format, ...) 23 | { 24 | if (svr_console_msg_fn == NULL) 25 | { 26 | return; 27 | } 28 | 29 | va_list va; 30 | va_start(va, format); 31 | svr_console_msg_v(format, va); 32 | va_end(va); 33 | } 34 | 35 | void svr_console_msg_v(const char* format, va_list va) 36 | { 37 | if (svr_console_msg_fn == NULL) 38 | { 39 | return; 40 | } 41 | 42 | char buf[1024]; 43 | SVR_VSNPRINTF(buf, format, va); 44 | 45 | svr_console_msg_fn(buf); 46 | } 47 | 48 | void svr_console_msg_and_log(const char* format, ...) 49 | { 50 | va_list va; 51 | va_start(va, format); 52 | svr_console_msg_and_log_v(format, va); 53 | va_end(va); 54 | } 55 | 56 | void svr_console_msg_and_log_v(const char* format, va_list va) 57 | { 58 | svr_log_v(format, va); 59 | svr_console_msg_v(format, va); 60 | } 61 | -------------------------------------------------------------------------------- /src/svr_shared/svr_console.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef SVR_SHARED_DLL 5 | #define SVR_CONSOLE_API __declspec(dllexport) 6 | #else 7 | #define SVR_CONSOLE_API __declspec(dllimport) 8 | #endif 9 | 10 | // Shared between svr_standalone.dll and svr_game.dll to print to the game console. 11 | 12 | extern "C" 13 | { 14 | 15 | SVR_CONSOLE_API void svr_console_init(); 16 | 17 | // Puts to game console. 18 | SVR_CONSOLE_API void svr_console_msg(const char* format, ...); 19 | SVR_CONSOLE_API void svr_console_msg_v(const char* format, va_list va); 20 | 21 | // Puts to game console and log. 22 | SVR_CONSOLE_API void svr_console_msg_and_log(const char* format, ...); 23 | SVR_CONSOLE_API void svr_console_msg_and_log_v(const char* format, va_list va); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/svr_shared/svr_log.cpp: -------------------------------------------------------------------------------- 1 | #include "svr_log.h" 2 | #include "svr_common.h" 3 | #include 4 | #include 5 | 6 | HANDLE log_file_handle; 7 | SRWLOCK log_lock; 8 | 9 | void log_function(const char* text, s32 length) 10 | { 11 | assert(log_file_handle); 12 | 13 | AcquireSRWLockExclusive(&log_lock); 14 | WriteFile(log_file_handle, text, sizeof(char) * length, NULL, NULL); 15 | ReleaseSRWLockExclusive(&log_lock); 16 | } 17 | 18 | void svr_init_log(const char* log_file_path, bool append) 19 | { 20 | if (log_file_handle) 21 | { 22 | return; 23 | } 24 | 25 | DWORD open_flags = append ? OPEN_EXISTING : CREATE_ALWAYS; 26 | log_file_handle = CreateFileA(log_file_path, GENERIC_WRITE, FILE_SHARE_READ, NULL, open_flags, FILE_ATTRIBUTE_NORMAL, NULL); 27 | 28 | // The file might be set to read only or something. Don't bother then. 29 | if (log_file_handle == INVALID_HANDLE_VALUE) 30 | { 31 | log_file_handle = NULL; 32 | return; 33 | } 34 | 35 | if (append) 36 | { 37 | // We need to move the file pointer to append new text. 38 | 39 | LARGE_INTEGER dist_to_move = {}; 40 | SetFilePointerEx(log_file_handle, dist_to_move, NULL, FILE_END); 41 | } 42 | } 43 | 44 | void svr_free_log() 45 | { 46 | svr_maybe_close_handle(&log_file_handle); 47 | } 48 | 49 | // Below log functions not used for integrated SVR, but we may get here still from game_log. 50 | 51 | void svr_log(const char* format, ...) 52 | { 53 | if (log_file_handle == NULL) 54 | { 55 | return; 56 | } 57 | 58 | va_list va; 59 | va_start(va, format); 60 | svr_log_v(format, va); 61 | va_end(va); 62 | } 63 | 64 | void svr_log_v(const char* format, va_list va) 65 | { 66 | if (log_file_handle == NULL) 67 | { 68 | return; 69 | } 70 | 71 | // We don't deal with huge messages and truncate as needed. 72 | 73 | char buf[1024]; 74 | s32 count = SVR_VSNPRINTF(buf, format, va); 75 | log_function(buf, count); 76 | } 77 | -------------------------------------------------------------------------------- /src/svr_shared/svr_log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef SVR_SHARED_DLL 5 | #define SVR_LOG_API __declspec(dllexport) 6 | #else 7 | #define SVR_LOG_API __declspec(dllimport) 8 | #endif 9 | 10 | // File logging stuff. 11 | // This is a DLL so the same state can be shared between svr_standalone.dll and svr_game.dll, as they are both loaded in the same process. 12 | 13 | extern "C" 14 | { 15 | 16 | SVR_LOG_API void svr_init_log(const char* log_file_path, bool append); 17 | SVR_LOG_API void svr_free_log(); 18 | 19 | SVR_LOG_API void svr_log(const char* format, ...); 20 | SVR_LOG_API void svr_log_v(const char* format, va_list va); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/svr_standalone/game_audio.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | struct GameAudioSearch 4 | { 5 | GameCaps caps; 6 | GameAudioDesc* desc; 7 | }; 8 | 9 | GameAudioSearch GAME_AUDIO_BACKENDS[] = 10 | { 11 | GameAudioSearch { GAME_CAP_AUDIO_DEVICE_1, &game_audio_v1_desc }, 12 | GameAudioSearch { GAME_CAP_AUDIO_DEVICE_1_5, &game_audio_v1_desc }, 13 | GameAudioSearch { GAME_CAP_AUDIO_DEVICE_2, &game_audio_v2_desc }, 14 | }; 15 | 16 | void game_audio_init() 17 | { 18 | GameAudioDesc* best_desc = NULL; 19 | s32 best_match = 0; 20 | 21 | for (s32 i = 0; i < SVR_ARRAY_SIZE(GAME_AUDIO_BACKENDS); i++) 22 | { 23 | GameAudioSearch* s = &GAME_AUDIO_BACKENDS[i]; 24 | 25 | s32 num_match = svr_count_set_bits(game_state.search_desc.caps & s->caps); 26 | 27 | if (num_match > best_match) 28 | { 29 | best_desc = s->desc; 30 | best_match = num_match; 31 | } 32 | } 33 | 34 | game_state.audio_desc = best_desc; 35 | 36 | if (game_state.audio_desc) 37 | { 38 | svr_log("Using game audio backend %s\n", game_state.audio_desc->name); 39 | game_state.audio_desc->init(); 40 | } 41 | } 42 | 43 | void game_audio_free() 44 | { 45 | if (game_state.audio_desc) 46 | { 47 | game_state.audio_desc->free(); 48 | game_state.audio_desc = NULL; 49 | } 50 | } 51 | 52 | void game_audio_frame() 53 | { 54 | if (svr_is_audio_enabled()) 55 | { 56 | if (game_state.audio_desc) 57 | { 58 | // Figure out how many samples we need to process for this frame. 59 | 60 | float time_ahead_to_mix = 1.0f / (float)game_state.rec_game_rate; 61 | float num_frac_samples_to_mix = (time_ahead_to_mix * game_state.search_desc.snd_sample_rate) + game_state.snd_lost_mix_time; 62 | 63 | s32 num_samples_to_mix = (s32)num_frac_samples_to_mix; 64 | game_state.snd_lost_mix_time = num_frac_samples_to_mix - (float)num_samples_to_mix; 65 | 66 | game_state.audio_desc->mix_audio_for_one_frame(num_samples_to_mix); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/svr_standalone/game_audio_v1.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | // The audio backend need times to be aligned to 4 sample boundaries. 4 | // The skipped samples are remembered for the next iteration. 5 | s32 game_audio_v1_align_sample_time(s32 value) 6 | { 7 | return value & ~3; 8 | } 9 | 10 | void game_audio_v1_init() 11 | { 12 | } 13 | 14 | void game_audio_v1_free() 15 | { 16 | } 17 | 18 | void game_audio_v1_mix_audio_for_one_frame(s32 num_samples_to_mix) 19 | { 20 | s32 paint_time = game_get_snd_paint_time_0(); 21 | s32 raw_end_time = paint_time + num_samples_to_mix + game_state.snd_skipped_samples; 22 | s32 aligned_end_time = game_audio_v1_align_sample_time(raw_end_time); 23 | s32 num_samples = aligned_end_time - paint_time; 24 | 25 | game_state.snd_skipped_samples = raw_end_time - aligned_end_time; 26 | game_state.snd_num_samples = num_samples; 27 | 28 | if (num_samples > 0) 29 | { 30 | game_state.snd_is_painting = true; 31 | game_snd_paint_chans_override_0(aligned_end_time, game_state.snd_listener_underwater); 32 | game_state.snd_is_painting = false; 33 | } 34 | } 35 | 36 | GameAudioDesc game_audio_v1_desc = 37 | { 38 | .name = "AudioV1", 39 | .init = game_audio_v1_init, 40 | .free = game_audio_v1_free, 41 | .mix_audio_for_one_frame = game_audio_v1_mix_audio_for_one_frame, 42 | }; 43 | -------------------------------------------------------------------------------- /src/svr_standalone/game_audio_v2.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | // The audio backend need times to be aligned to 4 sample boundaries. 4 | // The skipped samples are remembered for the next iteration. 5 | s64 game_audio_v2_align_sample_time(s64 value) 6 | { 7 | return value & ~3; 8 | } 9 | 10 | void game_audio_v2_init() 11 | { 12 | } 13 | 14 | void game_audio_v2_free() 15 | { 16 | } 17 | 18 | void game_audio_v2_mix_audio_for_one_frame(s32 num_samples_to_mix) 19 | { 20 | s64 paint_time = game_get_snd_paint_time_1(); 21 | s64 raw_end_time = paint_time + num_samples_to_mix + game_state.snd_skipped_samples; 22 | s64 aligned_end_time = game_audio_v2_align_sample_time(raw_end_time); 23 | s64 num_samples = aligned_end_time - paint_time; 24 | 25 | game_state.snd_skipped_samples = raw_end_time - aligned_end_time; 26 | game_state.snd_num_samples = num_samples; 27 | 28 | if (num_samples > 0) 29 | { 30 | game_state.snd_is_painting = true; 31 | game_snd_paint_chans_override_1(aligned_end_time, game_state.snd_listener_underwater); 32 | game_state.snd_is_painting = false; 33 | } 34 | } 35 | 36 | GameAudioDesc game_audio_v2_desc = 37 | { 38 | .name = "AudioV2", 39 | .init = game_audio_v2_init, 40 | .free = game_audio_v2_free, 41 | .mix_audio_for_one_frame = game_audio_v2_mix_audio_for_one_frame, 42 | }; 43 | -------------------------------------------------------------------------------- /src/svr_standalone/game_cfg.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | bool game_has_cfg(const char* name) 4 | { 5 | char full_cfg_path[MAX_PATH]; 6 | SVR_SNPRINTF(full_cfg_path, "%s\\data\\cfg\\%s", game_state.svr_path, name); 7 | 8 | bool res = svr_does_file_exist(full_cfg_path); 9 | return res; 10 | } 11 | 12 | bool game_run_cfg(const char* name, bool required) 13 | { 14 | char full_cfg_path[MAX_PATH]; 15 | SVR_SNPRINTF(full_cfg_path, "%s\\data\\cfg\\%s", game_state.svr_path, name); 16 | 17 | // Commands must end with a newline. 18 | char* file_mem = svr_read_file_as_string(full_cfg_path, SVR_READ_FILE_FLAGS_NEW_LINE); 19 | 20 | if (file_mem == NULL) 21 | { 22 | if (required) 23 | { 24 | svr_log("ERROR: Could not open cfg %s\n", full_cfg_path); 25 | } 26 | 27 | return false; 28 | } 29 | 30 | svr_log("Running cfg %s\n", name); 31 | 32 | // The file can be executed as is. The game takes care of splitting by newline. 33 | // We don't monitor what is inside the cfg, it's up to the user. 34 | game_engine_client_command(file_mem); 35 | 36 | svr_free(file_mem); 37 | 38 | return true; 39 | } 40 | 41 | // Run all user cfgs for a given event (such as movie start or movie end). 42 | void game_run_cfgs_for_event(const char* name) 43 | { 44 | game_run_cfg(svr_va("svr_movie_%s.cfg", name), true); 45 | game_run_cfg(svr_va("svr_movie_%s_user.cfg", name), false); 46 | } 47 | -------------------------------------------------------------------------------- /src/svr_standalone/game_d3d9ex.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | struct GameD3D9ExState 4 | { 5 | IDirect3DDevice9Ex* device; 6 | 7 | GameFnHook present_hook; 8 | }; 9 | 10 | GameD3D9ExState game_d3d9ex_state; 11 | 12 | HRESULT __stdcall game_d3d9ex_present_override(void* p, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion) 13 | { 14 | if (game_state.rec_disable_window_update) 15 | { 16 | if (svr_movie_active()) 17 | { 18 | return S_OK; 19 | } 20 | } 21 | 22 | using OrgFn = decltype(game_d3d9ex_present_override)*; 23 | OrgFn org_fn = (OrgFn)game_d3d9ex_state.present_hook.original; 24 | return org_fn(p, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion); 25 | } 26 | 27 | void game_d3d9ex_init() 28 | { 29 | game_d3d9ex_state.device = (IDirect3DDevice9Ex*)game_get_d3d9ex_device(); 30 | game_d3d9ex_state.device->AddRef(); 31 | 32 | // Fixed in ABI and cannot be changed. 33 | GameFnOverride d3d9ex_present_override; 34 | d3d9ex_present_override.target = game_get_virtual(game_get_d3d9ex_device(), 17); 35 | d3d9ex_present_override.override = game_d3d9ex_present_override; 36 | 37 | game_hook_create(&d3d9ex_present_override, &game_d3d9ex_state.present_hook); 38 | } 39 | 40 | void game_d3d9ex_free() 41 | { 42 | game_d3d9ex_state.device->Release(); 43 | game_d3d9ex_state.device = NULL; 44 | 45 | game_hook_remove(&game_d3d9ex_state.present_hook); 46 | } 47 | 48 | IUnknown* game_d3d9ex_get_game_device() 49 | { 50 | game_d3d9ex_state.device->AddRef(); 51 | return game_d3d9ex_state.device; 52 | } 53 | 54 | IUnknown* game_d3d9ex_get_game_texture() 55 | { 56 | // The game backbuffer is the first index. 57 | IDirect3DSurface9* bb_surf = NULL; 58 | game_d3d9ex_state.device->GetRenderTarget(0, &bb_surf); 59 | 60 | return bb_surf; 61 | } 62 | 63 | GameVideoDesc game_d3d9ex_desc = 64 | { 65 | .name = "D3D9Ex", 66 | .init = game_d3d9ex_init, 67 | .free = game_d3d9ex_free, 68 | .get_game_device = game_d3d9ex_get_game_device, 69 | .get_game_texture = game_d3d9ex_get_game_texture, 70 | }; 71 | -------------------------------------------------------------------------------- /src/svr_standalone/game_hook.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | void game_hook_init() 4 | { 5 | MH_Initialize(); 6 | } 7 | 8 | void game_hook_create(GameFnOverride* ov, GameFnHook* dest) 9 | { 10 | // No point handling errors because we cannot validate the result in any way. 11 | 12 | dest->target = ov->target; 13 | dest->override = ov->override; 14 | 15 | MH_CreateHook(ov->target, ov->override, &dest->original); 16 | } 17 | 18 | void game_hook_remove(GameFnHook* h) 19 | { 20 | MH_RemoveHook(h->target); 21 | } 22 | 23 | void game_hook_enable(GameFnHook* h, bool v) 24 | { 25 | assert(h->target); 26 | 27 | if (v) 28 | { 29 | MH_EnableHook(h->target); 30 | } 31 | 32 | else 33 | { 34 | MH_DisableHook(h->target); 35 | } 36 | } 37 | 38 | void game_hook_enable_all() 39 | { 40 | MH_EnableHook(MH_ALL_HOOKS); 41 | } 42 | -------------------------------------------------------------------------------- /src/svr_standalone/game_init.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | GameState game_state; 4 | 5 | DWORD CALLBACK game_init_async_thread_proc(LPVOID param) 6 | { 7 | game_init_async_proc(); 8 | return 0; // Not used. 9 | } 10 | 11 | void game_init(SvrGameInitData* init_data) 12 | { 13 | game_state.main_thread_id = GetCurrentThreadId(); 14 | game_state.svr_path = init_data->svr_path; 15 | 16 | game_wind_early_init(); 17 | 18 | // Init needs to be done async because we need to wait for the libraries to load while the game loads as normal. 19 | HANDLE h = CreateThread(NULL, 0, game_init_async_thread_proc, NULL, 0, NULL); 20 | CloseHandle(h); 21 | } 22 | 23 | void game_init_log() 24 | { 25 | char log_file_path[MAX_PATH]; 26 | SVR_SNPRINTF(log_file_path, "%s\\data\\SVR_LOG.txt", game_state.svr_path); 27 | 28 | // Append to the log file the launcher created. 29 | svr_init_log(log_file_path, true); 30 | 31 | // Need to notify that we have started because a lot of things can go wrong in standalone launch. 32 | svr_log("Hello from the game\n"); 33 | } 34 | 35 | void game_init_error(const char* format, ...) 36 | { 37 | assert(GetCurrentThreadId() != game_state.main_thread_id); // Only to be used in the init thread. 38 | 39 | char message[1024]; 40 | 41 | va_list va; 42 | va_start(va, format); 43 | SVR_VSNPRINTF(message, format, va); 44 | va_end(va); 45 | 46 | svr_log("!!! ERROR: %s\n", message); 47 | 48 | // Try and hide the game window if we have it. 49 | // This needs to be done because we are in a separate thread here, and we want the main thread to block as well. 50 | // Since we cannot add new custom messages and adjust the game window message loop, this is another way to prevent that. 51 | // We don't want the main window to be interacted with when this dialog is opened. 52 | 53 | HWND hwnd = NULL; 54 | EnumThreadWindows(game_state.main_thread_id, game_wind_enum_first_hwnd, (LPARAM)&hwnd); 55 | 56 | if (hwnd) 57 | { 58 | EnableWindow(hwnd, FALSE); 59 | ShowWindow(hwnd, SW_HIDE); 60 | } 61 | 62 | MessageBoxA(NULL, message, "SVR", MB_ICONERROR | MB_OK | MB_TASKMODAL); 63 | 64 | ExitProcess(1); 65 | } 66 | 67 | // In init thread. 68 | void game_init_async_proc() 69 | { 70 | game_init_log(); 71 | 72 | game_search_wait_for_libs(); 73 | 74 | svr_console_init(); 75 | game_hook_init(); 76 | svr_prof_init(); 77 | 78 | game_search_fill_desc(&game_state.search_desc); 79 | 80 | game_init_check_modules(); 81 | game_overrides_init(); 82 | game_video_init(); 83 | game_audio_init(); 84 | game_wind_init(); 85 | game_rec_init(); 86 | 87 | game_hook_enable_all(); 88 | 89 | IUnknown* video_device = game_state.video_desc->get_game_device(); 90 | 91 | if (!svr_init(game_state.svr_path, video_device)) 92 | { 93 | game_init_error("Could not initialize SVR. Ensure you are using the latest version of SVR and upload your SVR_LOG.txt."); 94 | } 95 | 96 | // It's useful to show that we have loaded when in standalone mode. 97 | // This message may not be the latest message but at least it's in there. 98 | 99 | svr_console_msg("-------------------------------------------------------\n"); 100 | svr_console_msg("SVR initialized\n"); 101 | svr_console_msg("-------------------------------------------------------\n"); 102 | } 103 | 104 | struct GameCapsPrint 105 | { 106 | GameCaps cap; 107 | const char* name; 108 | }; 109 | 110 | void game_init_check_modules() 111 | { 112 | #define CAP(X) GameCapsPrint { X, #X } 113 | 114 | GameCapsPrint caps_print[] = 115 | { 116 | CAP(GAME_CAP_HAS_CORE), 117 | CAP(GAME_CAP_HAS_VELO), 118 | CAP(GAME_CAP_HAS_AUDIO), 119 | CAP(GAME_CAP_HAS_VIDEO), 120 | CAP(GAME_CAP_HAS_AUTOSTOP), 121 | CAP(GAME_CAP_D3D9EX_VIDEO), 122 | CAP(GAME_CAP_AUDIO_DEVICE_1), 123 | CAP(GAME_CAP_AUDIO_DEVICE_1_5), 124 | CAP(GAME_CAP_AUDIO_DEVICE_2), 125 | CAP(GAME_CAP_64_BIT_AUDIO_TIME), 126 | CAP(GAME_CAP_VELO_1), 127 | CAP(GAME_CAP_VELO_2), 128 | }; 129 | 130 | svr_log("Game caps:\n"); 131 | 132 | for (s32 i = 0; i < SVR_ARRAY_SIZE(caps_print); i++) 133 | { 134 | GameCapsPrint* p = &caps_print[i]; 135 | 136 | if (game_state.search_desc.caps & p->cap) 137 | { 138 | svr_log("- %s\n", p->name); 139 | } 140 | } 141 | 142 | bool required_caps[] = 143 | { 144 | game_state.search_desc.caps & GAME_CAP_HAS_CORE, 145 | game_state.search_desc.caps & GAME_CAP_HAS_VIDEO, 146 | }; 147 | 148 | if (!svr_check_all_true(required_caps, SVR_ARRAY_SIZE(required_caps))) 149 | { 150 | game_init_error("SVR support is missing or wrong. Ensure you are using the latest version of SVR and upload your SVR_LOG.txt."); 151 | } 152 | 153 | #undef CAP 154 | } 155 | 156 | // Called when launching by the standalone launcher. This is before the process has started, and there are no game libraries loaded here. 157 | extern "C" __declspec(dllexport) void svr_init_from_launcher(SvrGameInitData* init_data) 158 | { 159 | game_init(init_data); 160 | } 161 | -------------------------------------------------------------------------------- /src/svr_standalone/game_libs.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | // Library waiting. 4 | 5 | s32 game_check_loaded_proc_modules(const char** list, s32 size) 6 | { 7 | s32 hits = 0; 8 | 9 | // See if all of the requested modules are loaded. 10 | 11 | for (s32 i = 0; i < size; i++) 12 | { 13 | // This function does not care about character case. 14 | HMODULE module = GetModuleHandleA(list[i]); // Does not increment the reference count of the module. 15 | 16 | if (module) 17 | { 18 | hits++; 19 | } 20 | } 21 | 22 | return hits; 23 | } 24 | 25 | bool game_wait_for_libs_to_load(const char** libs, s32 num, s32 timeout) 26 | { 27 | // Alternate method instead of hooking the LoadLibrary family of functions. 28 | // We don't need accuracy so this is good enough and much simpler. 29 | 30 | s32 num_loops = timeout * 10; 31 | 32 | for (s32 i = 0; i < num_loops; i++) 33 | { 34 | if (game_check_loaded_proc_modules(libs, num) == num) 35 | { 36 | return true; 37 | } 38 | 39 | Sleep(100); 40 | } 41 | 42 | return false; 43 | } 44 | -------------------------------------------------------------------------------- /src/svr_standalone/game_overrides.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | // Convert sample format and send. 4 | void game_prepare_and_send_sound_0(GameSndSample0* paint_buf, s32 num_samples) 5 | { 6 | if (!svr_is_audio_enabled()) 7 | { 8 | return; 9 | } 10 | 11 | SvrWaveSample* buf = (SvrWaveSample*)_alloca(sizeof(SvrWaveSample) * num_samples); 12 | 13 | for (s32 i = 0; i < num_samples; i++) 14 | { 15 | GameSndSample0* sample = &paint_buf[i]; 16 | buf[i] = SvrWaveSample { (s16)sample->left, (s16)sample->right }; 17 | } 18 | 19 | svr_give_audio(buf, num_samples); 20 | } 21 | 22 | // ---------------------------------------------------------------- 23 | 24 | void __cdecl game_snd_tx_stereo_override_0(void* unk, GameSndSample0* paint_buf, s32 paint_time, s32 end_time) 25 | { 26 | if (!svr_movie_active()) 27 | { 28 | return; 29 | } 30 | 31 | assert(game_state.snd_is_painting); 32 | 33 | s32 num_samples = end_time - paint_time; 34 | game_prepare_and_send_sound_0(paint_buf, num_samples); 35 | } 36 | 37 | // ---------------------------------------------------------------- 38 | 39 | void __fastcall game_snd_device_tx_samples_override_0(void* p, void* edx, u32 unused) 40 | { 41 | if (!svr_movie_active()) 42 | { 43 | // For this override, we must call the original in case we are not recording. 44 | // Otherwise there will only be sound when recording. 45 | 46 | using OrgFn = decltype(game_snd_device_tx_samples_override_0)*; 47 | OrgFn org_fn = (OrgFn)game_state.snd_device_tx_samples_hook.original; 48 | org_fn(p, edx, unused); 49 | return; 50 | } 51 | 52 | assert(game_state.snd_is_painting); 53 | assert(game_state.snd_num_samples); 54 | 55 | GameSndSample0* paint_buf = game_get_snd_paint_buffer_0(); 56 | game_prepare_and_send_sound_0(paint_buf, game_state.snd_num_samples); 57 | } 58 | 59 | // ---------------------------------------------------------------- 60 | 61 | void __cdecl game_snd_paint_chans_override_0(s32 end_time, bool is_underwater) 62 | { 63 | game_state.snd_listener_underwater = is_underwater; 64 | 65 | if (svr_movie_active() && !game_state.snd_is_painting) 66 | { 67 | return; // When movie is active we call this ourselves with the real number of samples write. 68 | } 69 | 70 | // Will call game_snd_tx_stereo_override_0 or game_snd_device_tx_samples_override_0. 71 | 72 | using OrgFn = decltype(game_snd_paint_chans_override_0)*; 73 | OrgFn org_fn = (OrgFn)game_state.snd_paint_chans_hook.original; 74 | org_fn(end_time, is_underwater); 75 | } 76 | 77 | void __cdecl game_snd_paint_chans_override_1(s64 end_time, bool is_underwater) 78 | { 79 | game_state.snd_listener_underwater = is_underwater; 80 | 81 | if (svr_movie_active() && !game_state.snd_is_painting) 82 | { 83 | return; // When movie is active we call this ourselves with the real number of samples write. 84 | } 85 | 86 | // Will call game_snd_tx_stereo_override_0 or game_snd_device_tx_samples_override_0. 87 | 88 | using OrgFn = decltype(game_snd_paint_chans_override_1)*; 89 | OrgFn org_fn = (OrgFn)game_state.snd_paint_chans_hook.original; 90 | org_fn(end_time, is_underwater); 91 | } 92 | 93 | // ---------------------------------------------------------------- 94 | 95 | void __cdecl game_start_movie_override_0(void* cmd_args) 96 | { 97 | game_rec_start_movie(cmd_args); 98 | } 99 | 100 | // ---------------------------------------------------------------- 101 | 102 | void __cdecl game_end_movie_override_0(void* cmd_args) 103 | { 104 | game_rec_end_movie(); 105 | } 106 | 107 | // ---------------------------------------------------------------- 108 | 109 | bool __fastcall game_eng_filter_time_override_0(void* p, void* edx, float dt) 110 | { 111 | bool ret = game_rec_run_frame(); 112 | 113 | if (!ret) 114 | { 115 | using OrgFn = decltype(game_eng_filter_time_override_0)*; 116 | OrgFn org_fn = (OrgFn)game_state.filter_time_hook.original; 117 | ret = org_fn(p, edx, dt); 118 | } 119 | 120 | return ret; 121 | } 122 | 123 | #ifndef _WIN64 124 | bool __fastcall game_eng_filter_time_override_1(void* p, void* edx) 125 | { 126 | float dt; 127 | 128 | __asm movss dt, xmm1; 129 | 130 | bool ret = game_rec_run_frame(); 131 | 132 | if (!ret) 133 | { 134 | __asm movss xmm1, dt; 135 | 136 | using OrgFn = decltype(game_eng_filter_time_override_1)*; 137 | OrgFn org_fn = (OrgFn)game_state.filter_time_hook.original; 138 | ret = org_fn(p, edx); 139 | } 140 | 141 | return ret; 142 | } 143 | #endif 144 | 145 | // ---------------------------------------------------------------- 146 | 147 | void game_overrides_init() 148 | { 149 | game_hook_create(&game_state.search_desc.start_movie_override, &game_state.start_movie_hook); 150 | game_hook_create(&game_state.search_desc.end_movie_override, &game_state.end_movie_hook); 151 | game_hook_create(&game_state.search_desc.filter_time_override, &game_state.filter_time_hook); 152 | 153 | if (game_state.search_desc.caps & GAME_CAP_HAS_AUDIO) 154 | { 155 | game_hook_create(&game_state.search_desc.snd_paint_chans_override, &game_state.snd_paint_chans_hook); 156 | } 157 | 158 | if (game_state.search_desc.caps & GAME_CAP_AUDIO_DEVICE_1) 159 | { 160 | game_hook_create(&game_state.search_desc.snd_tx_stereo_override, &game_state.snd_tx_stereo_hook); 161 | } 162 | 163 | if (game_state.search_desc.caps & GAME_CAP_AUDIO_DEVICE_1_5 | GAME_CAP_AUDIO_DEVICE_2) 164 | { 165 | game_hook_create(&game_state.search_desc.snd_device_tx_samples_override, &game_state.snd_device_tx_samples_hook); 166 | } 167 | 168 | // If the game has a restriction that prevents cvars from changing when in game or demo playback. 169 | // This replaces the flags to compare to, so the comparison will always be false. 170 | s32 cvar_restrict_patch_bytes = 0x00; 171 | game_apply_patch(game_get_cvar_patch_restrict(), &cvar_restrict_patch_bytes, sizeof(cvar_restrict_patch_bytes)); 172 | } 173 | -------------------------------------------------------------------------------- /src/svr_standalone/game_priv.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "svr_common.h" 3 | #include "svr_api.h" 4 | #include "svr_defs.h" 5 | #include "svr_standalone_common.h" 6 | #include "svr_log.h" 7 | #include "svr_array.h" 8 | #include "svr_ini.h" 9 | #include "svr_alloc.h" 10 | #include "svr_console.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "svr_prof.h" 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "game_common.h" 23 | -------------------------------------------------------------------------------- /src/svr_standalone/game_scan.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | // Memory scanning. 4 | 5 | // How many bytes there can be in a pattern scan. 6 | const s32 GAME_MAX_SCAN_BYTES = 256; 7 | 8 | struct GameScanPattern 9 | { 10 | // A value of -1 means unknown byte. 11 | s16 bytes[GAME_MAX_SCAN_BYTES]; 12 | s16 used; 13 | }; 14 | 15 | bool game_is_hex_char(char c) 16 | { 17 | return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'); 18 | } 19 | 20 | void game_pattern_bytes_from_string(const char* input, GameScanPattern* out) 21 | { 22 | const char* ptr = input; 23 | 24 | out->used = 0; 25 | 26 | for (; *ptr != 0; ptr++) 27 | { 28 | assert(out->used < GAME_MAX_SCAN_BYTES); 29 | 30 | if (game_is_hex_char(*ptr)) 31 | { 32 | assert(game_is_hex_char(*(ptr + 1))); // Next must be the next 4 bits. 33 | 34 | out->bytes[out->used] = strtol(ptr, NULL, 16); 35 | out->used++; 36 | ptr++; 37 | } 38 | 39 | else if (*ptr == '?') 40 | { 41 | assert(*(ptr + 1) == '?'); // Next must be question mark. 42 | 43 | out->bytes[out->used] = -1; 44 | out->used++; 45 | ptr++; 46 | } 47 | } 48 | 49 | assert(out->used > 0); // Must have written something. 50 | } 51 | 52 | bool game_compare_data(u8* data, GameScanPattern* pattern) 53 | { 54 | s32 index = 0; 55 | s16* bytes = pattern->bytes; 56 | 57 | for (s32 i = 0; i < pattern->used; i++) 58 | { 59 | s16 byte = *bytes; 60 | 61 | if (byte > -1 && *data != byte) 62 | { 63 | return false; 64 | } 65 | 66 | data++; 67 | bytes++; 68 | index++; 69 | } 70 | 71 | return index == pattern->used; 72 | } 73 | 74 | void* game_find_pattern(void* start, s32 search_length, GameScanPattern* pattern) 75 | { 76 | s16 length = pattern->used; 77 | 78 | for (s32 i = 0; i <= search_length - length; i++) 79 | { 80 | u8* addr = (u8*)start + i; 81 | 82 | if (game_compare_data(addr, pattern)) 83 | { 84 | return addr; 85 | } 86 | } 87 | 88 | return NULL; 89 | } 90 | 91 | void* game_scan_pattern(const char* dll, const char* pattern, void* from) 92 | { 93 | MODULEINFO info; 94 | 95 | if (!GetModuleInformation(GetCurrentProcess(), GetModuleHandleA(dll), &info, sizeof(MODULEINFO))) 96 | { 97 | // Module is not loaded. Not an error because we allow fallthrough scanning of multiple patterns. 98 | return NULL; 99 | } 100 | 101 | GameScanPattern pattern_bytes = {}; 102 | game_pattern_bytes_from_string(pattern, &pattern_bytes); 103 | 104 | if (from == NULL) 105 | { 106 | from = info.lpBaseOfDll; 107 | } 108 | 109 | else 110 | { 111 | // Start address must be in range of the module. 112 | assert(((u8*)from >= info.lpBaseOfDll) && (u8*)from < ((u8*)info.lpBaseOfDll + info.SizeOfImage)); 113 | } 114 | 115 | void* ret = game_find_pattern(from, info.SizeOfImage, &pattern_bytes); 116 | return ret; 117 | } 118 | -------------------------------------------------------------------------------- /src/svr_standalone/game_util.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | void* game_create_interface(const char* dll, const char* name) 4 | { 5 | using CreateInterfaceFn = void*(__cdecl*)(const char* name, s32* code); 6 | CreateInterfaceFn fn = (CreateInterfaceFn)game_get_export(dll, "CreateInterface"); 7 | 8 | s32 code; 9 | return fn(name, &code); 10 | } 11 | 12 | void* game_get_virtual(void* ptr, s32 idx) 13 | { 14 | if (ptr == NULL) 15 | { 16 | return NULL; 17 | } 18 | 19 | void** vtable = *((void***)ptr); 20 | return vtable[idx]; 21 | } 22 | 23 | void* game_get_export(const char* dll, const char* name) 24 | { 25 | HMODULE module = GetModuleHandleA(dll); 26 | assert(module); 27 | 28 | return GetProcAddress(module, name); 29 | } 30 | 31 | void game_apply_patch(void* target, void* bytes, s32 num_bytes) 32 | { 33 | DWORD old_protect; 34 | VirtualProtect(target, num_bytes, PAGE_EXECUTE_READWRITE, &old_protect); // Make page writable. 35 | memcpy(target, bytes, num_bytes); 36 | VirtualProtect(target, num_bytes, old_protect, NULL); // Restore page. 37 | } 38 | 39 | bool game_is_valid(GameFnOverride ov) 40 | { 41 | return ov.target; 42 | } 43 | 44 | bool game_is_valid(GameFnProxy px) 45 | { 46 | return px.target; 47 | } 48 | 49 | void* game_follow_displacement(void* from, s32 length) 50 | { 51 | u8* addr = (u8*)from; 52 | 53 | s32 offset = *(s32*)addr; 54 | addr += offset; 55 | addr += length; 56 | 57 | return addr; 58 | } 59 | -------------------------------------------------------------------------------- /src/svr_standalone/game_velo.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | void game_velo_frame() 4 | { 5 | if (!svr_is_velo_enabled()) 6 | { 7 | return; 8 | } 9 | 10 | void* player = game_velo_get_active_player(); 11 | 12 | if (player) 13 | { 14 | SvrVec3 vel = game_get_entity_velocity(player); 15 | svr_give_velocity((float*)&vel); 16 | } 17 | } 18 | 19 | void* game_velo_get_active_player() 20 | { 21 | if (game_state.search_desc.caps & GAME_CAP_VELO_1) 22 | { 23 | return game_velo_get_active_player_dumb(); 24 | } 25 | 26 | if (game_state.search_desc.caps & GAME_CAP_VELO_2) 27 | { 28 | return game_velo_get_active_player_smart(); 29 | } 30 | 31 | return NULL; 32 | } 33 | 34 | // Return local player or spectated player (for mp games). 35 | void* game_velo_get_active_player_dumb() 36 | { 37 | // There is a bug here if you go from a demo with bots to a local game with bots, 38 | // where the sticky indexes are still valid so we end up not reading from the local player. 39 | // Not sure how to reset the spec target easily without introducing yet more patterns. 40 | 41 | void* player = NULL; 42 | 43 | s32 spec = game_get_spec_target(); 44 | 45 | if (spec > 0) 46 | { 47 | player = game_get_player_by_index(spec); 48 | } 49 | 50 | // It's possible to be spectating someone without that player having any entity. 51 | if (player == NULL) 52 | { 53 | player = game_get_local_player(); 54 | } 55 | 56 | return player; 57 | } 58 | 59 | void* game_velo_get_active_player_smart() 60 | { 61 | return game_get_spec_target_or_local_player(); 62 | } 63 | -------------------------------------------------------------------------------- /src/svr_standalone/game_video.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | struct GameVideoSearch 4 | { 5 | GameCaps caps; 6 | GameVideoDesc* desc; 7 | }; 8 | 9 | GameVideoSearch GAME_VIDEO_BACKENDS[] = 10 | { 11 | GameVideoSearch { GAME_CAP_D3D9EX_VIDEO, &game_d3d9ex_desc } 12 | }; 13 | 14 | // Find the right video backend from the search description. 15 | void game_video_init() 16 | { 17 | GameVideoDesc* best_desc = NULL; 18 | s32 best_match = 0; 19 | 20 | for (s32 i = 0; i < SVR_ARRAY_SIZE(GAME_VIDEO_BACKENDS); i++) 21 | { 22 | GameVideoSearch* s = &GAME_VIDEO_BACKENDS[i]; 23 | 24 | s32 num_match = svr_count_set_bits(game_state.search_desc.caps & s->caps); 25 | 26 | if (num_match > best_match) 27 | { 28 | best_desc = s->desc; 29 | best_match = num_match; 30 | } 31 | } 32 | 33 | game_state.video_desc = best_desc; 34 | 35 | if (game_state.video_desc == NULL) 36 | { 37 | game_init_error("No video backend available."); 38 | } 39 | 40 | svr_log("Using game video backend %s\n", game_state.video_desc->name); 41 | 42 | game_state.video_desc->init(); 43 | } 44 | 45 | void game_video_free() 46 | { 47 | if (game_state.video_desc) 48 | { 49 | game_state.video_desc->free(); 50 | game_state.video_desc = NULL; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/svr_standalone/game_wind.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | 3 | const s32 GAME_WIND_TITLE_SIZE = 512; 4 | 5 | BOOL CALLBACK game_wind_enum_first_hwnd(HWND hwnd, LPARAM lparam) 6 | { 7 | HWND* out_hwnd = (HWND*)lparam; 8 | *out_hwnd = hwnd; 9 | return FALSE; // Just take the first one. 10 | } 11 | 12 | void game_wind_early_init() 13 | { 14 | // Used by the window progress bar. 15 | // Must be called in the main thread because it will be used in the main thread. 16 | 17 | CoInitializeEx(NULL, COINIT_MULTITHREADED); 18 | CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&game_state.wind_taskbar_list)); 19 | } 20 | 21 | void game_wind_init() 22 | { 23 | game_state.wind_def_title = (char*)svr_zalloc(sizeof(char) * GAME_WIND_TITLE_SIZE); 24 | 25 | // Find the main window. We could probably scan for this too. 26 | EnumThreadWindows(game_state.main_thread_id, game_wind_enum_first_hwnd, (LPARAM)&game_state.wind_hwnd); 27 | 28 | GetWindowTextA(game_state.wind_hwnd, game_state.wind_def_title, GAME_WIND_TITLE_SIZE); 29 | 30 | game_wind_reset(); 31 | } 32 | 33 | void game_wind_free() 34 | { 35 | if (game_state.wind_def_title) 36 | { 37 | svr_free(game_state.wind_def_title); 38 | game_state.wind_def_title = NULL; 39 | } 40 | 41 | if (game_state.wind_taskbar_list) 42 | { 43 | svr_release(game_state.wind_taskbar_list); 44 | game_state.wind_taskbar_list = NULL; 45 | } 46 | 47 | game_state.wind_hwnd = NULL; 48 | } 49 | 50 | // Update the window title to display the rendered video time, and elapsed real time. 51 | void game_wind_update_title(s64 now) 52 | { 53 | // Transform number of frames in a unit of frames per second into an elapsed period in microseconds. 54 | // This is the video time. 55 | SvrSplitTime video_split = svr_split_time(svr_rescale(game_state.rec_num_frames, 1000000, game_state.rec_game_rate)); 56 | 57 | // This is the real elapsed time. 58 | SvrSplitTime real_split = svr_split_time(now - game_state.rec_start_time); 59 | 60 | char buf[1024]; 61 | SVR_SNPRINTF(buf, "%02d:%02d.%03d (%02d:%02d:%02d)", video_split.minutes, video_split.seconds, video_split.millis, real_split.hours, real_split.minutes, real_split.seconds); 62 | 63 | SetWindowTextA(game_state.wind_hwnd, buf); 64 | } 65 | 66 | // Update the taskbar progress bar for region rendering. 67 | void game_wind_update_progress(s64 now) 68 | { 69 | if (game_state.rec_timeout == 0) 70 | { 71 | return; // Should not stop automatically, and no progress to update. 72 | } 73 | 74 | s64 end_frame = game_state.rec_timeout * game_state.rec_game_rate; 75 | 76 | game_state.wind_taskbar_list->SetProgressValue(game_state.wind_hwnd, game_state.rec_num_frames, end_frame); 77 | } 78 | 79 | void game_wind_update() 80 | { 81 | s64 now = svr_prof_get_real_time(); 82 | 83 | if (now < game_state.wind_next_update_time) 84 | { 85 | return; 86 | } 87 | 88 | // Need to throttle this because updating the window is slow apparently. 89 | game_state.wind_next_update_time = now + 500000; 90 | 91 | game_wind_update_title(now); 92 | game_wind_update_progress(now); 93 | } 94 | 95 | // Restore window title and progress bar. 96 | void game_wind_reset() 97 | { 98 | game_state.wind_next_update_time = 0; 99 | 100 | SetWindowTextA(game_state.wind_hwnd, game_state.wind_def_title); 101 | 102 | game_state.wind_taskbar_list->SetProgressValue(game_state.wind_hwnd, 0, 0); 103 | game_state.wind_taskbar_list->SetProgressState(game_state.wind_hwnd, TBPF_NOPROGRESS); 104 | } 105 | -------------------------------------------------------------------------------- /src/svr_standalone/unity_standalone.cpp: -------------------------------------------------------------------------------- 1 | #include "game_priv.h" 2 | #include "game_search.cpp" 3 | #include "game_overrides.cpp" 4 | #include "game_util.cpp" 5 | #include "game_libs.cpp" 6 | #include "game_scan.cpp" 7 | #include "game_hook.cpp" 8 | #include "game_wind.cpp" 9 | #include "game_rec.cpp" 10 | #include "game_cfg.cpp" 11 | #include "game_proxies.cpp" 12 | #include "game_init.cpp" 13 | #include "game_video.cpp" 14 | #include "game_d3d9ex.cpp" 15 | #include "game_audio.cpp" 16 | #include "game_audio_v1.cpp" 17 | #include "game_audio_v2.cpp" 18 | #include "game_velo.cpp" 19 | -------------------------------------------------------------------------------- /svr.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31410.414 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svr_encoder", "src\svr_encoder\svr_encoder.vcxproj", "{C31C1B41-95E7-45C6-A16F-F86A81F13D9F}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svr_game", "src\svr_game\svr_game.vcxproj", "{0B116E75-C1CC-409B-A50A-274DC2D4CB41}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svr_launcher", "src\svr_launcher\svr_launcher.vcxproj", "{E750167E-861F-4CF4-9F5D-20F473129641}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svr_standalone", "src\svr_standalone\svr_standalone.vcxproj", "{4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svr_common", "src\svr_common\svr_common.vcxproj", "{DF7F2790-4886-4224-AFC1-B44687571612}" 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "svr_shared", "src\svr_shared\svr_shared.vcxproj", "{0DA14111-6BA2-4670-A183-EE7BED08B7E9}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|x64 = Debug|x64 21 | Debug|x86 = Debug|x86 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Debug|x64.ActiveCfg = Debug|x64 27 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Debug|x64.Build.0 = Debug|x64 28 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Debug|x86.ActiveCfg = Debug|x64 29 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Debug|x86.Build.0 = Debug|x64 30 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Release|x64.ActiveCfg = Release|x64 31 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Release|x64.Build.0 = Release|x64 32 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Release|x86.ActiveCfg = Release|x64 33 | {C31C1B41-95E7-45C6-A16F-F86A81F13D9F}.Release|x86.Build.0 = Release|x64 34 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Debug|x64.ActiveCfg = Debug|x64 35 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Debug|x64.Build.0 = Debug|x64 36 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Debug|x86.ActiveCfg = Debug|Win32 37 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Debug|x86.Build.0 = Debug|Win32 38 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Release|x64.ActiveCfg = Release|x64 39 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Release|x64.Build.0 = Release|x64 40 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Release|x86.ActiveCfg = Release|Win32 41 | {0B116E75-C1CC-409B-A50A-274DC2D4CB41}.Release|x86.Build.0 = Release|Win32 42 | {E750167E-861F-4CF4-9F5D-20F473129641}.Debug|x64.ActiveCfg = Debug|x64 43 | {E750167E-861F-4CF4-9F5D-20F473129641}.Debug|x64.Build.0 = Debug|x64 44 | {E750167E-861F-4CF4-9F5D-20F473129641}.Debug|x86.ActiveCfg = Debug|Win32 45 | {E750167E-861F-4CF4-9F5D-20F473129641}.Debug|x86.Build.0 = Debug|Win32 46 | {E750167E-861F-4CF4-9F5D-20F473129641}.Release|x64.ActiveCfg = Release|x64 47 | {E750167E-861F-4CF4-9F5D-20F473129641}.Release|x64.Build.0 = Release|x64 48 | {E750167E-861F-4CF4-9F5D-20F473129641}.Release|x86.ActiveCfg = Release|Win32 49 | {E750167E-861F-4CF4-9F5D-20F473129641}.Release|x86.Build.0 = Release|Win32 50 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Debug|x64.ActiveCfg = Debug|x64 51 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Debug|x64.Build.0 = Debug|x64 52 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Debug|x86.ActiveCfg = Debug|Win32 53 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Debug|x86.Build.0 = Debug|Win32 54 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Release|x64.ActiveCfg = Release|x64 55 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Release|x64.Build.0 = Release|x64 56 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Release|x86.ActiveCfg = Release|Win32 57 | {4DE1E027-CAF8-4106-ABEF-1A6885CC82DD}.Release|x86.Build.0 = Release|Win32 58 | {DF7F2790-4886-4224-AFC1-B44687571612}.Debug|x64.ActiveCfg = Debug|x64 59 | {DF7F2790-4886-4224-AFC1-B44687571612}.Debug|x64.Build.0 = Debug|x64 60 | {DF7F2790-4886-4224-AFC1-B44687571612}.Debug|x86.ActiveCfg = Debug|Win32 61 | {DF7F2790-4886-4224-AFC1-B44687571612}.Debug|x86.Build.0 = Debug|Win32 62 | {DF7F2790-4886-4224-AFC1-B44687571612}.Release|x64.ActiveCfg = Release|x64 63 | {DF7F2790-4886-4224-AFC1-B44687571612}.Release|x64.Build.0 = Release|x64 64 | {DF7F2790-4886-4224-AFC1-B44687571612}.Release|x86.ActiveCfg = Release|Win32 65 | {DF7F2790-4886-4224-AFC1-B44687571612}.Release|x86.Build.0 = Release|Win32 66 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Debug|x64.ActiveCfg = Debug|x64 67 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Debug|x64.Build.0 = Debug|x64 68 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Debug|x86.ActiveCfg = Debug|Win32 69 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Debug|x86.Build.0 = Debug|Win32 70 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Release|x64.ActiveCfg = Release|x64 71 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Release|x64.Build.0 = Release|x64 72 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Release|x86.ActiveCfg = Release|Win32 73 | {0DA14111-6BA2-4670-A183-EE7BED08B7E9}.Release|x86.Build.0 = Release|Win32 74 | EndGlobalSection 75 | GlobalSection(SolutionProperties) = preSolution 76 | HideSolutionNode = FALSE 77 | EndGlobalSection 78 | GlobalSection(ExtensibilityGlobals) = postSolution 79 | SolutionGuid = {A259E5C4-19A4-456A-A266-E6F6E481A317} 80 | EndGlobalSection 81 | EndGlobal 82 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | * Somehow use apple coreaudio aac encoder. 2 | -------------------------------------------------------------------------------- /update.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | where /Q curl || ( 4 | echo Updating only supported on Windows 10 and later 5 | pause 6 | exit /b 7 | ) 8 | 9 | echo Downloading latest SVR 10 | 11 | REM This is a special GitHub link that points to the latest release. We always name our releases the same. 12 | curl -# -O -L https://github.com/crashfort/SourceDemoRender/releases/latest/download/svr.zip 13 | 14 | if errorlevel 1 ( 15 | echo There was some error trying to download 16 | pause 17 | exit /b 18 | ) 19 | 20 | echo Latest SVR is downloaded! Extract svr.zip and you're good to go 21 | pause 22 | --------------------------------------------------------------------------------