Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#!/usr/bin/ruby | ||
|
||
require 'get_pomo' | ||
require 'zlib' | ||
|
||
if ARGV.length != 2 | ||
STDERR.puts "usage: mklang.rb src_file out_file" | ||
exit 1 | ||
end | ||
|
||
src_file = ARGV[0] | ||
dst_file = ARGV[1] | ||
|
||
po = GetPomo::PoFile.parse(File.read(src_file)) | ||
|
||
File.open(dst_file, 'wb') do |file| | ||
file.write(Marshal.dump(po.map do |t| | ||
[Zlib.crc32(t.msgid), t.msgstr] | ||
end.to_h)) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,320 @@ | ||
#!/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 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) |