#!/usr/bin/env ruby # frozen_string_literal: true require 'optparse' require 'fileutils' require 'classes' require 'read' require 'write' def self.parse_options options = { disable_processing: Array.new(4, false) } OptionParser.new do |cmd| cmd.banner = "This tool allows to parse RPG Maker project to .txt files and back.\n\nUsage: rvpacker-txt COMMAND [options]\n\nCOMMANDS:\n read - Parses RPG Maker game files to .txt\n write - Writes parsed files back to their initial form\nOPTIONS:\n" cmd.on('-i', '--input-dir DIR', String, 'Input directory of RPG Maker project') do |dir| options[:input_dir] = File.exist?(dir) ? File.realpath(dir) : (raise "#{dir} not found") options[:output_dir] = options[:input_dir] end cmd.on('-o', '--output-dir DIR', String, 'Output directory of parsed/written files') do |dir| options[:output_dir] = File.exist?(dir) ? File.realpath(dir) : (raise "#{dir} not found") end cmd.on('--disable-processing FILES', Array, "Don't process specified files (maps, other, system, plugins)") do |files| files.each do |file| index = %w[maps other system scripts].index(file) options[:disable_processing][index] = true if index end end cmd.on('-s', '--shuffle NUM', Integer, 'Shuffle level (1: lines, 2: lines and words)') do |num| options[:shuffle_level] = num end cmd.on('--disable-custom-processing', 'Disables built-in custom parsing/writing for some games') do options[:disable_custom_processing] = true end cmd.on('-l', '--log', 'Log information while processing') do options[:logging] = true end cmd.on('-f', '--force', 'Force rewrite all files. Cannot be used with --append', 'USE WITH CAUTION!') do options[:force] = true end cmd.on('-a', '--append', 'When you update the rvpacker-txt, you probably should re-read', 'your files with append, as some new text might be added to parser', 'Cannot be used with --force') do raise '--append cannot be used beside --force.' if options[:force] options[:append] = true end cmd.on('-h', '--help', 'Show help message') do puts cmd exit end end.parse! options[:action] = ARGV.shift raise 'COMMAND argument is required. Use rvpacker-txt -h for help.' if options[:action].nil? raise 'Invalid command. Allowed commands are: read, write.' unless %w[read write].include?(options[:action]) options end def self.get_game_type(system_file_path) object = Marshal.load(File.binread(system_file_path)) game_title = object.instance_variable_get(:@game_title).to_s.downcase game_title.include?('lisa') ? 'lisa' : nil end start_time = Time.now options = parse_options input_dir = options[:input_dir] output_dir = options[:output_dir] disable_custom_processing = options[:disable_custom_processing] shuffle_level = options[:shuffle_level] logging = options[:logging] disable_processing = options[:disable_processing] force = options[:force] append = options[:append] extensions = { xp: '.rxdata', vx: 'rvdata', ace: 'rvdata2' } original_directory = Dir.glob(File.join(input_dir, '{data,original}'), File::FNM_CASEFOLD).first raise '"Data" or "original" directory not found within input directory.' unless original_directory paths = { original_path: original_directory, translation_path: File.join(input_dir, 'translation'), maps_path: File.join(input_dir, 'translation', 'maps'), other_path: File.join(input_dir, 'translation', 'other'), output_path: File.join(output_dir, 'output') } paths.each_value { |path| FileUtils.mkdir_p(path) } engine = extensions.find do |symbol, extension| symbol if File.exist?(File.join(paths[:original_path], "System.#{extension}")) end || (raise "Couldn't determine project engine.") files = Dir.glob("#{paths[:original_path]}/*#{extensions[engine]}") maps_files_paths = [] other_files_paths = [] system_file_path = nil scripts_file_path = nil files.each do |file| basename = File.basename(file) if basename.start_with?(/Map[0-9]/) maps_files_paths.push(file) elsif !basename.start_with?(/Map|Tilesets|Animations|System|Scripts|Areas/) other_files_paths.push(file) elsif basename.start_with?('System') system_file_path = file elsif basename.start_with?('Scripts') scripts_file_path = file end end ini_file_path = File.join(input_dir, "Game.ini") game_type = disable_custom_processing ? nil : get_game_type(system_file_path) wait_time = 0 processing_type = if force wait_time_start = Time.now puts "WARNING! You\'re about to forcefully rewrite all your translation files, including _trans files.\nIf you really want to do it, make sure you've made a backup of your _trans files, if you made some changes in them already.\nInput 'Y' to continue." exit unless gets.chomp == 'Y' wait_time = Time.now - wait_time_start 'force' else append ? 'append' : 'default' end puts 'Custom processing for this game is enabled. Use --disable-custom-processing to disable it.' unless game_type.nil? if options[:action] == 'read' read_map(maps_files_paths, paths[:maps_path], logging, game_type, processing_type) unless disable_processing[0] read_other(other_files_paths, paths[:other_path], logging, game_type, processing_type) unless disable_processing[1] read_system(system_file_path, ini_file_path, paths[:other_path], logging, processing_type) unless disable_processing[2] read_scripts(scripts_file_path, paths[:other_path], logging, processing_type) unless disable_processing[3] else write_map(maps_files_paths, paths[:maps_path], paths[:output_path], shuffle_level, logging, game_type, processing_type) unless disable_processing[0] write_other(other_files_paths, paths[:other_path], paths[:output_path], shuffle_level, logging, game_type, processing_type) unless disable_processing[1] write_system(system_file_path, ini_file_path, paths[:other_path], paths[:output_path], shuffle_level, logging, processing_type) unless disable_processing[2] write_scripts(scripts_file_path, paths[:other_path], paths[:output_path], logging, processing_type) unless disable_processing[3] end $wait_time = 0 if $wait_time.nil? end_time = Time.now - start_time - wait_time - $wait_time puts "Done in #{end_time}"