单机网游转局域网互联网的通用解决方案

原文链接

欢迎大家对于本站的访问 - AsterCasc

前言

最近在玩各种怀旧网游的单机版本,但是基本上都只是单机版本的,如果是局域网版本的,商家是要另外收费的。但是这个东西其实仔细研究一下还是相对比较简单的,咱们这里出一个通用的解决流程,以供小伙伴们使用。而且这样的好处是,你可以购买需要的最便宜的版本,因为那些贵的版本卖的都是服务,其实资源都是一模一样的,无非就是他们自己写几个脚本或者外层应用封装一下,实属没什么东西,建议是直接海鲜平台直接买你想要版本最便宜的那个就行,现在互联网市场卖的九成九都是二道贩子,啥也不知道

互联网就不解释了,能改成局域网就能改成互联网,具体参考本站家用网络下构建外网可访问的内网服务器,所以本文章后续只改到局域网,要想继续改互联网参考该文章即可。此时关于IP的配置就可以直接改成域名了,但是会遇到NAT回流的问题,小伙伴在路由器中设置hosts或者参考本站使用Docker搭建轻量级内网DNS服务器进行解决,当然也可以使用分布式SDN模拟一个内网环境进行处理,这个本文后面会介绍到

实现

首先你需要本地运行一下这个游戏,确认他单机可以运行,不然弄半天,发现他单机都跑不了,那就有点浪费时间了

一般解决方案

这里我们先从基础开始,首先我们拿到资源,有两种情况,第一种需要虚拟机的,第二种不需要虚拟机的。建议选择需要虚拟机的,因为本身你是要改局域网,肯定有设备没有和服务端在同一台机器上,如果你选择不需要虚拟机的,那么他的运行IP地址配置一般为127.0.0.1或者localhost,就相对虚拟机地址麻烦一点。而且极小概率他会直接写死为本地地址,写死的话就只能具体问题具体分析了,很有肯定就改不了了。最后无论他是所谓一键启动,还是一键配置,还是无需启动服务端,都是必有服务端和客户端的,只是是否能被你明显感知到而已,如果你选择本机启动的话,你就需要根据他的脚本或者文件目录来看找他的服务端和客户端,并且找到相应的启动方法

当然,这里是建议选择虚拟机版本,如果选择本地启动版本绝大多数情况也可以处理

本地版本预先处理部分

如果你是虚拟机版本可以跳过这个步骤

首先我们需要把他的服务端和客户端分离,然后使用VMware加载Windows Server 2012 R2镜像文件,虚拟机需要设置桥接,下方选择复制物理机网络状态,然后把服务端文件传入虚拟机

关于如何把客户端传入虚拟机,我顺便这里给一个对程序员友好的方法。因为正常方法都是依赖第三方软件的,无论是依赖Windows的共享文件夹,还是依赖SCP,还是依赖WMware Tools。咱们这里直接在需要上传的电脑里面起一个服务(因为这台电脑一般是开发机器,各种应用开发软件都是齐全,不过这里只需要一个Python即可,使用pip安装一个Flask),此时虚拟机只需要有一个默认浏览器,或者wget就可以下载文件,代码见底部附录

此时使用ipconfig确认你的ip地址,保证你的宿主机和虚拟机之前可以在的路由器网端内可以相互ping

虚拟机版本预先处理部分

如果你是本地版本可以跳过这个步骤

首先,我们需要改桥接,因为提供的虚拟机版本都是NAT网络的配置,所以我们需要在加载镜像文件之后,在该虚拟机的设置中将网络改成桥接,下方选择复制物理机网络状态,如果我们使用默认的NAT模式,局域网内其他机器不做特殊处理的情况下,是无法访问这个机器的

然后启动虚拟机,对于Windows虚拟机而言:在点击右下角的网络图标,在适配器中,选择工作的网络适配器(一般虚拟机就一个),右击属性,在网络选项卡中,下面的Internet协议IPv4中,把静态IP地址地址改成DHCP,然后禁用,再启用这个适配器,这样虚拟机就桥接到本地WIFI了。对于Linux而言,我这里使用CentOS举例,其他发行版本差不多,小伙伴相应检索就行,修改/etc/sysconfig/network-scripts/ifcfg-eth0文件,你的虚拟机不一定是eth0网卡,使用ifconfig查看你网卡之后,进入相应文件,然后将文件内容改成差不多这样:

TYPE=Ethernet
BOOTPROTO=dhcp
NAME=eth0
DEVICE=eth0
ONBOOT=yes

静态地址相关部分全部删掉,然后重启即可

网络配置

该步骤不是必须的,但是建议操作

打开你的路由器配置页面,找到刚才桥接进来的机器,记录它的MAC地址,在DHCP配置中将这个MAC地址和某个固定的静态IP绑定,这样该虚拟机接进来就是一个固定的IP地址了,方便我们客户端进行连接。配置完成后,重启虚拟机网络或者直接重启虚拟机

服务端和客户端IP修改

我们在客户端/服务端目录下面进行字符串检索,windows命令行为findstr /i /s /m "xxx" *.*linux命令行为grep -nr "xxx" ./。这里的xxx改成你相应的服务端默认运行地址:

  • 对于无虚拟机版本这个地址是127.0.0.1localhost你需要检查两次
  • 对于虚拟版本这个是游戏所配置的地址,比如192.168.200.100。你可以先看看他需要你配置的的NAT子网网段,超过半数是192.168.200.0,然后我们去检索192.168.200,这样基本就可以找到了

需要重点关注检索出的ini配置文件,文件名中带有日志相关的(比如名字包含log这种)就可以直接忽略

此时检索出的客户端和服务端IP地址应该是相同的,比如服务端选择运行在192.168.200.100上,那么客户端也应当是连接的192.168.200.100。对于虚拟机版本来说,非常简单,把所有这种都改成刚才我们在路由器中得到或者设置的桥接完成的IP地址即可,比如你本地网络是192.168.1.0/24,你的虚拟机桥接地址是192.168.1.100,那么将192.168.200.100全部改成192.168.1.100即可

对于无虚拟机的稍微有点麻烦,你需要判断一下,哪些本地接口是提供给本地服务端使用的,哪些是提供给客户端使用给的,然后只改客户端的部分。这个地方就具体问题具体分析了,当然你也可以暴力全改成192.168.1.100,大概率不会有问题,但是有些特殊情况下可能会导致服务端内部交互无法正常工作,如果他的服务端各个模块之间交互写的鲁棒性不是很强的话

完成后,按照他的方法运行服务端,客户端,已经有小部分的游戏改造成功可以运行了,如果不能运行我们可能就需要对于数据库进行处理了

数据库修改

我们按照他们的方法连接数据库,这里我们需要根据他给的工具判断使用的是哪种数据库,一般可能是SQL ServerMySQL或者PostgreSQL。使用我们自己的数据库连接工具,连接到数据库,一般都会给账号密码,没有给的话,就往简单了猜,111111123456这种,实在不行就暴力破解,也很快的,因为肯定是简单密码。但是基本肯定都给了,没给的指定就是二道贩子漏了,这种情况基本不会出现

进入数据库之后,重点找带server或者client的表名,一般都是写在这里的。这种表常见的有两种作用,第一种启动服务的配置文件,即该游戏可能并不止使用了配置文件处理服务,还使用了数据库内配置,第二种,验证客户端请求,类似白名单的效果,对于客户端的调用做一些限制。在这些表里面查询,是否有刚才服务默认配置IP,比如192.168.200.100,如果有,将其改成我们机器IP,比如192.168.1.100

这里如果没找到,或者找起来不方便还有一计,就是把所有表按大小排列,找那个只有几条数据的表,基本都能找到

如果还是没找到,我们就可以全库检索,这里给出PostgreSQLSQL Server的全库检索语句置于底部附录,其他数据库类型,小伙伴需要自行调整

完成后,按照他的方法运行服务端,客户端,基本绝大部分的游戏改造成功可以运行了

检查更正

这里如果还跑不起来,可以运行Wireshark,监控网络流量找找原因。比如如果还能监控到原始请求,即此例中的192.168.200.100,那么很有可能还是配置没有改全,极小概率是写死在可执行文件当中。或者找找有没有什么其他的可疑的地方,比如TCP握手正常不正常等等

