设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 13550|回复: 21
打印 上一主题 下一主题

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

 关闭 [复制链接]

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
跳转到指定楼层
1
发表于 2009-9-27 06:05:30 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

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

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

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


脚本源码


  1. #==============================================================================
  2. # ■  Hangup 异常根除
  3. #    Hangup Exception Eradication
  4. #----------------------------------------------------------------------------
  5. #
  6. #    Hangup 异常是 RMXP 底层引擎内置的一个异常类,游戏进程会在 Graphics.update
  7. #    没有调用超过 10 秒时抛出这个异常。这个脚本使用了 Windows API 暴力地解除
  8. #    了这个限制。
  9. #    使用方法:Hangup 异常根除脚本必须插入到脚本编辑器的最顶端,所有脚本之前,无
  10. #    例外。
  11. #
  12. #----------------------------------------------------------------------------
  13. #
  14. #    更新作者: 紫苏
  15. #    许可协议: FSL -MEE
  16. #    项目版本: 1.2.0827
  17. #    引用网址:
  18. #    http://rpg.blue/forum.php?mod=viewthread&tid=134316
  19. #    http://szsu.wordpress.com/2010/08/09/hangup_eradication
  20. #
  21. #----------------------------------------------------------------------------
  22. #
  23. #    - 1.2.0827 By 紫苏
  24. #      * 更改了配置模块名
  25. #      * 更改了 FSL 注释信息
  26. #
  27. #    - 1.2.0805 By 紫苏
  28. #      * 脚本开始遵循 FSL
  29. #      * 全局范围内改变了脚本结构
  30. #
  31. #    - 1.1.1101 By 紫苏
  32. #      * 修正了脚本在 Windows XP 平台下失效的问题
  33. #
  34. #    - 1.0.0927 By 紫苏
  35. #      * 初始版本完成
  36. #
  37. #==============================================================================

  38. $__jmp_here.call if $__jmp_here

  39. #----------------------------------------------------------------------------
  40. # ● 登记 FSL。
  41. #----------------------------------------------------------------------------
  42. $fscript = {} if !$fscript
  43. $fscript['HangupEradication'] = '1.2.0827'

  44. #==============================================================================
  45. # ■ FSL
  46. #------------------------------------------------------------------------------
  47. #  自由RGSS脚本通用公开协议的功能模块。
  48. #==============================================================================

  49. module FSL
  50.   module HangupEradication
  51.     #------------------------------------------------------------------------
  52.     # ● 定义需要的 Windows API。
  53.     #------------------------------------------------------------------------
  54.     OpenThread = Win32API.new('kernel32', 'OpenThread', 'LIL', 'L')
  55.     CloseHandle = Win32API.new('kernel32', 'CloseHandle', 'L', 'I')
  56.     Thread32Next = Win32API.new('kernel32', 'Thread32Next', 'LP', 'I')
  57.     ResumeThread = Win32API.new('kernel32', 'ResumeThread', 'L', 'L')
  58.     SuspendThread = Win32API.new('kernel32', 'SuspendThread', 'L', 'L')
  59.     Thread32First = Win32API.new('kernel32', 'Thread32First', 'LP', 'I')
  60.     GetCurrentProcessId = Win32API.new('kernel32', 'GetCurrentProcessId', 'V', 'L')
  61.     CreateToolhelp32Snapshot = Win32API.new('kernel32', 'CreateToolhelp32Snapshot', 'LL', 'L')
  62.   end
  63. end

  64. #==============================================================================
  65. # ■ HangupEradication
  66. #------------------------------------------------------------------------------
  67. #  处理根除 Hangup 异常的类。
  68. #==============================================================================

  69. class HangupEradication
  70.   include FSL::HangupEradication
  71.   #--------------------------------------------------------------------------
  72.   # ● 初始化对像。
  73.   #--------------------------------------------------------------------------
  74.   def initialize
  75.     @hSnapShot = CreateToolhelp32Snapshot.call(4, 0)
  76.     @hLastThread = OpenThread.call(2, 0, self.getLastThreadId)
  77.     #@hLastThread = OpenThread.call(2097151, 0, threadID)
  78.     ObjectSpace.define_finalizer(self, self.method(:finalize))
  79.   end
  80.   #--------------------------------------------------------------------------
  81.   # ● 获取当前进程创建的最后一个线程的标识。
  82.   #--------------------------------------------------------------------------
  83.   def getLastThreadId
  84.     threadEntry = [28, 0, 0, 0, 0, 0, 0].pack("L*")
  85.     threadId = 0                                          # 线程标识
  86.     found = Thread32First.call(@hSnapShot, threadEntry)   # 准备枚举线程
  87.     while found != 0
  88.       arrThreadEntry = threadEntry.unpack("L*")           # 线程数据解包
  89.       if arrThreadEntry[3] == GetCurrentProcessId.call    # 匹配进程标识
  90.         threadId = arrThreadEntry[2]                      # 记录线程标识
  91.       end
  92.       found = Thread32Next.call(@hSnapShot, threadEntry)  # 下一个线程
  93.     end
  94.     return threadId
  95.   end
  96.   #--------------------------------------------------------------------------
  97.   # ● 根除 Hangup 异常。
  98.   #     2       : “暂停和恢复线程访问权限”代码;
  99.   #     2097151 : “所有可能的访问权限”代码(Windows XP 平台下无效)。
  100.   #--------------------------------------------------------------------------
  101.   def eradicate
  102.     SuspendThread.call(@hLastThread)
  103.   end
  104.   #--------------------------------------------------------------------------
  105.   # ● 恢复 Hangup 异常。
  106.   #--------------------------------------------------------------------------
  107.   def resume
  108.     while ResumeThread.call(@hLastThread) > 1; end        # 恢复最后一个线程
  109.   end
  110.   #--------------------------------------------------------------------------
  111.   # ● 最终化对像。
  112.   #--------------------------------------------------------------------------
  113.   def finalize
  114.     CloseHandle.call(@hSnapShot)
  115.     CloseHandle.call(@hLastThread)
  116.   end
  117. end

  118. hangupEradication = HangupEradication.new
  119. hangupEradication.eradicate

  120. callcc { |$__jmp_here| }                                  # F12 后的跳转标记

  121. #==============================================================================
  122. # ■ 游戏主过程
  123. #------------------------------------------------------------------------------
  124. #  游戏脚本的解释从这个外壳开始。
  125. #==============================================================================

  126. for subscript in 1...$RGSS_SCRIPTS.size
  127.   begin
  128.     eval(Zlib::Inflate.inflate($RGSS_SCRIPTS[subscript][2]))
  129.   rescue Exception => ex
  130.     # 异常发生并抛出给解释器时恢复线程。
  131.     hangupEradication.resume unless defined?(Reset) and ex.class == Reset
  132.     raise ex
  133.   end
  134. end

  135. hangupEradication.resume
  136. exit
