CS:Source CS起源——源码编译记录

此次编译记录使用Ubuntu 22.04.5 于 2025/01/22

一、克隆源码和准备环境

克隆源码
css源码
git clone --recursive --depth 1 https://github.com/nillerusr/source-engine.git
cm源码
git clone --recursive --depth 1 https://github.com/jeffcwj/clientmod-android.git
–recursive 用于在拉取时初始化和更新子模块,–depth 1 用于仅拉取最后一次提交

准备环境

  1. 先更新一下源 apt update

  2. 使用 dpkg --add-architecture i386 命令将 i386 架构添加到 dpkg

  3. 安装以下包:

sudo apt install -y build-essential gcc-multilib g++-multilib pkg-config ccache
sudo apt install -y libsdl2-dev:i386 libfreetype6-dev:i386 libfontconfig1-dev:i386 libopenal-dev:i386 libjpeg-dev:i386 libpng-dev:i386 libcurl4-gnutls-dev:i386 libbz2-dev:i386 libedit-dev:i386

也可以使用 libcurl4-openssl-devlibcurl4-nss-dev 代替 libcurl4-gnutls-dev

  1. 配置ndk(编译安卓动态库需要)

1)下载 ndk r10e 和 llvm11+clang 组合包

wget https://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-11.1.0/clang+llvm-11.1.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz

2)解压

unzip android-ndk-r10e-linux-x86_64.zip -d ~/android-ndk/
mkdir -p ~/llvm11
tar -xvJf clang+llvm-11.1.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz -C ~/llvm11

3)配置path vim ~/.bashrc 并加入

export ANDROID_NDK_HOME=~/android-ndk/android-ndk-r10e
export PATH=$ANDROID_NDK_HOME:$PATH
export PATH=~/llvm11/bin:$PATH

source ~/.bashrc即可完成

二、构建

  1. configure 配置
    进入 source-engine 源码目录
    编译32位服务端(64位服务端不能安装后续的 metamod, sourcemod 插件平台)
    2025-3-31更新:metamod和sourcemod已支持64位,但有些许问题,如果32位服务端可用,尽量使用32位
    下面展示了老版本WAF(以ClientMod为例) 和新版本WAF(以source-engine的css为例) 的编译指令
  • 配置服务端的configure
    css: python3 ./waf configure -T release -d --32bits --build-games cstrike
    clientmod: python3 ./waf configure -T release -d --build-games=cstrike

  • 配置客户端的configure
    安卓64位ClientMod客户端:
    (clientmod最新版不开源,在github上公开的老版本clientmod源码中,32位的客户端有bug,别浪费时间编译了,现在也基本没有设备不支持64位)
    python3 ./waf configure -T debug --android=aarch64,host,21 --64bits --build-games=cstrike --togles
    安卓64位CSS客户端:
    ./waf configure -T release --android=aarch64,host,21 --64bits --build-games=cstrike --togles --enable-opus --strip

之前如果依赖没装完,缺东西,缺什么补什么

Checking for library bz2                                : not found

我缺这三个

sudo apt install libbz2-dev libbz2-dev:i386
sudo apt install libedit-dev libedit-dev:i386
sudo apt install zlib1g-dev zlib1g-dev:i386

出现'configure' finished successfully (0.702s)表示配置成功

  1. 开始构建
python3 ./waf build -j8

其中-j8的意思是使用8个核心并行编译,速度更快。核心数量看电脑处理器核心而定,可以在任务管理器 - 性能 - CPU中,查看内核数量

三、安装

服务端

  1. 先按照上个帖子下载好起源服务端底包,CM还需要将客户端数据包覆盖起源底包
    CS:MOS 开服教程 - 编程 - 学不会-热爱学习的技术论坛
  2. build文件夹搜.so结尾的所有动态库,将 libserver.so 放入 cstrike/bin 目录下,CM是放入cm/bin目录下。(放入前先删除文件夹内旧的.so文件,同下)
  3. 将剩下的所有动态库 .so 放入 bin 目录下
  4. 搜索 dedicated_server,将这个文件放到服务端根目录
  5. 服务端根目录下删除旧的 srcds_run,创建新的并编辑以下内容
