Class: WolfTrans::Patch
- Inherits:
-
Object
- Object
- WolfTrans::Patch
- Defined in:
- lib/wolftrans.rb,
lib/wolftrans/patch_data.rb,
lib/wolftrans/patch_text.rb
Instance Method Summary collapse
-
#apply(out_dir) ⇒ Object
Apply the patch to the files in the game path and write them to the output directory.
-
#initialize(game_path, patch_path) ⇒ Patch
constructor
A new instance of Patch.
- #load_data(game_dir) ⇒ Object
-
#load_patch(patch_dir) ⇒ Object
Loading Patch data #.
-
#process_patch_file(filename, mode) ⇒ Object
Load the translation strings indicated in the patch file, generate a new patch file with updated context information, and overwrite the patch.
Constructor Details
#initialize(game_path, patch_path) ⇒ Patch
Returns a new instance of Patch.
10 11 12 13 14 |
# File 'lib/wolftrans.rb', line 10 def initialize(game_path, patch_path) @strings = Hash.new { |hash, key| hash[key] = Hash.new } load_data(game_path) load_patch(patch_path) end |
Instance Method Details
#apply(out_dir) ⇒ Object
Apply the patch to the files in the game path and write them to the output directory
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/wolftrans/patch_data.rb', line 111 def apply(out_dir) out_dir = Util.sanitize_path(out_dir) out_data_dir = "#{out_dir}/Data" # Clear out directory FileUtils.rm_rf(out_dir) FileUtils.mkdir_p("#{out_data_dir}/BasicData") #TODO create directories for each asset # Patch the databases @databases.each do |db_name, db| db.types.each_with_index do |type, type_index| next if type.name.empty? type.data.each_with_index do |datum, datum_index| datum.each_translatable do |str, field| context = Context::Database.from_data(db_name, type_index, type, datum_index, datum, field) yield_translation(str, context) do |newstr| datum[field] = newstr end end end end name_noext = "#{out_data_dir}/BasicData/#{db_name}" db.dump("#{name_noext}.project", "#{name_noext}.dat") end # Patch the common events @common_events.events.each do |event| event.commands.each_with_index do |command, cmd_index| context = Context::CommonEvent.from_data(event, cmd_index, command) patch_command(command, context) end end @common_events.dump("#{out_data_dir}/BasicData/CommonEvent.dat") # Patch Game.dat patch_game_dat @game_dat.dump("#{out_dir}/#{@game_dat_filename}") # Patch all the maps @maps.each do |map_name, map| map.events.each do |event| next unless event event.pages.each do |page| page.commands.each_with_index do |command, cmd_index| context = Context::MapEvent.from_data(map_name, event, page, cmd_index, command) patch_command(command, context) end end end # Translate path assetpath = @assets["mapdata/#{map_name.downcase}.mps"] fullpath = "#{out_data_dir}/#{assetpath}" map.dump(fullpath) end # Copy remaining BasicData files copy_data_files(Util.join_path_nocase(@game_data_dir, 'basicdata'), ['xxxxx', 'dat', 'project', 'png'], "#{out_data_dir}/BasicData") # Copy remaining assets @assets.each_pair do |fn, newfn| filename = get_asset_filename(fn) next unless filename FileUtils.cp(filename, "#{out_data_dir}/#{newfn}") end # Copy fonts if @patch_data_dir copy_data_files(@patch_data_dir, ['ttf','ttc','otf'], out_data_dir) end copy_data_files(@game_data_dir, ['ttf','ttc','otf'], out_data_dir) # Copy remainder of files in the base patch/game dirs copy_files(@patch_assets_dir, out_dir) copy_files(@game_dir, out_dir) end |
#load_data(game_dir) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/wolftrans/patch_data.rb', line 12 def load_data(game_dir) @game_dir = Util.sanitize_path(game_dir) unless Dir.exist? @game_dir raise "could not find game folder '#{@game_dir}'" end @game_data_dir = Util.join_path_nocase(@game_dir, 'data') if @game_data_dir == nil raise "could not find Data folder in '#{@game_dir}'" end # Load databases, Game.dat, and common events @databases = {} basicdata_dir = Util.join_path_nocase(@game_data_dir, 'basicdata') if basicdata_dir == nil raise "could not find BasicData folder in '#{@game_data_dir}'" end Dir.entries(basicdata_dir).each do |entry| entry_downcase = entry.downcase filename = "#{basicdata_dir}/#{entry}" if entry_downcase == 'game.dat' @game_dat_filename = 'Data/BasicData/Game.dat' load_game_dat(filename) elsif entry_downcase.end_with?('.project') next if entry_downcase == 'sysdatabasebasic.project' basename = File.basename(entry_downcase, '.*') dat_filename = Util.join_path_nocase(basicdata_dir, "#{basename}.dat") next if dat_filename == nil load_game_database(filename, dat_filename) elsif entry_downcase == 'commonevent.dat' load_common_events(filename) end end # Game.dat is in a different place on older versions unless @game_dat Dir.entries(@game_dir).each do |entry| if entry.downcase == 'game.dat' @game_dat_filename = 'Game.dat' load_game_dat("#{@game_dir}/#{entry}") break end end end # Gather list of asset and map filenames map_names = Set.new @assets = {} @databases.each_value do |db| db.each_filename do |fn| fn_downcase = fn.downcase @assets[fn_downcase] = fn if fn_downcase.end_with?('.mps') map_names.add(File.basename(fn_downcase, '.*')) end end end @game_dat.each_filename do |fn| @assets[fn.downcase] = fn end @common_events.each_filename do |fn| @assets[fn.downcase] = fn end # Load maps maps_path = Util.join_path_nocase(@game_data_dir, 'mapdata') if maps_path == nil raise "could not find MapData folder in '#{@game_data_dir}'" end @maps = {} map_names.each do |name| map_path = Util.join_path_nocase(maps_path, name + '.mps') if map_path == nil STDERR.puts "warn: could not find map '#{name}'" next end load_map(map_path) end # Gather remaining asset filenames @maps.each_value do |map| map.each_filename do |fn| @assets[fn.downcase] = fn end end # Make sure not to treat certain kinds of filenames as assets @assets.reject! { |k, v| k.start_with?('save/') } # Rewrite asset filenames extcounts = Hash.new(0) @assets.keys.sort.each do |fn| ext = File.extname(fn)[1..-1] @assets[fn] = '%04d.%s' % [extcounts[ext], ext] extcounts[ext] += 1 end end |
#load_patch(patch_dir) ⇒ Object
Loading Patch data #
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/wolftrans/patch_text.rb', line 11 def load_patch(patch_dir) @patch_dir = Util.sanitize_path(patch_dir) @patch_assets_dir = "#{@patch_dir}/Assets" @patch_strings_dir = "#{@patch_dir}/Patch" # Make sure these directories all exist [@patch_assets_dir, @patch_strings_dir].each do |dir| FileUtils.mkdir_p dir end # Find data dir @patch_data_dir = Util.join_path_nocase(@patch_assets_dir, 'data') # Load blacklist @file_blacklist = [] if File.exists? "#{patch_dir}/blacklist.txt" Util.read_txt("#{patch_dir}/blacklist.txt").each_line do |line| line.strip! next if line.empty? if line.include? '\\' raise "file specified in blacklist contains a backslash (use a forward slash instead)" end @file_blacklist << line.downcase! end end # Load strings Find.find(@patch_strings_dir) do |path| next if FileTest.directory? path next unless File.extname(path).casecmp '.txt' process_patch_file(path, :load) end # Write back to patch files processed_filenames = [] Find.find(@patch_strings_dir) do |path| next if FileTest.directory? path next unless File.extname(path).casecmp '.txt' process_patch_file(path, :update) processed_filenames << path[@patch_strings_dir.length+1..-1] end # Now "process" any files that should be generated @strings.each do |string, contexts| contexts.each do |context, trans| unless processed_filenames.include? trans.patch_filename process_patch_file("#{@patch_strings_dir}/#{trans.patch_filename}", :update) processed_filenames << trans.patch_filename end end end end |
#process_patch_file(filename, mode) ⇒ Object
Load the translation strings indicated in the patch file, generate a new patch file with updated context information, and overwrite the patch
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/wolftrans/patch_text.rb', line 68 def process_patch_file(filename, mode) patch_filename = filename[@patch_strings_dir.length+1..-1] txt_version = nil # Parser state information state = :expecting original_string = '' contexts = [] translated_string = '' new_contexts = nil # Variables for the revised patch context_comments = {} # The revised patch output = '' output_write = false pristine_translated_string = '' if File.exists? filename output_write = true if mode == :update Util.read_txt(filename).each_line.with_index do |pristine_line, index| # Remove comments and strip pristine_line.gsub!(/\n$/, '') line = pristine_line.gsub(/(?!\\)#.*$/, '').rstrip comment = pristine_line.match(/(?<!\\)#.*$/).to_s.rstrip line_num = index + 1 if line.start_with? '>' instruction = line.gsub(/^>\s+/, '') # Parse the patch version parse_instruction(instruction, 'WOLF TRANS PATCH FILE VERSION') do |args| unless txt_version == nil raise "two version strings in file (line #{line_num})" end txt_version = Version.new(str: args.first) if txt_version > TXT_VERSION raise "patch version (#{new_version}) newer than can be read (#{TXT_VERSION})" end if mode == :update output << "> WOLF TRANS PATCH FILE VERSION #{TXT_VERSION}" output << comment unless comment.empty? output << "\n" end end # Make sure we have a version specified before reading other instructions if txt_version == nil raise "no version specified before first instruction" end # Now parse the instructions parse_instruction(instruction, 'BEGIN STRING') do |args| unless state == :expecting raise "began another string without ending previous string (line #{line_num})" end state = :reading_original original_string = '' if mode == :update output << pristine_line << "\n" end end parse_instruction(instruction, 'END STRING') do |args| if state == :expecting raise "ended string without a begin (line #{line_num})" elsif state == :reading_original raise "ended string without a translation block (line #{line_num})" end state = :expecting new_contexts = [] end parse_instruction(instruction, 'CONTEXT') do |args| if state == :expecting raise "context outside of begin/end block (line #{line_num})" end if args.empty? raise "no context string provided in context line (line #{line_num})" end # After a context, we're no longer reading the original text. state = :reading_translation begin new_context = Context.from_string(args.shift) rescue => e raise e, "#{e} (line #{line_num})", e.backtrace end # Append context if translated_string is empty, since that means # no translation was given. if translated_string.empty? contexts << new_context else new_contexts = [new_context] end if mode == :update # Save the comment for later context_comments[new_context] = comment end end # If we have a new context list queued, flush the translation to all # of the collected contexts if new_contexts original_string_new = unescape_string(original_string, false) translated_string_new = unescape_string(translated_string, true) contexts.each do |context| if mode == :update # Write an appropriate context line to the output output << '> CONTEXT ' if @strings.include?(original_string_new) && @strings[original_string_new].include?(context) output << @strings[original_string_new].select { |k,v| k.eql? context }.keys.first.to_s output << ' < UNTRANSLATED' if translated_string_new.empty? else output << context.to_s << ' < UNUSED' end output << " " << context_comments[context] unless comment.empty? output << "\n" else # Put translation in hash @strings[original_string_new][context] = Translation.new(patch_filename, translated_string_new, false) end end if mode == :update # Write the translation output << pristine_translated_string.rstrip << "\n" # If the state is "expecting", that means we need to write the END STRING # line to the output too. if state == :expecting output << pristine_line << "\n" end end # Reset variables for next read translated_string = '' pristine_translated_string = '' contexts = new_contexts new_contexts = nil end else # Parse text if state == :expecting unless line.empty? raise "stray text outside of begin/end block (line #{line_num})" end elsif state == :reading_original original_string << line << "\n" elsif state == :reading_translation translated_string << line << "\n" if mode == :update pristine_translated_string << pristine_line << "\n" end end # Make no modifications to the patch line if we're not reading translations unless state == :reading_translation if mode == :update output << pristine_line << "\n" end end end end # Final error checking if state != :expecting raise "final begin/end block has no end" end else # It's a new file, so just stick a header on it if mode == :update output << "> WOLF TRANS PATCH FILE VERSION #{TXT_VERSION}\n" end end if mode == :update # Write all the new strings to the file @strings.each do |orig_string, contexts| if contexts.values.any? { |trans| trans.autogenerate? && trans.patch_filename == patch_filename } output_write = true output << "\n> BEGIN STRING\n#{escape_string(orig_string)}\n" contexts.each do |context, trans| next unless trans.autogenerate? trans.autogenerate = false output << "> CONTEXT " << context.to_s << " < UNTRANSLATED\n" end output << "\n> END STRING\n" end end # Write the output to the file if output_write FileUtils.mkdir_p(File.dirname(filename)) File.open(filename, 'wb') { |file| file.write(output) } end end end |