复制代码

已知BUG与冲突

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

点评

eval可以考虑带全参数 用 TOPLEVEL_BINDING 文件名有 line-1~  发表于 2011-9-3 21:08

Lv1.梦旅人

梦石
0
星屑
70
在线时间
17 小时
注册时间
2009-7-3
帖子
64
2
发表于 2009-9-27 06:38:51 | 只看该作者
写的字好长,不懂其意思
回复 支持 反对

使用道具 举报

Lv3.寻梦者

孤独守望

梦石
0
星屑
3121
在线时间
1534 小时
注册时间
2006-10-16
帖子
4321

开拓者贵宾

3
发表于 2009-9-27 09:46:45 | 只看该作者
……= =真暴力
其实一直想问$__jmp_here是啥= =
总之EB干了许许多多的坏事囧
菩提本非树,明镜本非台。回头自望路漫漫。不求姻缘,但求再见。
本来无一物,何处惹尘埃。风打浪吹雨不来。荒庭遍野,扶摇难接。
不知道多久更新一次的博客
回复 支持 反对

使用道具 举报

Lv3.寻梦者 (暗夜天使)

精灵族の天使

梦石
0
星屑
1697
在线时间
3038 小时
注册时间
2007-3-16
帖子
33731

开拓者贵宾

4
发表于 2009-9-27 15:15:58 | 只看该作者
我想可以不可以重定义此线程以后注释hangup部分呢……
回复 支持 反对

使用道具 举报

头像被屏蔽

Lv1.梦旅人 (禁止发言)

梦石
0
星屑
46
在线时间
10 小时
注册时间
2007-5-27
帖子
2558

第1届Title华丽大赛新人奖

5
发表于 2009-9-27 21:21:45 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1045
在线时间
1564 小时
注册时间
2008-7-30
帖子
4418

贵宾

6
发表于 2009-9-27 22:19:20 | 只看该作者
额,原来是紫苏大大,膜拜先。

顺着查单词的空隙时间看了一下,突然发现自己还不明白什么是Hangup

枚举进程就只能用API么?还是用API相对简单?所谓的副作用就只是关闭不到主程序(就只能Kill Process么?)

See FScript Here:https://github.com/DeathKing/fscript
潜心编写URG3中。
所有对URG3的疑问和勘误或者建议,请移步至发布页面。
欢迎萌妹纸催更
回复 支持 反对

使用道具 举报

Lv3.寻梦者 (暗夜天使)

精灵族の天使

梦石
0
星屑
1697
在线时间
3038 小时
注册时间
2007-3-16
帖子
33731

开拓者贵宾

7
发表于 2009-9-27 22:27:54 | 只看该作者
hangup == 脚本已经被备份(这个就是10s内不及时update出的错误)
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1045
在线时间
1564 小时
注册时间
2008-7-30
帖子
4418

贵宾

8
发表于 2009-10-3 11:48:58 | 只看该作者
to 紫苏:收到,怪说不得在Linux下也有看到Ruby(Ruby还遵循GPL……)

to 精灵:原来是这样啊……

See FScript Here:https://github.com/DeathKing/fscript
潜心编写URG3中。
所有对URG3的疑问和勘误或者建议,请移步至发布页面。
欢迎萌妹纸催更
回复 支持 反对

使用道具 举报

Lv1.梦旅人

℃ake

梦石
0
星屑
50
在线时间
8 小时
注册时间
2009-6-6
帖子
787
9
发表于 2009-10-31 09:46:38 | 只看该作者
怎么用呀?把那段代码插入脚本编辑器顶部后,在脚本编辑器底部插入这段代码,十秒后按下确定键,就脚本已备份了。
  1. def print(text)
  2. a = File.open("Game.ini")
  3.   b = a.readlines
  4.   a.close
  5.   for i in 0..b.size
  6.     a = b[i]
  7.     if a.include?("Title")  
  8.       tit = a.split(/=/)[1]
  9.       break      
  10.       end
  11.     end
  12. # API掕媊
  13.    m2w = Win32API.new('kernel32', 'MultiByteToWideChar', 'ilpipi', 'i')
  14.    w2m = Win32API.new('kernel32', 'WideCharToMultiByte', 'ilpipipp', 'i')

  15. # UTF-8 -> Unicode
  16.    len = m2w.call(65001, 0, text, -1, nil, 0);
  17.    buf = "\0" * (len*2)
  18.    m2w.call(65001, 0, text, -1, buf, buf.size/2);

  19. # Unicode -> S-JIS
  20.    len = w2m.call(0, 0, buf, -1, nil, 0, nil, nil);
  21.    ret = "\0" * len
  22.    w2m.call(0, 0, buf, -1, ret, ret.size, nil, nil);
  23.    
  24. Win32API.new('user32','MessageBox',%w{L P P L},'I').call(0,ret,tit,0)
  25. end
  26. print "研究API是一件很复杂的事情!"
复制代码
我爱66RPG,但我讨厌66.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

炎发灼眼的讨伐者

梦石
0
星屑
50
在线时间
1707 小时
注册时间
2007-8-4
帖子
904
10
发表于 2009-10-31 10:17:38 | 只看该作者
ms 依然会跑出 hangup 呢。。

话说 偶现在对于容易 hangup 的代码都直接捕获。。= =
RMXP&RMVX通用Web化完成- -|||
回复 支持 反对

使用道具 举报

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

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-4-26 00:56

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表