#!/bin/bash

# setup the libraries, local dir first!
export LD_LIBRARY_PATH=".:bin:$LD_LIBRARY_PATH"

# 运行 srcds_run 并传递参数
./dedicated_launcher -insecure -maxplayers 10 -game cm -console -port 27015 -language English +map de_dust2_FPS_Final

记得将地图放入 /cstrike/maps 下,cm 注意修改 -game 参数
9. 执行 ./srcds_run 启动服务端,如果权限不够则 chmod +x srcds_runchmod +x dedicated_launcher 即可

四、安装 MetaMod 和 SourceMod

配置sourcemod指令 python3 ../configure.py --sdks css 到时用到的参考网站也需要在这里应用
ambuild
sm exts load game.cstrike.ext
sm plugins load DM
sm plugins reload DM

2025.03.27 大年初一晚上做的东西全忘了一点不剩,那时候也没记录详细过程

目前继续弄,并记录详细过程

  1. 让MetaMod工作,将这几动态库在其所在文件夹链接一份(注意别被坑了,不能复制+重命名,必须要链接!!!),后面加_srv就行了(libtie0, libvstdlib, libengine, libserver, 其中libserver.so链接为server_srv.so,这个动态库路径和其它so有区别,是[游戏名]/bin中,而不是根目录bin),全过程不需要修改metamod源代码(当前在研究cstrike原版本,等到csmos的时候应该就要改了!)
ln -s libtier0.so libtier0_srv.so
ln -s libvstdlib.so libvstdlib_srv.so
ln -s libengine.so engine_srv.so
ln -s libserver.so server_srv.so