破坏性解决方案

前面的都一样,但是这里我们不再需要改IP地址了,如果他原来是192.168.200.100,那我们就不改了。直接把在我们路由器中把网段设置为192.168.0.0/16,然后重启路由器,这样我们的机器就和192.168.200.100是同一个网段了,此时我们的桥接地址也不是上例的192.168.1.100了,路由器直接分本本身的192.168.200.100地址,就完事了

这个是最简单的解决方案,但是可能会产生问题,比如广播域变大会导致网络广播流量会显著增加,网络性能下降,比如可能会有兼容性的问题,有些设备可能就是默认255.255.255.0的掩码

当然你如果想,也可改成192.168.200/24,但是这种就更丑陋了,两个问题,第一,如果小伙伴们家里和我一样有多个静态绑定的服务器,那么所有相关配置都得改,第二个,虽然大部分游戏是192.168.200的,但是也有部分用了其他网段,这个时候又该怎么办呢

终极解决方案

当我们不愿意使用破坏性解决方案,也实在没有找到客户端连接不上服务器的原因的时候。虽然不常见,但是如果小伙伴改的多的话,遇见这种情况也是会遇到的,比如服务文件或者数据库表相关内容隐藏的比较深,或者直接写死在可执行文件内,比如程序有做来源配置,但是配置又没有显著暴露

这个时候,我们就需要使用终极解决方案,即前文在互联网部分提到的,使用分布式SDN模拟一个内网环境进行处理,我们这里使用ZeroTierOne为例进行操作,小伙伴也可以选择别的方案,比如WireGuard,为了隐私和安全性小伙伴也可以设置自己的ZeroTier控制器,参考Creating Your Own Roots,这里咱们简单起见,就快速搭建了

同样,使用这种方法,客户端和服务端都不用改IP,消除由于IP构建和验证带来的各种问题,我们只需要在桥接之后(改桥接这个也是选择性的,但是还是建议改一下,内网其他机器访问起来更方便,尤其是在改的过程当中),在客户端和服务端分别装上ZeroTierOne即可

具体使用方法参考官方文档,流程就是首先注册账号,然后创建的你的网络(有免费额度,后续有更多需求,你可以转自己的控制器),设置你网络的网段,复制你的网络ID,然后在本地的服务器端和各个客户端都添加这个网络ID,添加完成后你需要在WEB再次点击Auth同意他们进入,并分配IP,此例当中我们需要给服务器配置192.168.200.100,客户端就随便了

再稍微补充有点关于ZeroTierOne内容,使用时数据流量一般不会经过ZeroTier的服务器,他的中央服务器(根服务器)主要提供的功能就是:建立连接,配置发放,成员验证这种外围工作,一旦连接上就是走的点对点穿透,所以如果内网环境下,速度肯定是没问题的。如前文所说你也可以选择其他类VPN的应用去处理,甚至可以自己手动改网络转发相关信息,但是自己手动搓出来的如果出现问题,你不好定位到底你的问题还是游戏的问题,所以使用这种类VPN的软应用是最简单快捷的方式,后续可以自己慢慢改

至此搭建完成

附录

启动本地服务,支持对外提供本目录对文件下载:

from flask import Flask, send_from_directory, render_template_string
import os

app = Flask(__name__)

# 当前工作目录
CURRENT_DIR = os.getcwd()

# 主页路由 - 显示当前目录下所有文件的列表
@app.route('/')
def index():
    files = [f for f in os.listdir(CURRENT_DIR) if os.path.isfile(os.path.join(CURRENT_DIR, f))]
    html = '''
    <!DOCTYPE html>
    <html>
    <head>
        <title>文件下载服务</title>
        <style>
            body { font-family: Arial, sans-serif; margin: 20px; }
            h1 { color: #333; }
            ul { list-style-type: none; padding: 0; }
            li { margin: 10px 0; }
            a { text-decoration: none; color: #0066cc; }
            a:hover { text-decoration: underline; }
        </style>
    </head>
    <body>
        <h1>可下载文件列表</h1>
        <ul>
            {% for file in files %}
            <li><a href="/download/{{ file }}">{{ file }}</a></li>
            {% endfor %}
        </ul>
    </body>
    </html>
    '''
    return render_template_string(html, files=files)

# 下载路由
@app.route('/download/<filename>')
def download_file(filename):
    return send_from_directory(CURRENT_DIR, filename, as_attachment=True)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)

PostgreSQL全库检索某个字段,以192.168.200.100为例:

CREATE TEMP TABLE IF NOT EXISTS
  search_results (
    table_schema TEXT,
    table_name TEXT,
    column_name TEXT,
    found_value TEXT
  );

TRUNCATE TABLE search_results;

DO $$

DECLARE

rec RECORD;

query TEXT;

BEGIN

FOR rec IN

SELECT

c.table_schema,

c.table_name,

c.column_name

FROM

information_schema.columns c

JOIN information_schema.tables t

ON c.table_schema = t.table_schema

AND c.table_name = t.table_name

WHERE

t.table_type = 'BASE TABLE' 

AND c.table_schema NOT IN ('information_schema', 'pg_catalog')

LOOP

query := format(

'INSERT INTO search_results

SELECT %L, %L, %L, %L::TEXT

FROM %I.%I

WHERE %I::TEXT = %L

LIMIT 1;',

rec.table_schema,

rec.table_name,

rec.column_name,

'192.168.200.100',

rec.table_schema,

rec.table_name,

rec.column_name,

'192.168.200.100'

);

BEGIN

EXECUTE query;

EXCEPTION WHEN OTHERS THEN

CONTINUE;

END;

END LOOP;

END $$;

SELECT
  *
FROM
  search_results;

SQL Server全库检索某个字段,以192.168.200为例:

DECLARE @SearchStr NVARCHAR(100) = '%192.168.200%'

DECLARE @SQL NVARCHAR(MAX)

DECLARE @TableName NVARCHAR(256)

DECLARE @ColumnName NVARCHAR(128)

DECLARE @SearchSQL NVARCHAR(MAX)

  

CREATE TABLE #Results (

TableName NVARCHAR(256),

ColumnName NVARCHAR(128),

ColumnValue NVARCHAR(MAX),

RowNum INT

)

  

-- 获取所有用户表和它们的列(排除视图)

DECLARE TableCursor CURSOR FOR

SELECT

t.name AS TableName,

c.name AS ColumnName

FROM

sys.tables t

INNER JOIN sys.columns c ON t.object_id = c.object_id

INNER JOIN sys.types ty ON c.user_type_id = ty.user_type_id

WHERE

-- 只检查字符串类型列,可选

t.is_ms_shipped = 0

AND ty.name IN ('nvarchar', 'varchar', 'nchar', 'char', 'ntext', 'text')

ORDER BY

t.name, c.name

  

OPEN TableCursor

FETCH NEXT FROM TableCursor INTO @TableName, @ColumnName

  

WHILE @@FETCH_STATUS = 0

BEGIN

SET @SearchSQL = 'SELECT ''' + @TableName + ''' AS TableName, '''

+ @ColumnName + ''' AS ColumnName, CONVERT(NVARCHAR(MAX), [' + @ColumnName + ']) AS ColumnValue, '

+ 'ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RowNum FROM ['

+ @TableName + '] WHERE [' + @ColumnName + '] LIKE ''' + @SearchStr + ''''

  

BEGIN TRY

INSERT INTO #Results (TableName, ColumnName, ColumnValue, RowNum)

EXEC sp_executesql @SearchSQL

END TRY

BEGIN CATCH

PRINT 'Error searching ' + @TableName + '.' + @ColumnName + ': ' + ERROR_MESSAGE()

END CATCH

  

FETCH NEXT FROM TableCursor INTO @TableName, @ColumnName

END

  

CLOSE TableCursor

DEALLOCATE TableCursor

  

-- 显示结果

SELECT

TableName,

ColumnName,

ColumnValue,

RowNum

FROM

#Results

ORDER BY

TableName,

ColumnName,

RowNum

  

DROP TABLE #Results

原文链接

欢迎大家对于本站的访问 - AsterCasc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值