扫描二维码关注官方公众号
返回列表
+ 发新帖
查看: 78|回复: 0

[转载发布] 简单热更新系统(rmxp)

[复制链接]
累计送礼:
0 个
累计收礼:
0 个
  • TA的每日心情
    开心
    2025-3-29 03:52
  • 签到天数: 127 天

    连续签到: 11 天

    [LV.7]常住居民III

    2348

    主题

    419

    回帖

    1万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    VIP
    6
    卡币
    11306
    OK点
    16
    推广点
    0
    同能卷
    0
    积分
    14101

    灌水之王

    发表于 4 天前 | 显示全部楼层 |阅读模式
    一个简单的热更新系统,没有做文件校验,实现原理是在Gitee创建一个仓库,通过读取仓库简介中的信息获得版本号和需要更新的文件清单,然后把需要被下载的文件挂到仓库里。
    简介信息示例:
    版本:1.0.1 文件列表:update_part1.zip,update_part2.zip 文件大小:473858,3139291
    文件大小需要手动输入,每个文件大小顺序需要和前面的文件名数据一致,文件大小可以属性里看,但是要注意填到网页上的时候数字中间不要有逗号,不然会被分割导致文件大小读取异常
    下面代码:
    RUBY 代码
    1. #==============================
    2. #  Auto Update System
    3. #  Version: XP-Final-Plus
    4. #  TIME:20250413
    5. #  by:金牛
    6. #  发布论坛:rpg.blue
    7. #  部分声明在56行
    8. #网页信息格式示例: 版本:1.0.1 文件列表:update_part1.zip,update_part2.zip 文件大小:473858,3139291
    9. #使用Gitee仓库挂载更新文件和更新信息,信息写在仓库简介里。
    10. #==============================
    11. module WebContentFetcher#网页模块
    12.   # Win32API 声明
    13.   URLDownloadToCacheFile = Win32API.new('Urlmon', 'URLDownloadToCacheFile', 'ippiii', 'i')
    14.   User32_msgbox          = Win32API.new('user32', 'MessageBoxW', 'LppL', 'L')
    15.   # 正则表达式配置
    16.   CONTENT_REGEX =/版本:\s*(\d+\.\d+\.\d+)\s+文件列表:\s*([a-zA-Z0-9_\-\.]+(?:,[a-zA-Z0-9_\-\.]+)*)\s+文件大小:\s*(\d+(?:,\d+)*)/i
    17.   # UTF8与宽字符转换
    18.   defself.utf8_to_wide(str)
    19.     str.unpack("U*").pack("S*")+ "\0\0"
    20.   end
    21.   # 获取网页内容(核心方法)
    22.   defself.fetch(url)
    23.     begin
    24.       URLDownloadToCacheFile.call(0,url,buf = "\0" * 1024,1024,0,0)
    25.       content = open(buf.sub(/\0+$/){}, 'rb'){ |f| f.read}=~ CONTENT_REGEX
    26.       v= $1
    27.       f= $2
    28.       n= $3
    29.       if content !=nil
    30.       # fcontent={"version"=>v.to_s.strip,"flist"=>f.to_s.split(','),"#{f.to_s.split(',')[0]}"=>n.to_s.split(',')[0],"#{f.to_s.split(',')[1]}"=>n.to_s.split(',')[1]}   
    31.        fcontent={"version"=>v.to_s.strip,"flist"=>f.to_s.split(',')}   
    32.        j=0
    33.        for i in fcontent["flist"]
    34.          fcontent[i]=n.to_s.split(',')[j]
    35.          j+=1
    36.        end  
    37.        return fcontent
    38.       else
    39.         show_error("内容格式不匹配", url)
    40.         nil
    41.       end
    42.     rescue => e
    43.       show_error("获取失败: #{e.message}", url)
    44.       nil
    45.     end
    46.   end
    47.   # 错误提示
    48.   defself.show_error(msg, url)
    49.     title = utf8_to_wide(msg)
    50.     text = utf8_to_wide("请检查网页内容格式或访问:\n#{url}")
    51.     User32_msgbox.call(0, text, title, 16)
    52.   end
    53. end
    54. def get_update_msg
    55.   content = WebContentFetcher.fetch(REPO_URL)
    56.   return content#返回云端获取到的信息
    57.   #version=版本号,flist=一个存储所有需要下载文件的名字数组,“文件名”=该文件大小
    58. end  
    59. #==============================
    60. #  Auto Update System
    61. #  Version: XP-Final-Plus
    62. #  TIME:20250413
    63. #==============================
    64. $msg_update#存储云端信息
    65. REPO_URL    = 'https://gitee.com/你的用户名/你的仓库名'# 仓库主页地址
    66. UPBASE_URL  = 'https://gitee.com/你的用户名//你的仓库名/releases/download/你发布的版本名/'
    67. #TMP_ZIP     = './update.temp'    # 临时文件名 不用管
    68. #FINAL_ZIP   = './update.zip'     # 最终文件名 不用管
    69. GAME_DIR    = './'              # 游戏根目录
    70. CHUNK_SIZE  = 4096#4096              # 增大分块提升稳定性
    71. MAX_RETRIES = 3                # 最大重试次数
    72. CURRENT="1.0.0"               #设定当前版本号,当云端版本号出现迭代时进入更新流程
    73. User32_msgbox = Win32API.new('user32' , 'MessageBoxW' , 'LppL'  , 'L')
    74. #==============================
    75. # 下载进度窗口
    76. #==============================
    77. class Scene_Download
    78.   def initialize
    79.     # 创建半透明背景
    80.     @viewport = Viewport.new(0, 0, 640, 480)
    81.     @bg = Sprite.new(@viewport)
    82.     @bg.bitmap = Bitmap.new(640, 480)
    83.     @bg.bitmap.fill_rect(0, 0, 640, 480, Color.new(0,0,0,180))
    84.     # 进度窗口
    85.     @window = Window_Base.new(160, 180, 320, 140)
    86.     @window.contents = Bitmap.new(@window.width-32, @window.height-32)
    87.     @start_time = Time.now
    88.     refresh(0, 0, 0,"")
    89.   end
    90.   def refresh(downloaded, total, retry_count,fn)
    91.     @window.contents.clear
    92.     # 动态处理未知大小
    93.     text = "下载#{fn}: "
    94.     if total > 0
    95.       percent = (downloaded.to_f / total * 100).round
    96.       text += "#{percent}% (#{filesize_format(downloaded)}/#{filesize_format(total)})"
    97.       bar_width = (downloaded.to_f / total * 276).to_i
    98.     else
    99.       phase = (Time.now - @start_time) * 2
    100.       bar_width = (Math.sin(phase) * 50 + 50).to_i
    101.       text += "正在连接服务器#{'.' * (3 - (Time.now.to_i % 3))}"
    102.     end
    103.     # 重试提示
    104.     text += "\n重试次数:#{retry_count}"if retry_count > 0
    105.     # 绘制内容
    106.     @window.contents.font.color = Color.new(255,255,255)
    107.     @window.contents.draw_text(4, 0, 292, 48, text)
    108.     @window.contents.fill_rect(20, 60, 276, 16, Color.new(100,100,100))
    109.     @window.contents.fill_rect(20, 60, bar_width, 16, Color.new(0,200,0))
    110.     Graphics.update
    111.     Input.update
    112.   end
    113.   #==============================
    114.   # 文件大小格式化(兼容Ruby 1.8)
    115.   #==============================
    116.   def filesize_format(bytes)
    117.     return"0 B"if bytes 0#版本号迭代,需要更新
    118.       returntrue
    119.     end  
    120.   end  
    121.   returnfalse
    122. end  
    123. #==============================
    124. # 下载核心
    125. #==============================
    126. def xp_download(url,save_path,fn)#文件直链,保存路径,文件名
    127.   # 清理残留文件
    128.   File.delete(save_path)rescuenil
    129.   scene = Scene_Download.new
    130.   success = false
    131.   retry_count = 0
    132.   begin
    133.     internet_open = Win32API.new('wininet', 'InternetOpenA', 'plppl', 'l')
    134.     h_internet = internet_open.call("RGSS Player", 0, 0, 0, 0)
    135.     raise"网络初始化失败"if h_internet == 0
    136.     File.open(save_path, 'wb')do |f|
    137.       until success || retry_count >= MAX_RETRIES
    138.         begin
    139.           scene.refresh(0, 0, retry_count,fn)
    140.           # 建立连接
    141.          h_url = Win32API.new('wininet', 'InternetOpenUrlA', 'lpplll', 'l').call(h_internet, url, 0, 0, 0x80000000 | 0x00800000 | 0x00001000, 0)
    142.          raise"连接服务器失败"if h_url == 0
    143.          # 获取文件大小
    144.          total_size = get_content_length(fn)
    145.          dynamic_mode = total_size == 0
    146.          # 下载循环
    147.           buffer = "\0" * CHUNK_SIZE
    148.           downloaded = 0
    149.           last_update = Time.now
    150.           loopdo
    151.             bytes_read = [0].pack('L')
    152.             Win32API.new('wininet', 'InternetReadFile', 'lplp', 'l').call(
    153.               h_url, buffer, CHUNK_SIZE, bytes_read)
    154.             read_size = bytes_read.unpack('L').first
    155.             breakif read_size == 0
    156.             f.write(buffer[0, read_size])
    157.             downloaded += read_size
    158.             # 优化刷新频率
    159.             ifTime.now - last_update > 0.3
    160.               scene.refresh(downloaded, total_size, retry_count,fn)
    161.               last_update = Time.now
    162.             end
    163.           end
    164.           success = true
    165.         rescue => e
    166.           retry_count += 1
    167.           scene.refresh(downloaded, total_size, retry_count,fn)
    168.           sleep(2**retry_count)  # 指数退避
    169.           retryif retry_count < MAX_RETRIES
    170.         ensure
    171.           Win32API.new('wininet', 'InternetCloseHandle', 'l', 'l').call(h_url)rescuenil
    172.         end
    173.       end
    174.     end
    175.   rescue => e
    176.    # p "错误发生在:#{e.backtrace.first}"
    177.     show_message("最终错误: #{e.message}", "系统错误")
    178.   ensure
    179.     scene.disposeif scene
    180.     Win32API.new('wininet', 'InternetCloseHandle', 'l', 'l').call(h_internet)rescuenil
    181.     # 重命名临时文件
    182.   #  File.rename(TMP_ZIP, fn) if success && File.exist?(TMP_ZIP)
    183.   end
    184.   success
    185. end
    186. #==============================
    187. # 安全解压方法
    188. #==============================
    189. def unzip_file(zip_path, dest_dir)
    190.    # success = system("powershell -Command "Expand-Archive -Path '#{zip_path}' -DestinationPath '#{dest_dir}'"")
    191.     success = system("powershell -WindowStyle Hidden -Command "Expand-Archive -Path '#{zip_path}' -DestinationPath '#{dest_dir}'"")
    192.     success
    193. end
    194. #==============================
    195. # 增强版更新流程
    196. #==============================
    197. def perform_update
    198.   # 步骤1:同步云端版本信息,判断是否需要更新
    199.   if check_verison
    200.         # 步骤2:显示确认对话框
    201.      returnunless confirm_update?#不需要确认的话注释掉就行
    202.     # 步骤3:获取更新列表并下载更新
    203.     for fn in$msg_update["flist"]#遍历更新列表
    204.       url=UPBASE_URL+fn
    205.       if xp_download(url, GAME_DIR+fn,fn)
    206.       else
    207.           show_message("#{fn}下载失败,请检查网络连接", "网络错误")
    208.       end
    209.     end  
    210.     for fn in$msg_update["flist"]
    211.       Graphics.update
    212.       # 步骤3:解压文件
    213.       begin
    214.       if unzip_file(GAME_DIR+fn, GAME_DIR)
    215.               File.delete(fn)
    216.              # show_message("#{fn}更新成功!", "更新完成")
    217.       else
    218.             show_message("#{fn}解压失败,请手动解压文件", "解压错误")
    219.       end
    220.        rescue => e
    221.           show_message("解压过程发生错误:#{e.message}", "严重错误")
    222.       end  
    223.     end  
    224.     #步骤4:更新完成,重载数据库
    225.     show_message("更新成功!部分更新游戏重启后生效!", "更新完成")
    226.     #重新加载数据库的方法
    227.     #需根据自身需要实现重载方法。
    228.   end  
    229. end
    230. #==============================
    231. # 辅助方法
    232. #==============================
    233. def utf8_to_wide(str)
    234.    str.unpack("U*").pack("S*")+ "\0\0"
    235. end
    236. def show_message(text, title="提示")
    237.   User32_msgbox.call(0, utf8_to_wide(text), utf8_to_wide(title), 0x40)
    238. end
    239. def confirm_update?
    240.   response = User32_msgbox.call(0,
    241.     utf8_to_wide("检测到新版本,是否立即更新?"),
    242.     utf8_to_wide("版本更新"),
    243.     0x34)  # 带取消按钮的警告图标
    244.   response == 6  # IDYES
    245. end
    复制代码

                本帖来自P1论坛作者939034448,因Project1站服务器在国外有时候访问缓慢不方便作者交流学习,经联系P1站长fux2同意署名转载一起分享游戏制作经验,共同为国内独立游戏作者共同创造良好交流环境,原文地址:https://rpg.blue/forum.php?mod=viewthread&tid=497224  若有侵权,发帖作者可联系底部站长QQ在线咨询功能删除,谢谢。
    天天去同能,天天有童年!
    回复 送礼论坛版权

    使用道具 举报

    文明发言,和谐互动
    文明发言,和谐互动
    高级模式
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    关闭

    幸运抽奖

    社区每日抽奖来袭,快来试试你是欧皇还是非酋~

    立即查看

    聊天机器人
    Loading...

    QQ|Archiver|手机版|小黑屋|同能RPG制作大师 ( 沪ICP备12027754号-3 )

    GMT+8, 2025-4-18 15:28 , Processed in 0.107046 second(s), 57 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

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