查看: 5305|回复: 5

[讨论] 【RMXP】游戏防作弊集锦

[复制链接]
梦石
0
星屑
60
在线时间
705 小时
注册时间
2007-12-23
回帖
809
发表于 2013-8-13 11:43:32 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
今天去研究了一下RMXP的反作弊

除了破解RGSSAD以外,主要还有这两种作弊方式:
1.修改运行时内存数据(比如金山游侠、Cheat Engine等)
2.修改存档(网上应该已经有这类存档修改器了吧)

对于修改内存,RMXP也有保护机制,不过只是将数字乘以2再加1而已,地球人都知道。对于修改存档,则并没有什么好的防范手段。
针对这两种破解方式,在下在网上搜索了一早上,终于找到了几种反作弊方法。
1.内存防改(by @土撥撥 )
http://rpg.blue/thread-218920-1-1.html
6R上已经有现成的了,但也只能防止金钱和变量被修改,如果战斗的时候改改血量之类的就防不住了。希望大神可以研究、改进。
2.存档防改(by @FantasyDR )
这在6R上很难找到,而且已经可以暴力破解了。不过也只是暴力破解,毕竟还是需要一定时间的。可以防范大多数的傻瓜式作弊玩家。
它已经被应用于魔塔样板,但原版代码却很难找到,今天终于在某个角落里发现了它。
[pre lang="ruby" line="1"]#==============================================================================
# ☆★☆ Custom Save☆★☆
#------------------------------------------------------------------------------
# - FantasyDR
#------------------------------------------------------------------------------
# MSN: FantasyDR_SJL@hotmail.com
#------------------------------------------------------------------------------
# - 2006.7.18
#------------------------------------------------------------------------------
# 自定义存档数据排列顺序并校验存档
#==============================================================================

#==============================================================================
# ■ Scene_File
#------------------------------------------------------------------------------
#  存档画面及读档画面的超级类。
#==============================================================================

class Scene_File
  #--------------------------------------------------------------------------
  # ● 存档密钥,请定义成自己的密钥
  #   密钥不同的存档将无法被读取
  #--------------------------------------------------------------------------
  SAVE_KEY = 1234567
  #--------------------------------------------------------------------------
  # ● 存档号码最大值
  #   默认0~3共4个存档,如果需要大于4个存档,需要改动窗口类。
  #   这里不提供大于4个存档的窗口排布,如果有需要请自己定义。
  #--------------------------------------------------------------------------
  SAVE_MAX = 3
  #--------------------------------------------------------------------------
  # ● 初始化对像
  #   help_text : 帮助窗口显示的字符串
  #--------------------------------------------------------------------------
  def initialize(help_text,type = "档案",save_max = SAVE_MAX)
    @help_text = help_text
    @save_max = save_max
    @type = type
  end
  #--------------------------------------------------------------------------
  # ● 主处理
  #--------------------------------------------------------------------------
  def main
   
    # 生成存档选择
    save_list = []
    for i in 1..@save_max+1
      save_list.push(@type+i.to_s)
    end   
    # 生成帮助窗口
    @help_window = Window_Help.new
    @help_window.set_text(@help_text)  
    # 生成存档文件查
    @savefile_windows = []
    for i in 0..@save_max
      @savefile_windows.push(Window_SaveFile.new(i, make_filename(i)))
    end
    # 选择最后操作的文件
    @file_index = $game_temp.last_file_index
    @savefile_windows[@file_index].selected = true
    # 执行过渡
    Graphics.transition
    # 主循环
    loop do
      # 刷新游戏画面
      Graphics.update
      # 刷新输入信息
      Input.update
      # 刷新画面
      update
      # 如果画面被切换的话就中断循环
      if $scene != self
        break
      end
    end
    # 准备过渡
    Graphics.freeze
    # 释放窗口
    @help_window.dispose
    for i in @savefile_windows
      i.dispose
    end
  end
  #--------------------------------------------------------------------------
  # ● 刷新画面
  #--------------------------------------------------------------------------
  def update
    # 刷新窗口
    @help_window.update
    for i in @savefile_windows
      i.update
    end
    # 按下 C 键的情况下
    if Input.trigger?(Input::C)
      # 调用过程 on_decision (定义继承目标)
      on_decision(make_filename(@file_index))
      $game_temp.last_file_index = @file_index
      return
    end
    # 按下 B 键的情况下
    if Input.trigger?(Input::B)
      # 调用过程 on_cancel (定义继承目标)
      on_cancel
      return
    end
    # 按下方向键下的情况下
    if Input.repeat?(Input::DOWN)
      # 方向键下的按下状态不是重复的情况下、
      # 并且光标的位置在 3 以前的情况下
      if Input.trigger?(Input::DOWN) or @file_index < 3
        # 演奏光标 SE
        $game_system.se_play($data_system.cursor_se)
        # 光标向下移动
        @savefile_windows[@file_index].selected = false
        @file_index = (@file_index + 1) % 4
        @savefile_windows[@file_index].selected = true
        return
      end
    end
    # 按下方向键上的情况下
    if Input.repeat?(Input::UP)
      # 方向键上的按下状态不是重复的情况下、
      # 并且光标的位置在 0 以后的情况下
      if Input.trigger?(Input::UP) or @file_index > 0
        # 演奏光标 SE
        $game_system.se_play($data_system.cursor_se)
        # 光标向上移动
        @savefile_windows[@file_index].selected = false
        @file_index = (@file_index + 3) % 4
        @savefile_windows[@file_index].selected = true
        return
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● 生成文件名
  #   file_index : 文件名的索引 (0~3)
  #--------------------------------------------------------------------------
  def make_filename(file_index)
    return "Save#{file_index + 1}.rxdata"
  end
end

#==============================================================================
# ■ Scene_Load
#------------------------------------------------------------------------------
#  处理读档画面的类。
#==============================================================================

class Scene_Load < Scene_File
  #--------------------------------------------------------------------------
  # ● 初始化对像
  #--------------------------------------------------------------------------
  def initialize(save_max = SAVE_MAX)
    # 再生成临时对像
    $game_temp = Game_Temp.new
    # 选择存档时间最新的文件
    $game_temp.last_file_index = 0
    latest_time = Time.at(0)
    for i in 0..save_max
      filename = make_filename(i)
      if FileTest.exist?(filename)
        begin
          file = Zlib::GzipReader.open(filename)
        rescue
          next
        end
        if file.mtime > latest_time
          latest_time = file.mtime
          $game_temp.last_file_index = i
        end
        file.close
      end
    end
    super("要载入哪个文件?","读取",save_max)
  end
  #--------------------------------------------------------------------------
  # ● 确定时的处理
  #--------------------------------------------------------------------------
  def on_decision(filename)
    # 文件不存在的情况下
    unless FileTest.exist?(filename)
      # 演奏冻结 SE
      $game_system.se_play($data_system.buzzer_se)
      return
    end
    # 演奏读档 SE
    $game_system.se_play($data_system.load_se)
    # 写入存档数据
    begin
      file = Zlib::GzipReader.open(filename)
      read_save_data(file)
    rescue
      # 演奏冻结 SE
      $game_system.se_play($data_system.buzzer_se)
      return
    end
    file.close
    # 还原 BGM、BGS
    $game_system.bgm_play($game_system.playing_bgm)
    $game_system.bgs_play($game_system.playing_bgs)
    # 刷新地图 (执行并行事件)
    $game_map.update
    # 切换到地图画面
    $scene = Scene_Map.new
  end
  #--------------------------------------------------------------------------
  # ● 取消时的处理
  #--------------------------------------------------------------------------
  def on_cancel
    # 演奏取消 SE
    $game_system.se_play($data_system.cancel_se)
    # 切换到标题画面
    $scene = Scene_Title.new
  end
  #--------------------------------------------------------------------------
  # ● 读取存档数据
  #   file : 读取用文件对像 (已经打开)
  #--------------------------------------------------------------------------
  def read_save_data(file)
    # 读取描绘存档文件用的角色数据
    characters = Marshal.load(file)
    # 读取测量游戏时间用画面计数
    Graphics.frame_count = Marshal.load(file)
    # 读取校验
    crcs = Marshal.load(file)
    # 读取文档字串
    strings = Marshal.load(file)
    # 校验检测
    key = SAVE_KEY
    strings.each_index do |i|
      key = Zlib.crc32(strings,key)
      unless crcs == key
        file.close
        raise "file check error"
        return
      end
    end
    # 读取各种游戏对像
    $game_system = Marshal.load(Zlib::Inflate.inflate(strings[0]))
    $game_variables = Marshal.load(Zlib::Inflate.inflate(strings[1]))
    $game_self_switches = Marshal.load(Zlib::Inflate.inflate(strings[2]))
    $game_switches = Marshal.load(Zlib::Inflate.inflate(strings[3]))
    $game_troop = Marshal.load(Zlib::Inflate.inflate(strings[4]))
    $game_map = Marshal.load(Zlib::Inflate.inflate(strings[5]))
    $game_player = Marshal.load(Zlib::Inflate.inflate(strings[6]))
    $game_screen = Marshal.load(Zlib::Inflate.inflate(strings[7]))
    $game_actors = Marshal.load(Zlib::Inflate.inflate(strings[8]))
    $game_party = Marshal.load(Zlib::Inflate.inflate(strings[9]))
   
    # 魔法编号与保存时有差异的情况下
    # (加入编辑器的编辑过的数据)
    if $game_system.magic_number != $data_system.magic_number
      # 重新装载地图
      $game_map.setup($game_map.map_id)
      $game_player.center($game_player.x, $game_player.y)
    end
    # 刷新同伴成员
    $game_party.refresh
  end
