じ☆ve冰风 发表于 2024-4-19 18:42:40

[RMXP][FSL] 根除 10s Hangup “脚本已备份”异常 1.2.0827

http://szsu.files.wordpress.com/2010/08/group_115_banner.png?w=644

通用FSL信息

更新作者: 紫苏 许可协议: FSL -MEE 项目版本: 1.2.0827 引用网址:
http://rpg.blue/forum.php?mod=viewthread&tid=134316
http://szsu.wordpress.com/2010/08/09/hangup_eradication/

脚本说明

  灼眼的夏娜曾经发布过一个解决 RMXP 10 秒不刷新就抛出 Hangup 异常,提示“脚本已备份”(这个错误信息是汉化失误)问题的脚本,其原理是创建一个 Ruby 线程,定期调用 Graphics.update,这样就能防止异常的抛出。这个解决方法有一个弊端,就是当 RM 进程阻塞时,Ruby 线程也会停止运行,超过 10 秒后仍如果进程从阻塞中恢复,仍然会抛出 Hangup 异常。我们自然而然地会想:难道不能从根本杜绝 Hangup 的抛出吗?
  前不久在写精确获取窗口句柄的时候,发现 RMXP 游戏进程创建了不止一个的 Windows 线程,通过一个一个地结束线程发现,游戏进程最后创建的一个线程恰好就是控制 Hangup 异常抛出的线程。估计 10 秒的计时也是在这个线程内部的用户代码中进行的。那我们简单地、暴力地把这个线程咔嚓掉,不就搞定了吗?我这么做了,结果发现:原来处理程序最终化(即退出时)的也是这个线程,在我把这个线程咔嚓之后,无论是点窗口右上角的关闭按钮,所有脚本都解释完毕,还是在异常抛出到 Ruby 顶层后的正常退出都失效了,只能通过结束进程来关闭游戏。
  想了几天,终于想到了解决方法:在程序初始化的时候就暂停掉这个线程,直到游戏需要退出的时候再恢复其用户代码的运行,这样,游戏过程中所有本来应该抛出 Hangup 异常的地方都被鞋盒掉了,这就是这个脚本的暴力之处。但关键就是——怎么在所有需要退出的场合下都进行线程的恢复?点窗口关闭按钮的场合很容易,可以重新定义 exit;所有脚本解释完毕的场合也很容易,可以在 main 脚本下面做相关的处理;而异常抛出到顶层的场合,似乎就不那么么简单了。你说可以在 main 脚本的 begin … rescue … end 那里捕获其它所有异常?那万一在前面定义 Game_Temp 啊 Scene_Title 啊之类的地方发生了异常(也就是解释到 main 脚本之前)怎么办呢?
  现在的办法是这样的:游戏运行的时候,RM 会把脚本读取到一个全局数组 $RGSS_SCRIPTS 中,而实际在解释脚本的时候也是在访问这个数组的内容。我们可以越俎代庖,在 RM 解释所有脚本之前(不包括越俎代庖的这个脚本)先把脚本解释完了,然后直接退出程序。换句话说,就是通过 Ruby 的 eval 函数去代替了 RM 的 RGSSEval 函数,解释了所有的脚本。这样一来,在解释脚本过程中所有的异常(当然也不包括越俎代庖脚本中的异常,下面发布是经过调试后的脚本,应该不会有错误了,不过为了保险起见还望大家多多测试)都可以通过 rescue 来捕获到了!当然有利也有弊,这样做了之后,当异常抛出时,提示的消息就只有“eval:#{出错行数}#{出错信息}”,而没有为用户提供脚本的标题,并在脚本编辑器中把光标指向出错的地方了,这是这个脚本的副作用。
  当然,发生错误的时候你完全可以暂时屏蔽掉这个脚本,这样就可以把异常后的效果恢复到从前那样了,反正调试的时候你也不一定需要这个脚本。另外,这个脚本在捕获到异常,恢复了线程的运行之后就直接把异常再次抛出给了顶层,所以异常错误信息没有脚本的标题。但其实在循环中,完全可以通过 $RGSS_SCRIPT 的第二个元素获取到当前的脚本标题,有心人可以自己改一下,让脚本弹出消息框告诉你这些信息,而不是直接交给 RM 的异常处理机制来处理。
  最后,这个脚本完全有可能引起未知的问题,因为被屏蔽掉的线程对我们普通 RM 用户来说还是未知的,万一它还处理了什么其它的东西呢?我们还需要更多的测试。

更新历史

1.2.0827 By 紫苏
更改了配置模块名 更改了 FSL 注释信息
1.2.0805 By 紫苏
脚本开始遵循 FSL 全局范围内改变了脚本结构
1.1.1101 By 紫苏
修正了脚本在 Windows XP 平台下失效的问题
1.0.0927 By 紫苏
初始版本完成


脚本源码

#==============================================================================# ■Hangup 异常根除#    Hangup Exception Eradication#----------------------------------------------------------------------------##    Hangup 异常是 RMXP 底层引擎内置的一个异常类,游戏进程会在 Graphics.update#    没有调用超过 10 秒时抛出这个异常。这个脚本使用了 Windows API 暴力地解除#    了这个限制。#    使用方法:Hangup 异常根除脚本必须插入到脚本编辑器的最顶端,所有脚本之前,无#    例外。##----------------------------------------------------------------------------##    更新作者: 紫苏#    许可协议: FSL -MEE#    项目版本: 1.2.0827#    引用网址:#    http://rpg.blue/forum.php?mod=viewthread&tid=134316#    http://szsu.wordpress.com/2010/08/09/hangup_eradication##----------------------------------------------------------------------------##    - 1.2.0827 By 紫苏#      * 更改了配置模块名#      * 更改了 FSL 注释信息##    - 1.2.0805 By 紫苏#      * 脚本开始遵循 FSL#      * 全局范围内改变了脚本结构##    - 1.1.1101 By 紫苏#      * 修正了脚本在 Windows XP 平台下失效的问题##    - 1.0.0927 By 紫苏#      * 初始版本完成##==============================================================================$__jmp_here.call if $__jmp_here#----------------------------------------------------------------------------# ● 登记 FSL。#----------------------------------------------------------------------------$fscript = {} if !$fscript$fscript['HangupEradication'] = '1.2.0827'#==============================================================================# ■ FSL#------------------------------------------------------------------------------#  自由RGSS脚本通用公开协议的功能模块。#==============================================================================module FSLmodule HangupEradication    #------------------------------------------------------------------------    # ● 定义需要的 Windows API。    #------------------------------------------------------------------------    OpenThread = Win32API.new('kernel32', 'OpenThread', 'LIL', 'L')    CloseHandle = Win32API.new('kernel32', 'CloseHandle', 'L', 'I')    Thread32Next = Win32API.new('kernel32', 'Thread32Next', 'LP', 'I')    ResumeThread = Win32API.new('kernel32', 'ResumeThread', 'L', 'L')    SuspendThread = Win32API.new('kernel32', 'SuspendThread', 'L', 'L')    Thread32First = Win32API.new('kernel32', 'Thread32First', 'LP', 'I')    GetCurrentProcessId = Win32API.new('kernel32', 'GetCurrentProcessId', 'V', 'L')    CreateToolhelp32Snapshot = Win32API.new('kernel32', 'CreateToolhelp32Snapshot', 'LL', 'L')endend#==============================================================================# ■ HangupEradication#------------------------------------------------------------------------------#  处理根除 Hangup 异常的类。#==============================================================================class HangupEradicationinclude FSL::HangupEradication#--------------------------------------------------------------------------# ● 初始化对像。#--------------------------------------------------------------------------def initialize    @hSnapShot = CreateToolhelp32Snapshot.call(4, 0)    @hLastThread = OpenThread.call(2, 0, self.getLastThreadId)    #@hLastThread = OpenThread.call(2097151, 0, threadID)    ObjectSpace.define_finalizer(self, self.method(:finalize))end#--------------------------------------------------------------------------# ● 获取当前进程创建的最后一个线程的标识。#--------------------------------------------------------------------------def getLastThreadId    threadEntry = .pack("L*")    threadId = 0                                          # 线程标识    found = Thread32First.call(@hSnapShot, threadEntry)   # 准备枚举线程    while found != 0      arrThreadEntry = threadEntry.unpack("L*")         # 线程数据解包      if arrThreadEntry == GetCurrentProcessId.call    # 匹配进程标识      threadId = arrThreadEntry                      # 记录线程标识      end      found = Thread32Next.call(@hSnapShot, threadEntry)# 下一个线程    end    return threadIdend#--------------------------------------------------------------------------# ● 根除 Hangup 异常。#   2       : “暂停和恢复线程访问权限”代码;#   2097151 : “所有可能的访问权限”代码(Windows XP 平台下无效)。#--------------------------------------------------------------------------def eradicate    SuspendThread.call(@hLastThread)end#--------------------------------------------------------------------------# ● 恢复 Hangup 异常。#--------------------------------------------------------------------------def resume    while ResumeThread.call(@hLastThread) > 1; end      # 恢复最后一个线程end#--------------------------------------------------------------------------# ● 最终化对像。#--------------------------------------------------------------------------def finalize    CloseHandle.call(@hSnapShot)    CloseHandle.call(@hLastThread)endendhangupEradication = HangupEradication.newhangupEradication.eradicatecallcc { |$__jmp_here| }                                  # F12 后的跳转标记#==============================================================================# ■ 游戏主过程#------------------------------------------------------------------------------#  游戏脚本的解释从这个外壳开始。#==============================================================================for subscript in 1...$RGSS_SCRIPTS.sizebegin    eval(Zlib::Inflate.inflate($RGSS_SCRIPTS))rescue Exception => ex    # 异常发生并抛出给解释器时恢复线程。    hangupEradication.resume unless defined?(Reset) and ex.class == Reset    raise exendendhangupEradication.resumeexit复制代码
已知BUG与冲突

脚本在 Windows XP 平台下失效(已修复)

             本帖来自P1论坛作者紫苏,因Project1站服务器在国外有时候访问缓慢不方便作者交流学习,经联系P1站长fux2同意署名转载一起分享游戏制作经验,共同为国内独立游戏作者共同创造良好交流环境,原文地址:https://rpg.blue/forum.php?mod=viewthread&tid=134316若有侵权,发帖作者可联系底部站长QQ在线咨询功能删除,谢谢。
页: [1]
查看完整版本: [RMXP][FSL] 根除 10s Hangup “脚本已备份”异常 1.2.0827