遇到Failed to open bin/dedicated.so (bin/libstdc++.so.6: version `CXXABI_1.3.8' not found (required by bin/libdedicated.so))
是因为libstdc++库太老了,直接删掉bin目录下的libstdc++.so.6,让它读取系统的,或者把系统的来个链接到目录

  1. SourceMod适配,现在开始研究
    研究过程:原版自编译CS起源服务端 → csmos,不打算研究clientmod了,没有好用的源代码,没前途。github上的老版本严重bug是启动巨慢

CS起源原版服务端研究过程

./srcds_run: line 7: 9129 Segmentation fault (core dumped) ./dedicated_launcher -insecure -maxplayers 10 -game cstrike -console -port 27015 -language English +map de_dust2
忘记什么时候编译的了,东西替换完直接卡这里,不知道怎么回事,实际上也从来没有试过自编译的原版CS起源,今天拉最新源码再编译一份吧,会不会libstdc++那玩意真得链接而不是直接移除,然后不要的动态库是不是删掉比较好

在重新编译之前,需要应用一个补丁,允许source-engine运行sourcemod。若不应用,会出现
[META] Failed to load plugin addons/sourcemod/bin/sourcemod_mm: /home/steam/Steam/cs_source/cstrike/addons/sourcemod/bin/sourcemod.2.css.so: undefined symbol: g_VProfCurrentProfile
[META] Loaded 0 plugins.
感谢discord @arumoon在回答1回答2中的解答

diff --git a/public/tier0/vprof.h b/public/tier0/vprof.h
index 4dbdc3cf..5c6ab143 100644
--- a/public/tier0/vprof.h
+++ b/public/tier0/vprof.h
@@ -15,6 +15,10 @@
 #include "tier0/vprof_telemetry.h"
 
 // VProf is enabled by default in all configurations -except- X360 Retail.
+#if !( defined( _X360 ) && defined( _CERT ) )
+#define VPROF_ENABLED
+#endif
+// TODO(nillerusr): make stubbed vprofile
 
 #if defined(_X360) && defined(VPROF_ENABLED)
 #include "tier0/pmc360.h"

由于无法上传patch后缀的文件,我把文件内容直接粘贴到了这里,复制内容以后,将文件保存为vprof.patch
将这个文件放入source-engine目录内,然后执行git apply vprof.patch 即可

还有一个问题,32位的sourcemod请使用1.12版本,2.0版本的会有错误
[META] Failed to load plugin addons/sourcemod/bin/sourcemod_mm: Could not find interface: ServerGameClients005
[META] Loaded 0 plugins.

2025-3-31 重新编译一份以后,将bin目录直接清空后,放入新编译的动态库,cstrike/bin下清空并放入libserver.so,根目录放入dedicated_launcher,从开服教程获取srcds_run的命令,并将-game 的参数改为cstrike,替换地图后,成功运行服务器。

  1. metamod使用原版,按照上文让metamod工作的方法即可
  2. 根据官方构建SourceMod站点,首先克隆sourcemod的源码
mkdir -p alliedmodders
cd alliedmodders
git clone --recursive https://github.com/alliedmodders/sourcemod

# 克隆1.12版本(metamod同理)
git clone --recursive https://github.com/alliedmodders/sourcemod --depth 1 -b 1.12-dev sourcemod
  1. 接下来运行checkout-deps.sh脚本,可以先打开脚本看看,里面有一些参数说明,这里我们指定不拉取MySQL,并且仅拉取cs起源的sdk
./sourcemod/tools/checkout-deps.sh -s css -m -d

如果这个脚本不干活,可以看看下面的手动操作,我就直接复制了,看着改吧

git clone --mirror https://github.com/alliedmodders/hl2sdk hl2sdk-proxy-repo
# For each SDK you want to build with:
# git clone hl2sdk-proxy-repo hl2sdk-<SDK> -b <SDK>
# e.g. git clone hl2sdk-proxy-repo hl2sdk-csgo -b csgo

git clone https://github.com/alliedmodders/metamod-source mmsource-1.10 -b 1.10-dev

# If building version 1.10 of SourceMod, use MySQL 5.5
wget https://cdn.mysql.com/archives/mysql-5.5/mysql-5.5.54-win32.zip mysql-5.5

# If building < version 1.10 of SourceMod, use MySQL 5.0
wget https://cdn.mysql.com/archives/mysql-5.0/mysql-noinstall-5.0.24a-win32.zip mysql-5.0

# Install AMBuild
git clone https://github.com/alliedmodders/ambuild
pip install ./ambuild

我执行这个脚本,如果不跳过mysql就不干活,所以需要手动拉取MySQL

wget https://cdn.mysql.com/archives/mysql-5.7/mysql-5.7.44-linux-glibc2.12-i686.tar.gz
wget https://cdn.mysql.com/archives/mysql-5.7/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz
tar zxf mysql-5.7.44-linux-glibc2.12-i686.tar.gz
tar zxf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz
mv mysql-5.7.44-linux-glibc2.12-i686 mysql-5.7
mv mysql-5.7.44-linux-glibc2.12-x86_64 mysql-5.7-x86_64
rm -rf mysql-5.7.44-linux-glibc2.12-i686.tar.gz mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz
  1. 开始配置
cd sourcemod
mkdir build
cd build
python3 ../configure.py --sdks css --targets x86_64

configure.py的参数,可以打开这个py文件看。下面是一些常用的参数

--enable-debug - 使用符号和调试检查/断言进行编译。
--enable-optimize - 使用优化进行编译。
--no-sse - 禁用浮点优化(如果您的 CPU 非常非常旧)。
--no-mysql - 不要构建 MySQL 数据库模块。
--sdks css - 仅构建 css。
--targets x86_64 构建64位版本
  1. 开始编译,在build文件夹下执行
ambuild

编译完成的文件位于 build 的 package 文件夹中。
直接丢到服务端目录的cstrike文件夹下即可

经过测试,metamod和sourcemod均运行正常

  1. 64位服务端测试
  • 从源码编译64位css服务端
  • 服务端复制一份用于测试,直接使用2.0最新版metamod,sourcemod编译为64位版本进行测试
    我怀疑之前,2.0 32位的问题会复现,还得先解决这个问题
    2.0 metamod也启动不了,一些函数签名不对,改了也不生效
    PLATFORM_CLASS void Lock( const uint32 threadId, unsigned nSpinSleepTime ) volatile;
    nm -D metamod.2.css.so | grep CThreadFastMutex

不研究metamod源码了,直接注入进程
加载动态库 LD_PRELOAD="./libdobby.so ./libnative.so" ./srcds_run

注入不了一点,但是已解决metamod和sourcemod函数签名问题,只需要对着编译完成的动态库执行

sed -i 's/_ZNV16CThreadFastMutex4LockEyj/_ZNV16CThreadFastMutex4LockEjj/g' sourcemod.2.css.so

把有差别的函数签名直接改成和服务端相同,这个应该是起源的一个毛病,暂时没找到解决方法,直接强行改了,对游戏无影响。要是有影响的话,source-engine这边就得炸,毕竟64位的服务端还用的uint32

为了方便后续sourcemod研究,写个自动化从构建到启动服务器的一键脚本。晚点把sourcemod版本换成1.12,metamod2.0也删除重新构建1.12的吧,一键脚本也包含metamod在内

#!/bin/bash

set -e  # 遇到错误立即退出

BASE_DIR="$(dirname "$(realpath "$0")")"
MM_DIR="$BASE_DIR/mmsource-1.12"
SM_DIR="$BASE_DIR/sourcemod"
SERVER_DIR="/home/steam/Steam/cs_source"
CS_TYPE="cstrike"
CS_DIR="$SERVER_DIR/$CS_TYPE"
SERVER_EXEC="$SERVER_DIR/srcds_run"
DELETE_BUILD=false
START_SERVER=false

# 解析参数
while getopts "cd" opt; do
    case $opt in
        c) DELETE_BUILD=true ;;
        d) START_SERVER=true ;;
        *) echo "使用: $0 [-c] [-d]"; exit 1 ;;
    esac
done

build_and_copy() {
    local SRC_DIR="$1"
    local BUILD_DIR="$SRC_DIR/build"
    local PACKAGE_DIR="$BUILD_DIR/package"
    local IS_SOURCEMOD="$2"
    
    # 只有加了 -c 参数才删除 build 目录
    if [ "$DELETE_BUILD" = true ]; then
        rm -rf "$BUILD_DIR"
        mkdir -p "$BUILD_DIR"
    fi
    
    cd "$BUILD_DIR"
    
    # 执行构建
    python3 ../configure.py --sdks css --targets x86_64
    ambuild
    
    # 复制文件
    if [ "$IS_SOURCEMOD" = true ]; then
        rsync -av --mkpath --ignore-existing "$PACKAGE_DIR/addons/sourcemod/configs/" "$CS_DIR/addons/sourcemod/configs/"
        rsync -av --mkpath --ignore-existing "$PACKAGE_DIR/addons/sourcemod/gamedata/" "$CS_DIR/addons/sourcemod/gamedata/"
        rsync -av --mkpath --ignore-existing "$PACKAGE_DIR/addons/sourcemod/scripting/" "$CS_DIR/addons/sourcemod/scripting/"
        rsync -av --mkpath --ignore-existing "$PACKAGE_DIR/addons/sourcemod/plugins/" "$CS_DIR/addons/sourcemod/plugins/"
        rsync -av --mkpath --ignore-existing "$PACKAGE_DIR/addons/sourcemod/translations/" "$CS_DIR/addons/sourcemod/translations/"
    fi
    
    # 复制其余文件(正常覆盖,但排除已处理的 Sourcemod 目录)
    rsync -av --exclude="addons/sourcemod/configs" \
              --exclude="addons/sourcemod/gamedata" \
              --exclude="addons/sourcemod/scripting" \
              --exclude="addons/sourcemod/plugins" \
              --exclude="addons/sourcemod/translations" \
              "$PACKAGE_DIR/" "$CS_DIR/"
}

# 构建并复制 Metamod(不需要特殊处理)
build_and_copy "$MM_DIR" false

# 构建并复制 Sourcemod(包含特殊目录处理)
build_and_copy "$SM_DIR" true

# 替换 metamod.2.css.so
sed -i 's/_ZNV16CThreadFastMutex4LockEyj/_ZNV16CThreadFastMutex4LockEjj/g' "$CS_DIR/addons/metamod/bin/linux64/metamod.2.css.so"

# 替换 sourcemod.2.css.so
sed -i 's/_ZNV16CThreadFastMutex4LockEyj/_ZNV16CThreadFastMutex4LockEjj/g' "$CS_DIR/addons/sourcemod/bin/x64/sourcemod.2.css.so"

echo "构建和替换完成!"

# 如果加了 -d 参数,启动服务器
if [ "$START_SERVER" = true ]; then
    echo "正在启动服务器..."
    cd "$SERVER_DIR"
    "$SERVER_EXEC"
fi

禁止sourcemod检测更新偏移文件
编辑addons/sourcemod/configs/core.cfg

"DisableAutoUpdate"			"yes"

即可禁用更新

加载sourcemod时候,遇到无法识别Interface版本ServerGameClients005,将函数替换为这个即可,原理就是直接把ServerGameClients005替换为ServerGameClients004简单粗暴

/**
 * @brief Macro for automatically getting a current or newer Valve interface.
 *
 * @param v_factory		Factory method to use from ISmmAPI (such as engineFactory).
 * @param v_var			Variable name to store into.
 * @param v_type		Interface type (do not include the pointer/asterisk).
 * @param v_name		Interface name. lmao just change 005 to 004
 */
#define GET_V_IFACE_CURRENT(v_factory, v_var, v_type, v_name) \
	{ \
		const char* actual_v_name = (strcmp(v_name, "ServerGameClients005") == 0) ? "ServerGameClients004" : v_name; \
		v_var = (v_type *)ismm->VInterfaceMatch(ismm->v_factory(), actual_v_name); \
		if (!v_var) \
		{ \
			if (error && maxlen) \
			{ \
				ismm->Format(error, maxlen, "GET_V_IFACE_CURRENT Could not find interface: %s", actual_v_name); \
			} \
			return false; \
		} \
	}

完成以后,metamod和sourcemod均工作正常(两者均使用1.12-dev分支)。你可以查看当时使用的
sourcemod版本metamod版本

2025.4.3 玩家进服崩溃 段错误,和csmos错误一样。csmos甚至无法正确获取pEntity指针,经@FJH_03指出可能是偏移问题。
另外,@FJH_03对本记录有许多重要贡献,在此表示诚挚感谢。

尝试使用sourcemod中的工具获取虚函数位置

CSMOS服务端研究过程

2025.3.31 CSMOS当前仅支持64位服务端,所幸不久前metamod和sourcemod终于支持了css的64位,CSMOS才有可能使用插件平台
之前2025大年初一晚上尝试对ClientMod(32位服务端)进行适配,但是不了了之,后面一直没打开过VMWare了。这次直接尝试CSMOS的64位插件平台适配,希望能有所收获。若太困难,我会重新开始ClientMod 32位服务端的适配。

需要先研究metamod的编译,加入识别csmos(而不是替换),使用本地git管理版本,方便回溯历史更改

2025.4.16 非常感谢CNSR团队FJH03开源的插件平台,现在终于可以在pawn脚本里面实现点东西了!!!
2025.4.17 03开源的插件平台仅适配了windows,经过进一步修改hl2sdk-css(从CSMOS源码移动过去,关键文件请查看 Entity Properties - AlliedModders Wiki)和调整gamedata中的偏移,插件平台的部分sdktool功能已经可用,工作量减少也归功于直接编译的带符号的CSMOS服务端,但还有部分会崩溃,仍待检查。近期研究就到此结束了,以后有时间再继续。

gdb调试小技巧

export LD_LIBRARY_PATH=".:bin:$LD_LIBRARY_PATH"
gdb --args ./dedicated_launcher -insecure -maxplayers 10 -game csmos -console -port 27015 -language English +map de_dust2

然后run运行
bt查看堆栈
break src/net/server.cpp:45 下断点

遇到段错误,将dt_send.cppdt_send.h替换sdk内的文件,直接就干活了
目前玩家进服,服务端又发生段错误,看看什么情况

ubuntu解决/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38’ not found的问题 - umichan - 博客园
一句话总结:添加编译服务端ubuntu版本的apt源,更新apt,安装libc6即可

找虚函数偏移小技巧

(虚函数地址 - 虚表(vtable)地址 - sizeof(void*) * 2) / sizeof(void*)
例子:

// 虚表位置
.data.rel.ro:0000000000278EA8 ; `vtable for'CBaseClient
...
// 目标函数位置
.data.rel.ro:0000000000279078                 dq offset _ZN11CBaseClient7SetNameEPKc ; CBaseClient::SetName(char const*)

服务端是x86_64架构,void*大小为8
则偏移 = (0x279078 - 0x278EA8 - 0x8 * 2) / 0x8 = 0x38
在gamedata中填入的是10进制的偏移,38(16) = 56(10),则填入56

服务端如果去除了符号,就只能靠搜索字符串和有符号的函数特征推断了。如果需要函数签名,则使用windows那种类型的函数签名也行,也就是函数开头的若干位16进制,如\x80\xBF\x8D\x04\x00\x00\x00\x75\x67\x53\x48\x89\xFB\x48\x83\xEC\x10

附官方查虚函数偏移网页工具 VTable Dumper 仅有符号使用

手机连接VMWare中的服务器小技巧

第一种,适用于所有设备都在一个网段下的情况,包括手机给电脑开热点

  1. 虚拟机设置中勾选桥接模式和复制物理网络连接状态
  2. 在虚拟网络编辑器中,选择正在使用的网卡(用WiFi就选无线网卡,用网线就选Ethernet)

    用自家路由器或手机给电脑开热点的话,这时候应该可以上网了,ifconfig看一下ip,若这时手机也连接同一个路由器,处于同一个网段,就可以直接连接虚拟机中的服务器。
    如果使用公共网络,而且DHCP分配出错(通常是公共网络对MAC地址等的限制)的话,可以尝试在Ubuntu设置里面手动设置一下IPv4地址和DNS。如果公网确实无法分配IP地址,或电脑给手机开热点的情况,就用下面备用方法

第二种,适用于所有情况,包括电脑给手机开热点
通用方法:虚拟机网络设置为NAT模式,然后打开虚拟网络编辑器,授权管理员权限,在列表中选中NAT,点击NAT设置按钮,在端口转发列表下,添加端口转发,主机端口为27015,也就是游戏端口,类型选择UDP,虚拟机IP地址为NAT网卡的地址,通过虚拟机内ifconfig指令可以查看,一般是ens33网卡,我的是192.168.20.128。虚拟机端口也设置为27015即可。


用热点的话,windows下ipconfig,可以查看到热点的IP。不知道是哪个也可以打开热点设置,查看已连接的设备。我这里手机IP是192.168.137.79,那么热点的IP一般就是192.168.137.1。那游戏里连接就使用connect 192.168.137.1:27015

虚拟机命令行连接真机的proxy方法,比如v2rayN,一般在真机执行ipconfig,是VMware Network Adapter VMnet8: 192.168.137.1,或者热点的IP,或者直接用当前网络的IP(真机连的WIFI或网线IP,在windows设置里面可以找到),全都试一下,肯定有一个可以,然后配置~/.bashrc 里面添加

export http_proxy="http://192.168.137.1:10809"
export https_proxy="http://192.168.137.1:10809"
export no_proxy="localhost,127.0.0.1,::1"

最后source ~/.bashrc 即可