Merge branch 'master' of github.com:glenux/xtm-utils
This commit is contained in:
commit
31da8a7201
6 changed files with 286 additions and 183 deletions
128
bin/xtminfo
128
bin/xtminfo
|
@ -1,84 +1,82 @@
|
||||||
#!/usr/bin/ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
require 'optparse'
|
require 'optparse'
|
||||||
|
require 'xtmfile/header'
|
||||||
|
|
||||||
require 'rubygems'
|
module XtmFile
|
||||||
require 'bindata'
|
class XtmInfo
|
||||||
|
class XtmInfoArgumentError < ArgumentError ; end
|
||||||
|
|
||||||
require 'xtmfile/xtmheader'
|
attr_reader :opts
|
||||||
|
|
||||||
|
|
||||||
class XtmInfo
|
def initialize args
|
||||||
class XtmInfoArgumentError < ArgumentError ; end
|
@args = []
|
||||||
|
@input_xtm = nil
|
||||||
attr_reader :opts
|
parse_command_line args
|
||||||
|
|
||||||
|
|
||||||
def initialize args
|
|
||||||
@args = []
|
|
||||||
@input_xtm = nil
|
|
||||||
parse_command_line args
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_command_line args
|
|
||||||
@args = args.clone
|
|
||||||
@opts = OptionParser.new do |opts|
|
|
||||||
opts.banner = "Usage: #{File.basename $0} [options]\n"
|
|
||||||
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Mandatory options"
|
|
||||||
|
|
||||||
opts.on("-i", "--input FILE", "Input XTM file") do |r|
|
|
||||||
@input_xtm = r
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "General options:"
|
|
||||||
|
|
||||||
opts.on("-h", "--help", "Show this help") do |h|
|
|
||||||
@help = h
|
|
||||||
end
|
|
||||||
opts.on("-v", "--verbose", "Show warnings too") do |v|
|
|
||||||
@verbose = v
|
|
||||||
end
|
|
||||||
opts.separator ""
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def validate!
|
def parse_command_line args
|
||||||
@opts.parse!
|
@args = args.clone
|
||||||
|
@opts = OptionParser.new do |opts|
|
||||||
|
opts.banner = "Usage: #{File.basename $0} [options]\n"
|
||||||
|
|
||||||
raise XtmInfoArgumentError, "No input XTM file specified!" if @input_xtm.nil?
|
opts.separator ""
|
||||||
end
|
opts.separator "Mandatory options"
|
||||||
|
|
||||||
|
opts.on("-i", "--input FILE", "Input XTM file") do |r|
|
||||||
|
@input_xtm = r
|
||||||
|
end
|
||||||
|
|
||||||
def run
|
opts.separator ""
|
||||||
validate!
|
opts.separator "General options:"
|
||||||
|
|
||||||
File.open( @input_xtm, "rb" ) do |io|
|
opts.on("-h", "--help", "Show this help") do |h|
|
||||||
header = XtmHeader::read io
|
@help = h
|
||||||
puts header
|
end
|
||||||
|
opts.on("-v", "--verbose", "Show warnings too") do |v|
|
||||||
|
@verbose = v
|
||||||
|
end
|
||||||
|
opts.separator ""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.main args
|
def validate!
|
||||||
xj = nil
|
@opts.parse!
|
||||||
begin
|
|
||||||
xj = XtmInfo.new args
|
|
||||||
xj.run
|
|
||||||
exit 0
|
|
||||||
rescue XtmInfoArgumentError => e
|
|
||||||
STDERR.puts "%s" % xj.opts
|
|
||||||
STDERR.puts "error: %s" % e.message
|
|
||||||
|
|
||||||
exit 1
|
raise XtmInfoArgumentError, "No input XTM file specified!" if @input_xtm.nil?
|
||||||
rescue SystemExit => e
|
end
|
||||||
raise e
|
|
||||||
rescue Exception => e
|
|
||||||
STDERR.puts "error: %s" % e.message
|
def run
|
||||||
exit 1
|
validate!
|
||||||
|
|
||||||
|
File.open( @input_xtm, "rb" ) do |io|
|
||||||
|
header = Header::read io
|
||||||
|
puts header.to_summary_string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.main args
|
||||||
|
xj = nil
|
||||||
|
begin
|
||||||
|
xj = XtmInfo.new args
|
||||||
|
xj.run
|
||||||
|
exit 0
|
||||||
|
rescue XtmInfoArgumentError => e
|
||||||
|
STDERR.puts "%s" % xj.opts
|
||||||
|
STDERR.puts "error: %s" % e.message
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
rescue SystemExit => e
|
||||||
|
raise e
|
||||||
|
rescue Exception => e
|
||||||
|
STDERR.puts "error: %s" % e.message
|
||||||
|
STDERR.puts e.backtrace
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
XtmInfo.main ARGV
|
XtmFile::XtmInfo.main ARGV
|
||||||
|
|
90
bin/xtmjoin
90
bin/xtmjoin
|
@ -5,15 +5,15 @@ require 'optparse'
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'bindata'
|
require 'bindata'
|
||||||
|
|
||||||
require 'xtmfile/xtmheader'
|
require 'xtmfile/header'
|
||||||
|
require 'xtmfile/joiner'
|
||||||
|
require 'xtmfile/splitter'
|
||||||
|
|
||||||
class XtmJoin
|
class XtmJoin
|
||||||
class XtmJoinArgumentError < ArgumentError ; end
|
class XtmJoinArgumentError < ArgumentError ; end
|
||||||
|
|
||||||
attr_reader :opts
|
attr_reader :opts
|
||||||
|
|
||||||
BUFFER_MAX_SIZE = 4096 * 4096
|
|
||||||
|
|
||||||
def initialize args
|
def initialize args
|
||||||
@args = []
|
@args = []
|
||||||
|
@ -27,18 +27,24 @@ class XtmJoin
|
||||||
opts.banner = "Usage: #{File.basename $0} [options]\n"
|
opts.banner = "Usage: #{File.basename $0} [options]\n"
|
||||||
|
|
||||||
opts.separator ""
|
opts.separator ""
|
||||||
opts.separator "Mandatory options"
|
opts.separator "Options"
|
||||||
|
|
||||||
opts.on("-i", "--input FILE", "Input XTM file") do |r|
|
opts.on("-i", "--input FILE", "Input XTM file (mandatory)") do |r|
|
||||||
@input_filename = r
|
@input_filename = r
|
||||||
end
|
end
|
||||||
|
|
||||||
opts.separator ""
|
opts.on("-o", "--output FILE", "Output file (optional)") do |o|
|
||||||
opts.separator "General options:"
|
@output_filename = o
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.on("-m", "--md5", "Verify MD5 sums") do |m|
|
||||||
|
@verify_md5 = m
|
||||||
|
end
|
||||||
|
|
||||||
opts.on("-h", "--help", "Show this help") do |h|
|
opts.on("-h", "--help", "Show this help") do |h|
|
||||||
@help = h
|
@help = h
|
||||||
end
|
end
|
||||||
|
|
||||||
opts.on("-v", "--verbose", "Show warnings too") do |v|
|
opts.on("-v", "--verbose", "Show warnings too") do |v|
|
||||||
@verbose = v
|
@verbose = v
|
||||||
end
|
end
|
||||||
|
@ -56,73 +62,25 @@ class XtmJoin
|
||||||
@input_filename = File.expand_path @input_filename
|
@input_filename = File.expand_path @input_filename
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def run
|
def run
|
||||||
validate!
|
validate!
|
||||||
|
|
||||||
output_file = nil
|
|
||||||
# initial file
|
|
||||||
|
|
||||||
in_xtm = File.open @input_filename, "rb"
|
|
||||||
header = XtmHeader::read in_xtm
|
|
||||||
|
|
||||||
output_file = header.filename_str
|
|
||||||
puts "Writing data to %s" % output_file
|
|
||||||
|
|
||||||
# FIXME: prevent overwriting
|
# FIXME: prevent overwriting
|
||||||
out_xtm = File.open output_file, "wb"
|
|
||||||
|
xtmj = XtmFile::Joiner.new @input_filename, @output_filename
|
||||||
|
puts "Writing data to %s" % xtmj.output_filename
|
||||||
|
|
||||||
cur_xtm = @input_filename
|
xtmj.start do |event, info|
|
||||||
is_first = true
|
case event
|
||||||
cur_size = header.filesize
|
when XtmFile::Joiner::EVENT_OPENFILE then
|
||||||
print "\x1b[s"
|
puts "Opening %s" % info
|
||||||
while cur_size > 0 do
|
when XtmFile::Joiner::EVENT_PROGRESS then
|
||||||
unless is_first then
|
print "\x1b[uProgress : %s %" % info
|
||||||
cur_xtm = _nextfile cur_xtm
|
|
||||||
puts "Opening %s" % cur_xtm
|
|
||||||
in_xtm = File.open cur_xtm, "rb"
|
|
||||||
end
|
|
||||||
while cur_size > 0 and (not in_xtm.eof?) do
|
|
||||||
pcent = ((header.filesize - cur_size) * 100 / header.filesize)
|
|
||||||
|
|
||||||
STDOUT.print "\x1b[uProgress : %s %" % pcent
|
|
||||||
STDOUT.flush
|
STDOUT.flush
|
||||||
|
|
||||||
read_size = if cur_size > BUFFER_MAX_SIZE then BUFFER_MAX_SIZE
|
|
||||||
else cur_size
|
|
||||||
end
|
|
||||||
buffer = in_xtm.read read_size
|
|
||||||
cur_size = cur_size - buffer.length
|
|
||||||
out_xtm.write buffer.slice(0, buffer.length)
|
|
||||||
end
|
end
|
||||||
is_first = false
|
|
||||||
in_xtm.close
|
|
||||||
end
|
end
|
||||||
out_xtm.close
|
|
||||||
STDOUT.puts ""
|
|
||||||
|
|
||||||
# remaining files
|
STDOUT.puts ""
|
||||||
end
|
|
||||||
|
|
||||||
def _nextfile curfile
|
|
||||||
result = nil
|
|
||||||
cur_idx = 0
|
|
||||||
cur_len = 0
|
|
||||||
case curfile
|
|
||||||
when /\.([0-9]+)\.xtm$/ then
|
|
||||||
cur_idx = $1.to_i
|
|
||||||
cur_len = $1.length
|
|
||||||
next_idx = cur_idx + 1
|
|
||||||
result = curfile.gsub(/\.([0-9]+)\.xtm$/, ".%0#{cur_len}d.xtm" % next_idx)
|
|
||||||
when /\.xtm\.([0-9]+)$/ then
|
|
||||||
cur_idx = $1.to_i
|
|
||||||
cur_len = $1.length
|
|
||||||
next_idx = cur_idx + 1
|
|
||||||
result = curfile.gsub(/\.xtm\.([0-9]+)$/, ".xtm.%0#{cur_len}d" % next_idx)
|
|
||||||
else
|
|
||||||
raise "Unable to detect a naming patterng for file sequence!"
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.main args
|
def self.main args
|
||||||
|
|
91
xtmfile/header.rb
Normal file
91
xtmfile/header.rb
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
|
gem 'bindata', '~> 1.2.1'
|
||||||
|
require 'bindata'
|
||||||
|
|
||||||
|
module XtmFile
|
||||||
|
|
||||||
|
#
|
||||||
|
# Offset | Information | Size (bytes)
|
||||||
|
# --------------------------------------------
|
||||||
|
# 0 | Software name length | 1
|
||||||
|
# 1 | Software name | 20
|
||||||
|
# 21 | Version length | 1
|
||||||
|
# 22 | Version | 4
|
||||||
|
# 26 | Not used | 10
|
||||||
|
# 36 | Date | 4
|
||||||
|
# 40 | Filename length | 1
|
||||||
|
# 41 | Filename | 50
|
||||||
|
# 91 | MD5 Enable | 1
|
||||||
|
# 92 | File parts count | 4
|
||||||
|
# 96 | Original file size | 8
|
||||||
|
#
|
||||||
|
class Header < BinData::Record
|
||||||
|
endian :little
|
||||||
|
|
||||||
|
uint8 :progname_len, :value => lambda { progname_str.length }
|
||||||
|
string :progname_str, :length => 20
|
||||||
|
|
||||||
|
uint8 :version_len, :value => lambda { version_str.length }
|
||||||
|
string :version_str, :length => 4
|
||||||
|
|
||||||
|
string :unused, :length => 10
|
||||||
|
|
||||||
|
uint32le :date
|
||||||
|
|
||||||
|
uint8 :filename_len
|
||||||
|
string :filename_str, :read_length => :filename_len
|
||||||
|
# fake padding
|
||||||
|
string :filename_str_pad, :read_length => lambda { 50 - filename_len }
|
||||||
|
|
||||||
|
uint8 :md5
|
||||||
|
|
||||||
|
uint32 :filecount
|
||||||
|
uint64 :filesize
|
||||||
|
|
||||||
|
def to_full_string
|
||||||
|
r = []
|
||||||
|
r << "[%03d] Progname len : %s" % [self.progname_len.offset, self.progname_len]
|
||||||
|
r << "[%03d] Progname str : %s" % [self.progname_str.offset, self.progname_str]
|
||||||
|
r << "[%03d] Version len : %s" % [self.version_len.offset, self.version_len]
|
||||||
|
r << "[%03d] Version str : %s" % [self.version_str.offset, self.version_str]
|
||||||
|
r << "[%03d] Unused : %s" % [self.unused.offset, self.unused]
|
||||||
|
r << "[%03d] Date : %s" % [self.date.offset, self.date]
|
||||||
|
r << "[%03d] Filename len : %s" % [self.filename_len.offset, self.filename_len]
|
||||||
|
r << "[%03d] Filename str : %s" % [self.filename_str.offset, self.filename_str]
|
||||||
|
r << "[%03d] md5 : %s" % [self.md5.offset, self.md5]
|
||||||
|
r << "[%03d] Filecount : %d" % [self.filecount.offset, self.filecount]
|
||||||
|
r << "[%03d] Filesize : %s" % [self.filesize.offset, self.filesize]
|
||||||
|
return r.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def to_summary_string
|
||||||
|
r = []
|
||||||
|
r << " Software : %s (%s)" % [self.progname_str,self.version_str]
|
||||||
|
r << " Date : %s" % self.date
|
||||||
|
r << "Filename str : %s" % self.filename_str
|
||||||
|
r << " md5 : %s" % ( self.md5 ? 'enabled' : 'disabled' )
|
||||||
|
r << " Filecount : %d" % self.filecount
|
||||||
|
r << " Filesize : %s" % _readable_file_size( self.filesize, 2 )
|
||||||
|
return r.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
GIGA_SIZE = 1073741824.0
|
||||||
|
MEGA_SIZE = 1048576.0
|
||||||
|
KILO_SIZE = 1024.0
|
||||||
|
def _readable_file_size size, precision
|
||||||
|
|
||||||
|
case
|
||||||
|
when size == 1 : "1 Byte"
|
||||||
|
when size < KILO_SIZE : "%d Bytes" % size
|
||||||
|
when size < MEGA_SIZE : "%.#{precision}f KB" % (size / KILO_SIZE)
|
||||||
|
when size < GIGA_SIZE : "%.#{precision}f MB" % (size / MEGA_SIZE)
|
||||||
|
else "%.#{precision}f GB" % (size / GIGA_SIZE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
89
xtmfile/joiner.rb
Normal file
89
xtmfile/joiner.rb
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
|
||||||
|
require 'xtmfile/header'
|
||||||
|
|
||||||
|
module XtmFile
|
||||||
|
|
||||||
|
class Joiner
|
||||||
|
|
||||||
|
attr_reader :header, :input_filename, :output_filename
|
||||||
|
|
||||||
|
# yield this event + filename when opening a new file
|
||||||
|
EVENT_OPENFILE = :file
|
||||||
|
# yield this event + percent on joining progress
|
||||||
|
EVENT_PROGRESS = :progress
|
||||||
|
|
||||||
|
BUFFER_MAX_SIZE = 4096 * 4096
|
||||||
|
|
||||||
|
def initialize input, output=nil
|
||||||
|
@input_filename = input
|
||||||
|
@output_filename = output
|
||||||
|
|
||||||
|
File.open @input_filename, "rb" do |in_xtm|
|
||||||
|
@header = Header::read in_xtm
|
||||||
|
end
|
||||||
|
if @output_filename.nil? then
|
||||||
|
@output_filename = @header.filename_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def start &blk
|
||||||
|
in_xtm = File.open @input_filename, "rb"
|
||||||
|
in_xtm.seek @header.num_bytes, IO::SEEK_SET
|
||||||
|
|
||||||
|
yield EVENT_OPENFILE, @input_filename if block_given?
|
||||||
|
|
||||||
|
|
||||||
|
out_xtm = File.open @output_filename, "wb"
|
||||||
|
|
||||||
|
cur_xtm = @input_filename
|
||||||
|
is_first = true
|
||||||
|
cur_size = @header.filesize
|
||||||
|
print "\x1b[s"
|
||||||
|
while cur_size > 0 do
|
||||||
|
unless is_first then
|
||||||
|
cur_xtm = _nextfile cur_xtm
|
||||||
|
yield EVENT_OPENFILE, cur_xtm if block_given?
|
||||||
|
in_xtm = File.open cur_xtm, "rb"
|
||||||
|
end
|
||||||
|
while cur_size > 0 and (not in_xtm.eof?) do
|
||||||
|
pcent = (( @header.filesize - cur_size) * 100 / @header.filesize)
|
||||||
|
yield EVENT_PROGRESS, pcent if block_given?
|
||||||
|
|
||||||
|
read_size = if cur_size > BUFFER_MAX_SIZE then BUFFER_MAX_SIZE
|
||||||
|
else cur_size
|
||||||
|
end
|
||||||
|
buffer = in_xtm.read read_size
|
||||||
|
cur_size = cur_size - buffer.length
|
||||||
|
out_xtm.write buffer.slice(0, buffer.length)
|
||||||
|
end
|
||||||
|
is_first = false
|
||||||
|
in_xtm.close
|
||||||
|
end
|
||||||
|
out_xtm.close
|
||||||
|
|
||||||
|
# remaining files
|
||||||
|
end
|
||||||
|
|
||||||
|
def _nextfile curfile
|
||||||
|
result = nil
|
||||||
|
cur_idx = 0
|
||||||
|
cur_len = 0
|
||||||
|
case curfile
|
||||||
|
when /\.([0-9]+)\.xtm$/ then
|
||||||
|
cur_idx = $1.to_i
|
||||||
|
cur_len = $1.length
|
||||||
|
next_idx = cur_idx + 1
|
||||||
|
result = curfile.gsub(/\.([0-9]+)\.xtm$/, ".%0#{cur_len}d.xtm" % next_idx)
|
||||||
|
when /\.xtm\.([0-9]+)$/ then
|
||||||
|
cur_idx = $1.to_i
|
||||||
|
cur_len = $1.length
|
||||||
|
next_idx = cur_idx + 1
|
||||||
|
result = curfile.gsub(/\.xtm\.([0-9]+)$/, ".xtm.%0#{cur_len}d" % next_idx)
|
||||||
|
else
|
||||||
|
raise "Unable to detect a naming patterng for file sequence!"
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
19
xtmfile/splitter.rb
Normal file
19
xtmfile/splitter.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
module XtmFile
|
||||||
|
class Splitter
|
||||||
|
attr_reader :parts, :parts_size
|
||||||
|
def initialize input_filename
|
||||||
|
@input_filename = input_filename
|
||||||
|
@parts = 0
|
||||||
|
@parts_size = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def parts= count
|
||||||
|
end
|
||||||
|
|
||||||
|
def parts_size= size
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,52 +0,0 @@
|
||||||
|
|
||||||
class XtmHeader < BinData::Record
|
|
||||||
# Offset Information Taille (octets)
|
|
||||||
# 0 Longueur du nom du programme 1
|
|
||||||
# 1 *Nom du programme 20
|
|
||||||
# 21 Longueur de la version 1
|
|
||||||
# 22 Version 4
|
|
||||||
# 26 Not used 10
|
|
||||||
# 36 Date 4
|
|
||||||
# 40 Longueur du nom du fichier original 1
|
|
||||||
# 41 Nom du fichier original 50
|
|
||||||
# 91 **Hash MD5 1
|
|
||||||
# 92 Nombre de fichiers 4
|
|
||||||
# 96 Taille du fichier original 8
|
|
||||||
|
|
||||||
endian :little
|
|
||||||
|
|
||||||
uint8 :progname_len, :value => lambda { progname_str.length }
|
|
||||||
string :progname_str, :length => 20
|
|
||||||
|
|
||||||
uint8 :version_len, :value => lambda { version_str.length }
|
|
||||||
string :version_str, :length => 4
|
|
||||||
|
|
||||||
string :unused, :length => 10
|
|
||||||
|
|
||||||
uint32le :date
|
|
||||||
|
|
||||||
uint8 :filename_len
|
|
||||||
string :filename_str, :read_length => :filename_len
|
|
||||||
string :filename_str_pad, :read_length => lambda { 50 - filename_len }
|
|
||||||
|
|
||||||
uint8 :md5
|
|
||||||
|
|
||||||
uint32 :filecount
|
|
||||||
uint64 :filesize
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
r = []
|
|
||||||
r << ( "Progname len : %s [%s]" % [self.progname_len, self.progname_len.offset] )
|
|
||||||
r << ( "Progname str : %s [%s]" % [self.progname_str, self.progname_str.offset] )
|
|
||||||
r << ( " Version len : %s [%s]" % [self.version_len, self.version_len.offset] )
|
|
||||||
r << ( " Version str : %s [%s]" % [self.version_str, self.version_str.offset] )
|
|
||||||
r << ( " Unused : %s [%s]" % [self.unused, self.unused.offset] )
|
|
||||||
r << ( " Date : %s [%s]" % [self.date, self.date.offset] )
|
|
||||||
r << ( "Filename len : %s [%s]" % [self.filename_len, self.filename_len.offset] )
|
|
||||||
r << ( "Filename str : %s [%s]" % [self.filename_str, self.filename_str.offset] )
|
|
||||||
r << ( " md5 : %s [%s]" % [self.md5, self.md5.offset] )
|
|
||||||
r << ( " Filecount : %d [%s]" % [self.filecount, self.filecount.offset] )
|
|
||||||
r << ( " Filesize : %s [%s]" % [self.filesize, self.filesize.offset] )
|
|
||||||
return r.join("\n")
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue