mirror of
https://forgejo.xatacraft.ru/Sunshine/mkxp-sunshine.git
synced 2026-08-19 21:39:57 +00:00
336 lines
7.7 KiB
Ruby
336 lines
7.7 KiB
Ruby
#!/usr/bin/ruby
|
|
require 'zlib'
|
|
|
|
# Dummy RGSS template for unmarshalling
|
|
module RPG
|
|
# Useful
|
|
class Actor
|
|
attr_reader :name
|
|
end
|
|
class Map
|
|
attr_reader :events
|
|
end
|
|
class MapInfo
|
|
attr_reader :name
|
|
attr_reader :parent_id
|
|
end
|
|
class Event
|
|
attr_reader :name
|
|
attr_reader :pages
|
|
class Page
|
|
attr_reader :list
|
|
class Condition
|
|
end
|
|
class Graphic
|
|
end
|
|
end
|
|
end
|
|
class EventCommand
|
|
attr_reader :code
|
|
attr_reader :parameters
|
|
end
|
|
class CommonEvent
|
|
attr_reader :name
|
|
attr_reader :list
|
|
end
|
|
class Item
|
|
attr_reader :name
|
|
attr_reader :description
|
|
end
|
|
|
|
# Useless
|
|
class AudioFile
|
|
end
|
|
class MoveRoute
|
|
end
|
|
class MoveCommand
|
|
end
|
|
end
|
|
class Table
|
|
def each_with_index
|
|
x = 0
|
|
y = 0
|
|
z = 0
|
|
while z < self.sizez
|
|
while y < self.sizey
|
|
while x < self.sizex
|
|
yield(self[x, y, z], x, y, z)
|
|
x += 1
|
|
end
|
|
y += 1
|
|
end
|
|
z += 1
|
|
end
|
|
end
|
|
|
|
def self._load(foo)
|
|
end
|
|
end
|
|
class Color
|
|
def self._load(foo)
|
|
end
|
|
end
|
|
class Tone
|
|
def self._load(foo)
|
|
end
|
|
end
|
|
|
|
# Various escape functions
|
|
# https://stackoverflow.com/questions/8639642/best-way-to-escape-and-unescape-strings-in-ruby
|
|
module Escape
|
|
# Ruby
|
|
UNESCAPES = {
|
|
'a' => "\x07", 'b' => "\x08", 't' => "\x09",
|
|
'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
|
|
'r' => "\x0d", 'e' => "\x1b", "\\" => "\x5c",
|
|
'"' => "\x22", "'" => "\x27"
|
|
}
|
|
ESCAPES = {
|
|
"\x07" => 'a', "\x08" => 'b', "\x09" => 't',
|
|
"\x0a" => 'n', "\x0b" => 'v', "\x0c" => 'f',
|
|
"\x0d" => 'r', "\x1b" => 'e', "\x5c" => "\\",
|
|
"\x22" => '"'
|
|
}
|
|
|
|
def self.unescape(str)
|
|
str.gsub(/\\(?:([#{Regexp.escape(UNESCAPES.keys.join)}])|u([\da-fA-F]{4}))|\\0?x([\da-fA-F]{2})/) do
|
|
if $1
|
|
UNESCAPES[$1]
|
|
elsif $2 # escape \u0000 unicode
|
|
["#$2".hex].pack('U*')
|
|
elsif $3 # escape \0xff or \xff
|
|
[$3].pack('H2')
|
|
end
|
|
end
|
|
end
|
|
|
|
# gettext
|
|
def self.escape(str)
|
|
str.gsub(/[#{Regexp.escape(ESCAPES.keys.join)}]/) do |m|
|
|
"\\" + ESCAPES[m]
|
|
end
|
|
end
|
|
end
|
|
|
|
class StringList
|
|
class Entry
|
|
attr_accessor :string
|
|
attr_accessor :contexts
|
|
attr_accessor :comments
|
|
|
|
def initialize(string)
|
|
@string = string
|
|
@contexts = []
|
|
@comments = []
|
|
end
|
|
|
|
def add(context, comment)
|
|
@contexts << context unless @contexts.include?(context) || context == nil
|
|
case comment
|
|
when String
|
|
@comments << comment unless @comments.include?(comment)
|
|
when Array
|
|
comment.each do |com|
|
|
@comments << com unless @comments.include?(com)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def initialize
|
|
@strings = Hash.new { |k, v| k[v] = Entry.new(v) }
|
|
@order = []
|
|
end
|
|
|
|
def add(string, context, comment)
|
|
@order << string unless @strings.include? string
|
|
@strings[string].add(context, comment)
|
|
end
|
|
|
|
def dump(filename)
|
|
File.open(filename, 'w') do |file|
|
|
# Write header
|
|
file.puts '# Translation template for OneShot'
|
|
file.puts
|
|
file.puts 'msgid ""'
|
|
file.puts 'msgstr ""'
|
|
file.puts
|
|
@order.each do |str|
|
|
entry = @strings[str]
|
|
entry.comments.each do |comment|
|
|
file.puts "#. #{comment}"
|
|
end
|
|
unless entry.contexts.empty?
|
|
file.puts "#: #{entry.contexts.join(' ')}"
|
|
end
|
|
file.puts "msgid \"#{Escape.escape(entry.string)}\""
|
|
file.puts "msgstr \"\""
|
|
file.puts
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def load_data(filename)
|
|
Marshal.load(File.read(filename))
|
|
end
|
|
|
|
# Script
|
|
if ARGV.size < 2
|
|
STDERR.puts "usage: mklangsrc.rb game_dir out_file"
|
|
exit 1
|
|
end
|
|
game_dir = ARGV[0]
|
|
out_file = ARGV[1]
|
|
|
|
strlist = StringList.new
|
|
|
|
# Actors
|
|
load_data(File.join(game_dir, 'Data/Actors.rxdata')).each_with_index do |actor, i|
|
|
next if !actor || actor.name.empty?
|
|
strlist.add(actor.name, "Actors:#{i}", 'actor name')
|
|
end
|
|
|
|
# Items
|
|
load_data(File.join(game_dir, 'Data/Items.rxdata')).each_with_index do |item, i|
|
|
next if !item || item.name.empty?
|
|
strlist.add(item.name, "Items:#{i}", 'item name')
|
|
next if item.description.empty?
|
|
strlist.add(item.description, "Items:#{i}", 'item description')
|
|
end
|
|
|
|
# Scripts
|
|
load_data(File.join(game_dir, 'Data/xScripts.rxdata')).each do |script|
|
|
script_name = script[1]
|
|
comment = nil
|
|
Zlib::Inflate.inflate(script[2]).each_line.with_index do |line, line_num|
|
|
# Scan for tr() calls
|
|
line.scan(/(?:^|[^_A-Za-z0-9])tr\s*\(\s*(?:'((?:\\.|[^'])*)'|"((?:\\.|[^"])*)")\s*\)/) do |single, double|
|
|
string = single || double
|
|
string = Escape.unescape(string)
|
|
next if string =~ /^\s*$/
|
|
strlist.add(string, "Scripts/#{script_name}:#{line_num+1}", comment)
|
|
end
|
|
# Scan for any comments on this line
|
|
m = line.match(/^(?:[^"'#]|"(?:\\"|[^"])*"|'(?:\\'|[^'])*')*(?:#\s*(.*))?$/)
|
|
if !m || m.captures.empty?
|
|
comment = nil
|
|
else
|
|
comment = m.captures.first
|
|
end
|
|
end
|
|
end
|
|
|
|
# Yields all translatable EdText strings
|
|
def tr_edtext(script)
|
|
# Scan for tr() calls
|
|
script.scan(/(?:^|[^_A-Za-z0-9])EdText\.\w+\s*\(\s*(?:'((?:\\.|[^'])*)'|"((?:\\.|[^"])*)")\s*\)/m) do |single, double|
|
|
string = single || double
|
|
string = Escape.unescape(string).gsub(/\s+/, ' ').gsub(/^\s+/, '').gsub(/\s+$/, '')
|
|
next if string =~ /^\s*$/
|
|
yield string
|
|
end
|
|
end
|
|
|
|
# Events
|
|
def parse_event_list(strlist, name, page, list)
|
|
# Parse commands
|
|
if page == nil
|
|
context = name
|
|
else
|
|
context = "#{name}:#{page}"
|
|
end
|
|
comment = nil
|
|
i = 0
|
|
while i < list.size
|
|
case list[i].code
|
|
when 101
|
|
# Message box
|
|
string = list[i].parameters[0].rstrip
|
|
loop do
|
|
i += 1
|
|
break unless list[i].code == 401
|
|
string << " " << list[i].parameters[0].rstrip
|
|
end
|
|
string.gsub!(/\s*\\n\s*/, '\\n')
|
|
string.strip!
|
|
unless string.empty?
|
|
strlist.add(string, "#{context}:#{i}:text", comment)
|
|
end
|
|
comment = nil
|
|
when 102
|
|
# Choices
|
|
list[i].parameters[0].each_with_index do |choice, j|
|
|
unless choice.empty?
|
|
strlist.add(choice, "#{context}:#{i}:choice:#{j}", comment)
|
|
end
|
|
end
|
|
i += 1
|
|
comment = nil
|
|
when 108
|
|
# Comment
|
|
comment = []
|
|
loop do
|
|
comment << list[i].parameters[0].rstrip
|
|
i += 1
|
|
break unless list[i].code == 408
|
|
end
|
|
when 111
|
|
# Conditional branch (may contain script text)
|
|
if list[i].parameters[0] == 12
|
|
tr_edtext(list[i].parameters[1]) do |string|
|
|
strlist.add(string, "#{context}:#{i}:condition", comment)
|
|
end
|
|
end
|
|
i += 1
|
|
comment = nil
|
|
when 355
|
|
# Script
|
|
script = list[i].parameters[0]
|
|
loop do
|
|
i += 1
|
|
break unless list[i].code == 655
|
|
script += "\n" + list[i].parameters[0]
|
|
end
|
|
tr_edtext(script) do |string|
|
|
strlist.add(string, "#{context}:#{i}:script", comment)
|
|
end
|
|
comment = nil
|
|
else
|
|
i += 1
|
|
comment = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
# Do common events
|
|
load_data(File.join(game_dir, 'Data/CommonEvents.rxdata')).each do |event|
|
|
parse_event_list(strlist, 'CommonEvents/' + event.name, nil, event.list) if event
|
|
end
|
|
|
|
# Do map events
|
|
map_info = load_data(File.join(game_dir, 'Data/MapInfos.rxdata'))
|
|
map_info.each do |map_id, foo|
|
|
# Construct full name of map
|
|
map_name = ''
|
|
i = map_id
|
|
begin
|
|
map_name.insert(0, "/" + map_info[i].name)
|
|
i = map_info[i].parent_id
|
|
end while i > 0
|
|
|
|
# Load map
|
|
map = Marshal.load(File.read(File.join(game_dir, sprintf('Data/Map%03d.rxdata', map_id))))
|
|
|
|
# Parse each event
|
|
map.events.sort.each do |_, event|
|
|
# Construct full name of event
|
|
name = "Maps#{map_name}/#{event.name}"
|
|
# Iterate through pages
|
|
event.pages.each_with_index do |page, i|
|
|
parse_event_list(strlist, name, i + 1, page.list)
|
|
end
|
|
end
|
|
end
|
|
|
|
strlist.dump(out_file)
|