Skip to content
This repository has been archived by the owner on Apr 25, 2022. It is now read-only.

Added autodetection (closed #6) #8

Merged
merged 7 commits into from
Dec 31, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Usage:

Example:

ruby ./bin/arc_unpacker.rb --fmt xp3 --plugin fsn ~/games/fate/fgimage.xp3 ./fgimage_unpacked/
ruby ./bin/arc_unpacker.rb --fmt=xp3 --plugin=fsn ~/games/fate/fgimage.xp3 ./fgimage_unpacked/

Warning:

Expand Down
24 changes: 18 additions & 6 deletions bin/arc_packer.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
#!/usr/bin/ruby -W2
require 'fileutils'
require_relative '../lib/common'
require_relative '../lib/cli'
require_relative '../lib/binary_io'
require_relative '../lib/input_files'

# CLI frontend
class ArchivePacker < CLI
def run_internal
FileUtils.mkpath(File.dirname(@options[:output_path]))
archive = ArchiveFactory.get(@options[:format])
archive.pack(
@options[:input_path],
@options[:output_path],
@options)
fail 'Must specify output format.' if @options[:archive].nil?
pack(@options[:archive], @options)
end

def pack(archive, options)
fail 'Packing not supported' unless defined? archive::Packer
if defined? archive.parse_cli_options
archive.parse_cli_options(@arg_parser, options)
end

input_files = InputFiles.new(options[:input_path], options)
packer = archive::Packer.new
BinaryIO.from_file(options[:output_path], 'wb') do |arc_file|
packer.pack(arc_file, input_files, options)
end
end
end

Expand Down
49 changes: 43 additions & 6 deletions bin/arc_unpacker.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,53 @@
#!/usr/bin/ruby -W2
require 'fileutils'
require_relative '../lib/common'
require_relative '../lib/cli'
require_relative '../lib/binary_io'
require_relative '../lib/output_files'
require_relative '../lib/archive_factory'

# CLI frontend
class ArchiveUnpacker < CLI
def run_internal
FileUtils.mkpath(@options[:output_path])
archive = @options[:archive]
archive.unpack(
@options[:input_path],
@options[:output_path],
@options)
verbose = @options[:verbosity] != :quiet

unless @options[:archive].nil?
unpack(@options[:archive], @options)
return
end

ArchiveFactory.each do |key, archive|
@options[:format] = key
@options[:archive] = archive
print format('Trying %s... ', key) if verbose
begin
unpack(archive, @options)
puts 'ok' if verbose
return
rescue => e
puts e.message if verbose
if @options[:verbosity] == :debug && !e.is_a?(ArcError)
puts
puts e.backtrace
puts
end
next
end
end
end

def unpack(archive, options)
fail 'Unpacking not supported' unless defined? archive::Unpacker
if defined? archive.parse_cli_options
archive.parse_cli_options(@arg_parser, options)
end

FileUtils.mkpath(options[:output_path])
output_files = OutputFiles.new(options[:output_path], options)
unpacker = archive::Unpacker.new
BinaryIO.from_file(options[:input_path], 'rb') do |arc_file|
unpacker.unpack(arc_file, output_files, options)
end
end
end

Expand Down
32 changes: 0 additions & 32 deletions lib/archive.rb

This file was deleted.

28 changes: 16 additions & 12 deletions lib/archive_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,60 @@ def self.format_strings
factory.keys
end

def self.each(&block)
factory.each { |k, v| block.call(k, v.call) }
end

def self.factory
{
'xp3' => lambda do
require_relative 'kirikiri/xp3_archive'
Xp3Archive.new
Xp3Archive
end,

'ykc' => lambda do
require_relative 'yuka/ykc_archive'
YkcArchive.new
YkcArchive
end,

'sar' => lambda do
require_relative 'nscripter/sar_archive'
SarArchive.new
SarArchive
end,

'nsa' => lambda do
require_relative 'nscripter/nsa_archive'
NsaArchive.new
NsaArchive
end,

'melty_blood' => lambda do
require_relative 'french_bread/melty_blood_archive'
MeltyBloodArchive.new
MeltyBloodArchive
end,

'nitroplus/pak' => lambda do
require_relative 'nitroplus/pak_archive'
PakArchive.new
'nitroplus/pak2' => lambda do
require_relative 'nitroplus/pak2_archive'
Pak2Archive
end,

'fjsys' => lambda do
require_relative 'nsystem/fjsys_archive.rb'
FjsysArchive.new
FjsysArchive
end,

'rpa' => lambda do
require_relative 'renpy/rpa_archive.rb'
RpaArchive.new
RpaArchive
end,

'mbl' => lambda do
require_relative 'ivory/mbl_archive.rb'
MblArchive.new
MblArchive
end,

'exe' => lambda do
require_relative 'misc/exe_reader.rb'
ExeReader.new
ExeReader
end
}
end
Expand Down
138 changes: 43 additions & 95 deletions lib/arg_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,40 @@ def initialize(message)
# The key reason for using this over built-in OptParser is that it can be
# decorated dynamically without weird hacks.
class ArgParser
attr_reader :stray

def initialize(args)
@getters = []
@switches = {}
@flags = []
@stray = []
@help_entries = []
@args = args
end

def on(short, long, help_message = nil, values = nil, &block)
@help_entries.push(
short: short,
long: long,
message: help_message,
parameters: block.parameters,
mandatory: false)

get_internal(short, long, false, values, &block)
parse(args)
end

def get(short, long, help_message = nil, values = nil, &block)
@help_entries.push(
short: short,
long: long,
message: help_message,
parameters: block.parameters,
mandatory: true)

get_internal(short, long, true, values, &block)
def add_help(invocation, description, possible_values: [])
unless possible_values.empty?
description += " Possible values:\n"
description += possible_values.map { |e| format('- %s', e) } * "\n"
end
@help_entries.push(invocation: invocation, description: description)
end

def on_stray(&block)
get_stray_internal(false, &block)
def clear_help
@help_entries = []
end

def get_stray(&block)
get_stray_internal(true, &block)
def switch(keys)
[*keys].each do |key|
value = @switches[strip_dashes(key)]
next if value.nil?
return value
end
nil
end

def parse
getters = @getters.dup
@getters = []
getters.each(&:call)
def flag?(keys)
keys = [*keys].map { |k| strip_dashes(k) }
@flags.any? { |f| keys.include?(f) }
end

def print_help
Expand All @@ -58,14 +52,8 @@ def print_help
end

@help_entries.each do |entry|
params = entry[:parameters].map { |p| p[1] }
params = params.map { |p| format('[%s]', p) } unless entry[:mandatory]
switches =
['-' + strip_dashes(entry[:short]), '--' + strip_dashes(entry[:long])]
.select { |m| strip_dashes(m).length > 0 }

left = format('%s %s', switches.join(', '), params.join(', ').upcase)
right = entry[:message]
left = entry[:invocation]
right = entry[:description]

left_size = 25
right_size = 78 - left_size
Expand All @@ -77,70 +65,30 @@ def print_help

private

def word_wrap(message, width)
message.scan(/\S.{0,#{width}}\S(?=\s|$)|\S+/)
end

def myfail(message)
fail OptionError, message
end

def get_internal(short, long, mandatory, allowed_values, &block)
@getters.push(lambda do
index = \
@args.index('-' + strip_dashes(short)) || \
@args.index('--' + strip_dashes(long))

if index.nil?
myfail(format('Required argument %s is missing.', long)) if mandatory
return
end

indices = @args.each_index.select do |i|
i > index && !@args[i].start_with?('-')
end.first(block.arity)

if mandatory && indices.length < block.arity
myfail(format('Required values for %s are missing.', long))
def parse(args)
args.each do |arg|
match = /\A--?(?<key>\w+)=(?<value>.*)\Z/.match(arg)
if match
@switches[match[:key]] = match[:value]
next
end

values = @args.values_at(*indices)
@args.delete_if.with_index { |_, i| indices.include?(i) }
@args.delete_at(index)

unless allowed_values.nil?
fail 'This doesn\'t make sense!' if block.arity != 1
unless allowed_values.include?(values.first) \
|| allowed_values.include?(values.first.to_sym)
myfail(format(
"Bad value %s for %s. Allowed values:\n%s",
values.first,
long,
allowed_values * "\n"))
end
match = /\A--?(?<key>\w+)\Z/.match(arg)
if match
@flags.push(match[:key])
next
end

block.call(*values)
end)
@stray.push(arg)
end
end

def get_stray_internal(mandatory, &block)
@getters.push(lambda do
fail 'This doesn\'t make sense!' if block.arity == 0

indices = @args.each_index.select do |i|
!@args[i].start_with?('-')
end.first(block.arity)

if mandatory && indices.length < block.arity
myfail('Required arguments are missing.')
end

values = @args.values_at(*indices)
@args.delete_if.with_index { |_, i| indices.include?(i) }
def word_wrap(message, width)
message.scan(/\S.{0,#{width}}\S(?=\s|$)|\S+/)
end

block.call(*values)
end)
def myfail(message)
fail OptionError, message
end

def strip_dashes(key)
Expand Down
Loading