#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# Versioning for RMX-OS by Blizzard
# Version: 1.21
# Type: RMX-OS Add-on
# Date: 30.5.2010
# Date v1.01: 12.9.2010
# Date v1.1: 17.9.2010
# Date v1.2: 12.6.2013
# Date v1.21: 29.7.2014
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
#
# This script is to be distributed under the same terms and conditions like
# the script it was created for: RMX-OS.
#
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
#
# Information:
#
# This script must be placed below RMX-OS and requires RMX-OS to work
# properly. It is able to update the game client connecting to the server to
# the newest game version.
#
#
# Instructions:
#
# The server extension validates the game version from the client and if the
# client game version is lower than the server game version, the server will
# refuse a connection. Instead the server will notify the client that it is
# possible to update. After the player has confirmed that he wants to update
# the client, the server will initiate version updating of the server. For
# the server to know which files need to be sent, you need to create version
# log files.
#
#
# Version Logs:
#
# The version log files on the server have a simple format. Each file is
# named like "NUMBER.txt" where NUMBER is the version.
#
# examples:
#
# 10.txt - version log for updating from version 9 to 10
# 15.txt - version log for updating from version 14 to 15
# 5475685.txt - version log for updating from version 5475684 to 5475685
#
# Version log files have a specific format that allows you to define which
# files need to be updated. The basic format is:
#
# :COMMAND
# FILES
#
# The following commands are at your disposal:
#
# file - update a single file
# dir - update an entire directory with all subdirectories recursively
# delete - delete a file OR a directory with all subdirectories recursively
#
# When using the ":delete" command, the FILES parameter is the name of the
# file or directory that is affected.
# When using the ":file" or the ":dir" command, there are 2 FILES parameters.
# The first is the source file path and the second the destination file path.
# You can add many files as you want with one single command.
#
# example:
#
# :file
# versions/24/Game.rgssad
# Game.rgssad
# versions/Fonts.zip
# fonts/fonts.dat
# :dir
# versions/24/BGM
# Audio/BGM
# :delete
# utils/screenshot.dll
# game.ico
# artwork
# :dir
# versions/24/Audio/ME
# Audio/ME
#
# The example above will do following:
#
# 1. The file "versions/24/Game.rgssad" from the RMX-OS root directory will
# be transferred to the client and store in the file "Game.rgssad" in
# his game's root directory.
# 2. The file "versions/Fonts.zip" from the RMX-OS root directory will be
# transferred to the client and stored in the file "fonts/fonts.dat" in
# his game's root directory.
# 3. The directory "versions/24/BGM" and all of its contents (recursively)
# from the RMX-OS root directory will be transferred to the client and
# store in the directory "Audio/BGM" in his game's root directory.
# 4. The file or directory "utils/screenshot.dll" will be deleted.
# 5. The file or directory "game.ico" will be deleted.
# 6. The file or directory "artwork" will be deleted.
# 7. The directory "versions/24/Audio/ME" and all of its contents
# (recursively) from the RMX-OS root directory will be transferred to
# the client and stored in the directory "Audio/ME" in his game's root
# directory.
#
#
# Notes:
#
# - There is no syntax checking or other verification of your version log
# files. It is recommended not to use batch processing with one command but
# use a command for every single file or directory.
#
# example:
#
# Instead of this type of writing:
#
# :file
# versions/24/Game.rgssad
# Game.rgssad
# versions/Fonts.zip
# fonts/fonts.dat
#
# use this type of writing:
#
# :file
# versions/24/Game.rgssad
# Game.rgssad
# :file
# versions/Fonts.zip
# fonts/fonts.dat
#
# - The constant INTERNAL_VERSION is independent from RMXOS::GAME_VERSION,
# keep that in mind.
# - Make sure to update the client scripts each time because you need to
# update the constant INTERNAL_VERSION in the configuration of this script to
# make sure the client knows it's up to date. Either update Scripts.rxdata
# or update Game.rgssad where Scripts.rxdata is contained.
# - Update is done only all at once. First all files are downloaded into a
# temporary directory. After a successful download of ALL files, first the
# files and directories that need to be delete are deleted, then the
# downloaded files are moved into the actual game's directory and are
# updated.
# - Multiple version updates work one after another and are all done at once.
# If one version update fails for some reason, the previous updates will
# not be lost.
# - It is recommended to keep version updates small. Big updates are more
# likely to break. You can simply split an update into several updates if
# it's too big.
#
#
# If you find any bugs, please report them here:
# http://forum.chaos-project.com
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
if !defined?(RMXOS) || RMXOS::VERSION < 2.0
raise 'ERROR: The "Versioning" requires RMX-OS 2.0 or higher.'
end
$rmxos_versioning = 1.21
#==============================================================================
# module RMXOS
#==============================================================================
module RMXOS
CONNECTION_VERSION_MISMATCH = 1034
UPDATING_END = 1035
#============================================================================
# module RMXOS::Options
#============================================================================
module Options
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# START Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
INTERNAL_VERSION = 1 # internal version number, use integers only
EXECUTABLE = 'Game.exe' # game executable (usually Game.exe)
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# END Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end
#============================================================================
# module RMXOS::Data
#============================================================================
module Data
AnswerNo = 'No'
AnswerYes = 'Yes'
UpdatingNow = 'Updating now. This may take a while.'
UpdatePrompt = 'Your client needs to be updated. Continue?'
UpdateRestart = 'Update finished. If the game does not restart automatically, please restart it manually.'
TempDir = ENV['TEMP'].gsub('\\', '/') + '/RMXP_update' # system temp folder
# don't change this
TempDir += '/' + Options::INTERNAL_VERSION.to_s
end
#============================================================================
# RMXOS::Network
#============================================================================
class Network
def request_connection
self.send('CON', RMXOS::VERSION, RMXOS::Options::GAME_VERSION, RMXOS::Options::INTERNAL_VERSION)
end
def request_update
if FileTest.exist?(RMXOS::Data::TempDir)
Dir.clear(RMXOS::Data::TempDir)
else
Dir.mkdirs(RMXOS::Data::TempDir)
end
@delete_queue = []
self.send('UPDT', RMXOS::Options::INTERNAL_VERSION)
end
alias check_connection_versioning_later check_connection
def check_connection(message)
case message
when /\VFAIL\Z/ # game version mismatch
@messages.push(CONNECTION_VERSION_MISMATCH)
return true
when /\AMKDIR\t(.+)/ # make directory
Dir.mkdirs("#{RMXOS::Data::TempDir}/#{$1}")
return true
when /\AFILE\t(.+)\t(.+)/ # copy file
path = "#{RMXOS::Data::TempDir}/#{$1}"
Dir.create_path(path)
data = eval($2)
file = File.open(path, 'wb')
file.write(data)
file.close
return true
when /\AFILE\t(.+)/ # copy empty file
path = "#{RMXOS::Data::TempDir}/#{$1}"
Dir.create_path(path)
file = File.open(path, 'wb')
file.close
when /\AFILC\t(.+)\t(.+)/ # append to file (in case of big files)
path = "#{RMXOS::Data::TempDir}/#{$1}"
Dir.create_path(path)
data = eval($2)
file = File.open(path, 'ab')
file.write(data)
file.close
return true
when /\ADELE\t(.+)/ # delete directory / file
@delete_queue.push($1)
return true
when /\AUEND\Z/ # updating end
Dir.clear(RMXOS::Data::TempDir)
Dir.rmdirs(RMXOS::Data::TempDir)
@messages.push(UPDATING_END)
return true
when /\AUNEX\Z/ # execute update
@delete_queue.each {|filename|
if FileTest.directory?(filename)
Dir.clear(filename)
Dir.rmdirs(filename)
else
File.delete(filename) rescue nil
end
Graphics.update}
Dir.copy(RMXOS::Data::TempDir, '.')
Dir.clear(RMXOS::Data::TempDir)
@delete_queue = []
return true
end
return check_connection_versioning_later(message)
end
end
end
#==============================================================================
# Dir
#==============================================================================
class Dir
def self.clear(source)
dirs = [source]
while dirs.size > 0
dir = dirs.shift
dirs += self.clear_non_recursive(dir)
end
end
def self.clear_non_recursive(source)
dirs = []
Dir.foreach(source) {|name|
if name != '.' && name != '..'
filename = "#{source}/#{name}"
if FileTest.directory?(filename)
dirs.push(filename)
elsif FileTest.file?(filename)
File.delete(filename) rescue nil
end
end}
return dirs
end
def self.copy(source, destination)
dirs = [[source, destination]]
while dirs.size > 0
dir = dirs.shift
Dir.mkdirs(dir[1]) if !FileTest.exist?(dir[1])
dirs += self.copy_non_recursive(dir[0], dir[1])
end
end
def self.copy_non_recursive(source, destination)
dirs = []
Dir.foreach(source) {|name|
if name != '.' && name != '..'
filename = "#{source}/#{name}"
target = "#{destination}/#{name}"
if FileTest.directory?(filename)
dirs.push([filename, target])
elsif FileTest.file?(filename)
File.copy(filename, target) rescue nil
end
end}
return dirs
end
def self.create_path(source)
dirs = source.split('/')
dirs.pop
Dir.mkdirs(dirs.join('/')) if dirs.size > 0
end
def self.mkdirs(source)
dirs = source.split('/')
path = dirs.shift
loop do
Dir.mkdir(path) rescue nil if !FileTest.exist?(path)
break if dirs.size == 0
path += '/' + dirs.shift
end
end
def self.rmdirs(source)
all_dirs = [source]
dirs = [source]
while dirs.size > 0
dir = dirs.shift
new_dirs = self.get_subdirs(dir)
dirs += new_dirs
all_dirs += new_dirs
end
all_dirs.sort.reverse.each {|dir| Dir.rmdir(dir) rescue nil}
end
def self.get_subdirs(source)
dirs = []
Dir.foreach(source) {|name|
if name != '.' && name != '..'
filename = "#{source}/#{name}"
dirs.push(filename) if FileTest.directory?(filename)
end}
return dirs
end
end
#==============================================================================
# File
#==============================================================================
class File
def self.copy(source, destination)
file = File.open(source, 'rb')
data = file.read
file.close
file = File.open(destination, 'wb')
file.write(data)
file.close
Graphics.update # prevent script is hanging error
end
end
#==============================================================================
# Window_Dialog
#==============================================================================
class Window_Dialog < Window_Command
def initialize(width, commands = ['Yes', 'No'], caption = 'Are you sure?')
commands.push('')
@caption = caption
@cursor_width = 32
super(width, commands)
if $fontface != nil
self.contents.font.name = $fontface
self.contents.font.size = $fontsize
elsif $defaultfonttype != nil
self.contents.font.name = $defaultfonttype
self.contents.font.size = $defaultfontsize
end
self.x, self.y, self.z = 320 - self.width/2, 240 - self.height/2, 1500
@item_max, self.opacity = commands.size-1, 192
commands.each {|c|
w = self.contents.text_size(c).width
@cursor_width = w if @cursor_width < w}
@cursor_width += 32
refresh
update_cursor_rect
end
def refresh
super
self.contents.font.color = system_color
self.contents.draw_text(4, 0, self.width-40, 32, @caption, 1)
end
def draw_item(index, color)
self.contents.font.color = color
rect = Rect.new(4, 32 * (index+1), self.contents.width - 8, 32)
self.contents.fill_rect(rect, Color.new(0, 0, 0, 0))
self.contents.draw_text(rect, @commands[index], 1)
end
def update_cursor_rect
if @index < 0 || @item_max == 0
self.cursor_rect.empty
else
x = (self.contents.width - @cursor_width) / 2
self.cursor_rect.set(x, (@index+1)*32, @cursor_width, 32)
end
end
end
#==============================================================================
# Scene_Servers
#==============================================================================
class Scene_Servers
alias update_versioning_later update
def update
if @dialog_window == nil
update_versioning_later
if @wait_count == 0 && @update_mode
@update_mode = nil
@help_window.set_text(@helptext)
Dir.clear(RMXOS::Data::TempDir)
Dir.rmdirs(RMXOS::Data::TempDir)
end
else
@dialog_window.update
if Input.trigger?(Input::B)
$game_system.se_play($data_system.cancel_se)
@dialog_window.dispose
@dialog_window = nil
elsif Input.trigger?(Input::C)
$game_system.se_play($data_system.decision_se)
if @dialog_window.index == 0
@help_window.set_text(RMXOS::Data::UpdatingNow)
@wait_count = RMXOS::Options::SERVER_TIMEOUT * 2
$network.request_update
@update_mode = true
end
@dialog_window.dispose
@dialog_window = nil
end
end
end
alias waiting_for_server_versioning_later waiting_for_server
def waiting_for_server
waiting_for_server_versioning_later
$network.messages.each {|message|
case message
when RMXOS::CONNECTION_VERSION_MISMATCH # version mismatch
@dialog_window = Window_Dialog.new(416, [RMXOS::Data::AnswerYes,
RMXOS::Data::AnswerNo], RMXOS::Data::UpdatePrompt)
refresh_server_states
@refresh_count = RMXOS::Options::SERVER_REFRESH
return false
when RMXOS::UPDATING_END # updating done
@update_mode = false
p RMXOS::Data::UpdateRestart
Thread.new {system(RMXOS::Options::EXECUTABLE)}
exit
return false
end}
return true
end
end