赞 | 2 |
VIP | 143 |
好人卡 | 1 |
积分 | 1 |
经验 | 216792 |
最后登录 | 2019-10-10 |
在线时间 | 24 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 61
- 在线时间
- 24 小时
- 注册时间
- 2008-8-5
- 帖子
- 1924
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 紫苏 于 2010-8-28 02:37 编辑
通用FSL信息
脚本说明
灼眼的夏娜曾经发布过一个解决 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[subscript] 的第二个元素获取到当前的脚本标题,有心人可以自己改一下,让脚本弹出消息框告诉你这些信息,而不是直接交给 RM 的异常处理机制来处理。
最后,这个脚本完全有可能引起未知的问题,因为被屏蔽掉的线程对我们普通 RM 用户来说还是未知的,万一它还处理了什么其它的东西呢?我们还需要更多的测试。
更新历史
- 1.2.0827 By 紫苏
- 1.2.0805 By 紫苏
- 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 FSL
- module 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')
- end
- end
- #==============================================================================
- # ■ HangupEradication
- #------------------------------------------------------------------------------
- # 处理根除 Hangup 异常的类。
- #==============================================================================
- class HangupEradication
- include 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 = [28, 0, 0, 0, 0, 0, 0].pack("L*")
- threadId = 0 # 线程标识
- found = Thread32First.call(@hSnapShot, threadEntry) # 准备枚举线程
- while found != 0
- arrThreadEntry = threadEntry.unpack("L*") # 线程数据解包
- if arrThreadEntry[3] == GetCurrentProcessId.call # 匹配进程标识
- threadId = arrThreadEntry[2] # 记录线程标识
- end
- found = Thread32Next.call(@hSnapShot, threadEntry) # 下一个线程
- end
- return threadId
- end
- #--------------------------------------------------------------------------
- # ● 根除 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)
- end
- end
- hangupEradication = HangupEradication.new
- hangupEradication.eradicate
- callcc { |$__jmp_here| } # F12 后的跳转标记
- #==============================================================================
- # ■ 游戏主过程
- #------------------------------------------------------------------------------
- # 游戏脚本的解释从这个外壳开始。
- #==============================================================================
- for subscript in 1...$RGSS_SCRIPTS.size
- begin
- eval(Zlib::Inflate.inflate($RGSS_SCRIPTS[subscript][2]))
- rescue Exception => ex
- # 异常发生并抛出给解释器时恢复线程。
- hangupEradication.resume unless defined?(Reset) and ex.class == Reset
- raise ex
- end
- end
- hangupEradication.resume
- exit
复制代码
已知BUG与冲突
脚本在 Windows XP 平台下失效(已修复)
|
|