end

#==============================================================================
# 自定义存档菜单
#==============================================================================

#==============================================================================
# ■ Scene_Save
#------------------------------------------------------------------------------
#  处理存档画面的类。
#==============================================================================

class Scene_Save < Scene_File
  #--------------------------------------------------------------------------
  # ● 初始化对像
  #--------------------------------------------------------------------------
  def initialize(save_max = SAVE_MAX)
    super("要保存到这个文件吗?","存入",save_max)
  end
  #--------------------------------------------------------------------------
  # ● 确定时的处理
  #--------------------------------------------------------------------------
  def on_decision(filename)
    # 演奏存档 SE
    $game_system.se_play($data_system.save_se)
    # 写入存档数据
    file = Zlib::GzipWriter.open(filename,9)
    write_save_data(file)
    file.close
    # 如果被事件调用
    if $game_temp.save_calling
      # 清除存档调用标志
      $game_temp.save_calling = false
      # 切换到地图画面
      $scene = Scene_Map.new
      return
    end
    # 切换到菜单画面
    $scene = Scene_Menu.new(5)
  end
  #--------------------------------------------------------------------------
  # ● 取消时的处理
  #--------------------------------------------------------------------------
  def on_cancel
    # 演奏取消 SE
    $game_system.se_play($data_system.cancel_se)
    # 如果被事件调用
    if $game_temp.save_calling
      # 清除存档调用标志
      $game_temp.save_calling = false
      # 切换到地图画面
      $scene = Scene_Map.new
      return
    end
    # 切换到菜单画面
    $scene = Scene_Menu.new(5)
  end
  #--------------------------------------------------------------------------
  # ● 写入存档数据
  #   file : 写入用文件对像 (已经打开)
  #--------------------------------------------------------------------------
  def write_save_data(file)   
    # 生成描绘存档文件用的角色图形
    characters = []
    for i in 0...$game_party.actors.size
      actor = $game_party.actors
      characters.push([actor.character_name, actor.character_hue,actor.id])
    end
   
    strings = []
   
    # 写入描绘存档文件用的角色数据
    Marshal.dump(characters,file)
    # 写入测量游戏时间用画面计数
    Marshal.dump(Graphics.frame_count,file)
    # 增加 1 次存档次数
    $game_system.save_count += 1
    # 保存魔法编号
    # (将编辑器保存的值以随机值替换)
    $game_system.magic_number = $data_system.magic_number
   
    # 写入各种游戏对像
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_system)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_variables)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_self_switches)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_switches)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_troop)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_map)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_player)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_screen)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_actors)))
    strings.push(Zlib::Deflate.deflate(Marshal.dump($game_party)))
   
    # 计算校验值
    crcs = []
    key = SAVE_KEY
    strings.each do |i|
      key = Zlib.crc32(i,key)
      crcs.push(key)
    end
    Marshal.dump(crcs,file)
    Marshal.dump(strings,file)
  end
end

#==============================================================================
# ■ Window_SaveFile
#------------------------------------------------------------------------------
#  显示存档以及读档画面、保存文件的窗口。
#==============================================================================

class Window_SaveFile < Window_Base
  #--------------------------------------------------------------------------
  # ● 初始化对像
  #   file_index : 存档文件的索引 (0~n)
  #   filename   : 文件名
  #--------------------------------------------------------------------------
  def initialize(file_index, filename,viewport=nil)
    super(0, 64 + file_index % 4 * 104, 640, 104)
    self.contents = Bitmap.new(width - 32, height - 32)
   
    @file_index = file_index
    @filename = "Save#{@file_index + 1}.rxdata"
    @time_stamp = Time.at(0)
    @file_exist = FileTest.exist?(@filename)
   
    if @file_exist
      begin
        file = Zlib::GzipReader.open(filename)
        @time_stamp = file.mtime
        @characters = Marshal.load(file)
        @frame_count = Marshal.load(file)
        @total_sec = @frame_count / Graphics.frame_rate
        file.close
      rescue
        @file_exist = false
      end
    end
   
    self.refresh
    @selected = false
  end
end
[/pre]
使用时只需修改SAVE_KEY即可,一般人是无法轻易破解的


唉,6R上的朋友似乎对素材保护比较感兴趣,对作弊倒没什么防范。要是一个游戏可以作弊,那么正常通关的玩家还会有同样的成就感吗?其实反作弊也是在为玩家着想。
但是这两种反作弊还得配合加密技术才能发挥作用,而加解密居然是禁止讨论的,那些技术贴都加了阅读权限限制。而且这些都只是很简陋的加密,对高手来说不堪一击。它的目的并不是绝对保护,而是浪费一下那些无节操作弊玩家的时间和精力,让他们失去破解的信心。
买了正版RMMV的同学进来看一下,谢谢~
https://rpg.blue/thread-393237-1-1.html
梦石
0
星屑
3905
在线时间
1972 小时
注册时间
2013-1-3
回帖
9146
发表于 2013-8-13 13:03:58 | 显示全部楼层
其实那个存档密钥用起来不方便
因为需要这样加密的游戏一般比较高端,而自己重定义存档又太费劲…
《宿愿·寻剑篇》正式版已经发布!快去看看!点击进入论坛发布贴
回复

使用道具 举报

梦石
0
星屑
752
在线时间
800 小时
注册时间
2011-10-20
回帖
2257

开拓者

发表于 2013-8-13 13:57:10 | 显示全部楼层
云rm
欢迎您

点评

纯IE支持差评(好想给你减分  发表于 2013-8-14 12:54
回复

使用道具 举报

梦石
0
星屑
9292
在线时间
2504 小时
注册时间
2011-5-20
回帖
14521

开拓者

发表于 2013-8-14 12:06:10 | 显示全部楼层
百度的云平台告诉你修改是浮云
[img]http://service.t.sina.com.cn/widget/qmd/5339802982/c02e16bd/7.png
回复

使用道具 举报

梦石
0
星屑
65
在线时间
391 小时
注册时间
2012-5-5
回帖
185
发表于 2013-8-14 16:47:47 | 显示全部楼层
本帖最后由 my.0lantulantu0 于 2013-8-14 17:12 编辑

其实Marshal的入口点还是蛮好找的比如04 08……
Zlib可以通过重写module Zlib实现XX……
加密算法时间:破解算法时间 大约在1:10~1:200左右吧
估计解下边这个也不太轻松:
加密算法时间:破解算法时间 大约在1:5~1:500左右吧
[pre lang="ruby" line="1"]Marshal.dump(_res).each_byte{|w|f=f+([w].pack(_packtype))}
file.write([f].pack(_packtype2))[/pre]
回复

使用道具 举报

梦石
0
星屑
4756
在线时间
389 小时
注册时间
2012-11-8
回帖
235
发表于 2013-8-14 21:04:10 | 显示全部楼层
内存防修改可以hook api或者用api监控进程,前者容易报毒,后者改个进程名就好了。。。貌似没什么好的方法。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部