narc 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README +12 -0
  2. data/Rakefile +47 -0
  3. data/bin/narc +9 -0
  4. data/lib/narc.rb +171 -0
  5. data/lib/narc/cli.rb +53 -0
  6. metadata +53 -0
data/README ADDED
@@ -0,0 +1,12 @@
1
+ == narc
2
+
3
+ === Description
4
+ This gem provides a simple API for packing/unpacking *NARC* (Nitro ARChive) files, an archive file format commonly used in Nintendo DS games. It also counts with a (very) simple command line interface.
5
+
6
+ === Known Bugs/Issues
7
+ * Unable to pack/unpack NARC files containing files with filenames and/or folders.
8
+
9
+ === Version Log
10
+
11
+ ==== 0.0.0
12
+ * Initial version.
@@ -0,0 +1,47 @@
1
+ #
2
+ # To change this template, choose Tools | Templates
3
+ # and open the template in the editor.
4
+
5
+
6
+ require 'rake'
7
+ require 'rake/clean'
8
+ require 'rake/gempackagetask'
9
+ require 'rake/rdoctask'
10
+ require 'rake/testtask'
11
+
12
+
13
+ spec = Gem::Specification.new do |s|
14
+ s.name = 'narc'
15
+ s.version = '0.0.0'
16
+ s.platform = Gem::Platform::RUBY
17
+ s.has_rdoc = true
18
+ s.extra_rdoc_files = ['README']
19
+ s.summary = 'A NARC file packing/unpacking gem.'
20
+ s.description = 'This gem provides a simple API for packing/unpacking NARC (Nitro ARChive) files, an archive file format commonly used in Nintendo DS games. It also counts with a (very) simple command line interface.'
21
+ s.author = 'arc_meta'
22
+ s.email = 'arc_nerotech@hotmail.com'
23
+ s.homepage = 'https://rubygems.org/gems/narc'
24
+ s.executables << 'narc'
25
+ s.files = %w(README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
26
+ s.require_path = "lib"
27
+ s.bindir = "bin"
28
+ end
29
+
30
+ Rake::GemPackageTask.new(spec) do |p|
31
+ p.gem_spec = spec
32
+ p.need_tar = true
33
+ p.need_zip = true
34
+ end
35
+
36
+ Rake::RDocTask.new do |rdoc|
37
+ files =['README', 'lib/**/*.rb']
38
+ rdoc.rdoc_files.add(files)
39
+ rdoc.main = "README" # page to start on
40
+ rdoc.title = "narc Docs"
41
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
42
+ rdoc.options << '--line-numbers'
43
+ end
44
+
45
+ Rake::TestTask.new do |t|
46
+ t.test_files = FileList['test/**/*.rb']
47
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'narc.rb'
4
+
5
+ begin
6
+ Narc::CLI.new(ARGV)
7
+ rescue Exception => e
8
+ puts "Sorry, an error has occurred: #{e.message}."
9
+ end
@@ -0,0 +1,171 @@
1
+
2
+ module Narc
3
+ Element = Struct.new(:name, :data)
4
+
5
+ NarcSignature = "NARC\xFE\xFF\x00\x01".force_encoding("binary")
6
+ FatbSignature = "BTAF".force_encoding("binary")
7
+ FntbSignature = "BTNF".force_encoding("binary")
8
+ FimgSignature = "GMIF".force_encoding("binary")
9
+
10
+ class NarcException < StandardError; end
11
+
12
+ end
13
+
14
+ require 'narc/cli.rb'
15
+
16
+ class NarcFile
17
+ attr_accessor :name, :elements
18
+
19
+ def initialize(name = "narc")
20
+ @name = name
21
+ @elements = []
22
+ @offsets = {}
23
+ @size_offsets = {}
24
+ end
25
+
26
+ def self.open(filename)
27
+ io = File.open(filename, "rb")
28
+ narc = self.new(File.basename(filename, ".narc"))
29
+ narc.get_data(io)
30
+ io.close
31
+ return narc
32
+ end
33
+
34
+ def extract_to_folder(folder_name = nil)
35
+ folder_name ||= "ex_" + @name
36
+ Dir.mkdir(folder_name) unless Dir.exists?(folder_name)
37
+ Dir.chdir(folder_name) do
38
+ for element in @elements
39
+ File.open(element.name, "wb") do |file|
40
+ file.write(element.data)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.from_folder(dir)
47
+ narc = self.new(File.basename(dir))
48
+ files = Dir.entries(dir).sort - [".", ".."]
49
+ narc.elements = Array.new(files.size) do Narc::Element.new end
50
+ Dir.chdir(dir) do
51
+ files.each_with_index do |file, i|
52
+ narc.elements[i].name = file.split("_")[-1]
53
+ File.open(file, "rb") do |f|
54
+ narc.elements[i].data = f.read
55
+ end
56
+ end
57
+ end
58
+ return narc
59
+ end
60
+
61
+ def save(filename = nil, include_names = false)
62
+ filename ||= "saved_" + self.name
63
+ File.open(filename, "w+b") do |f|
64
+ write_narc_section(f)
65
+ write_fatb_section(f)
66
+ write_fntb_section(f)
67
+ write_fimg_section(f)
68
+ write_size_data(f)
69
+ end
70
+ end
71
+
72
+ def get_data(io)
73
+ check_signature(io)
74
+ get_section_offsets(io)
75
+ get_elements(io)
76
+ end
77
+
78
+ private
79
+ def check_signature(io)
80
+ io.pos = 0
81
+ raise Narc::NarcException, "Signature does not match (is it really a NARC file?)" unless io.read(4) == 'NARC'
82
+ end
83
+
84
+ def get_section_offsets(io)
85
+ @offsets[:narc] = 0x0
86
+ @offsets[:btaf] = 0x10
87
+ io.pos = @offsets[:btaf] + 0x8 # Number of elements
88
+ @offsets[:btnf] = io.read(4).unpack('V')[0] * 8 + @offsets[:btaf] + 0xC
89
+ io.pos = @offsets[:btnf] + 0x4 # FNTB Size
90
+ @offsets[:gmif] = @offsets[:btnf] + io.read(4).unpack('V')[0]
91
+ end
92
+
93
+ def get_elements(io)
94
+ io.pos = @offsets[:btaf] + 0x8
95
+ @elements = Array.new(io.read(4).unpack('V')[0]) do Narc::Element.new end
96
+ io.pos = @offsets[:btnf] + 0x8
97
+ names = io.read(4).unpack('V')[0] == 0x00000004 ? false : true
98
+ @elements.size.times do |i|
99
+ @elements[i].name = @name + "_" + "%04d" % i
100
+ io.pos = @offsets[:btaf] + 12 + i * 0x8
101
+ elem_start, elem_end = io.read(8).unpack('V2')
102
+ io.pos = @offsets[:gmif] + 0x8 + elem_start
103
+ size = elem_end - elem_start
104
+ @elements[i].data = io.read(size)
105
+ end
106
+ end
107
+
108
+ def write_narc_section(file)
109
+ @offsets[:narc] = file.pos
110
+ file.write(Narc::NarcSignature) # Signature
111
+ @size_offsets[:file] = file.pos # File size
112
+ file.write([0x0].pack('V'))
113
+ file.write([0x10].pack('v')) # Section size
114
+ file.write([0x3].pack('v')) # Number of sections
115
+ end
116
+
117
+ def write_fatb_section(file)
118
+ @offsets[:btaf] = file.pos
119
+ file.write(Narc::FatbSignature) # Signature
120
+ @size_offsets[:fatb] = file.pos # FATB block size
121
+ file.write([0x0].pack('V'))
122
+ file.write([self.elements.size].pack('V')) # Number of entries
123
+ offset = 0
124
+ self.elements.size.times do |i| # Element offsets
125
+ offset += 1 until offset % 4 == 0
126
+ file.write([offset].pack('V'))
127
+ offset += self.elements[i].data.size
128
+ file.write([offset].pack('V'))
129
+ end
130
+ end
131
+
132
+ def write_fntb_section(file)
133
+ @offsets[:btnf] = file.pos
134
+ file.write(Narc::FntbSignature)
135
+ file.write([0x10, 0x4, 0x10000].pack('V3'))
136
+ end
137
+
138
+ def write_fimg_section(file)
139
+ @offsets[:gmif] = file.pos
140
+ file.write(Narc::FimgSignature)
141
+ @size_offsets[:fimg] = file.pos # FIMG block size
142
+ file.write([0x0].pack('V'))
143
+ offset = 0
144
+ self.elements.size.times do |i|
145
+ until offset % 4 == 0
146
+ file.write("\xFF")
147
+ offset += 1
148
+ end
149
+ file.write(self.elements[i].data)
150
+ offset += self.elements[i].data.size
151
+ end
152
+ end
153
+
154
+ def write_size_data(file)
155
+ file.pos = 0
156
+ filesize = file.stat.size
157
+ file.pos = @size_offsets[:file]
158
+ file.write([filesize].pack('V'))
159
+ file.pos = @size_offsets[:fatb]
160
+ file.write([0x8 + 0x4 + (self.elements.size * 8)].pack('V'))
161
+
162
+ fimgsize = get_fimg_size(file)
163
+ file.pos = @size_offsets[:fimg]
164
+ file.write([fimgsize].pack('V'))
165
+ end
166
+
167
+ def get_fimg_size(file)
168
+ file.pos = @offsets[:btnf] - 0x4 # The size of the FIMG block == the end offset of the last file in the FIMG block + 0x8
169
+ return (file.read(4).unpack('V')[0] + 0x8)
170
+ end
171
+ end
@@ -0,0 +1,53 @@
1
+ Narc::Usage = <<DOC
2
+
3
+ narc command usage:
4
+
5
+ narc
6
+ (with no arguments) Show this help.
7
+
8
+ narc -x <narc file> <output folder>
9
+ Extracts the contents of a narc file to a folder. If the <output folder>
10
+ arguments is missing, extracts to a folder with the same name of the narc
11
+ file.
12
+
13
+ narc -c <input folder> <narc file>
14
+ Creates a narc file with the contents of <input folder> folder. If <narc
15
+ file> is absent, the name of the narc file will be the same as the input
16
+ folder name.
17
+
18
+ DOC
19
+
20
+ module Narc
21
+ class CLI
22
+ def initialize(args)
23
+ @args = args
24
+ process_cmds
25
+ end
26
+
27
+ def process_cmds
28
+ if @args.size == 0
29
+ puts Narc::Usage
30
+ end
31
+ case @args[0]
32
+ when '-x'
33
+ unless @args[1]
34
+ puts "You must provide the path of the narc file as argument."
35
+ return
36
+ end
37
+ puts "Extracting..."
38
+ narc = NarcFile.open(@args[1])
39
+ narc.extract_to_folder(@args[2])
40
+ puts "Extract successful."
41
+ when '-c'
42
+ unless @args[1]
43
+ puts "You must provide the path of the folder as argument."
44
+ return
45
+ end
46
+ puts "Creating narc file..."
47
+ narc = NarcFile.from_folder(@args[1])
48
+ narc.save(@args[2])
49
+ puts "Narc file was created successfully."
50
+ end
51
+ end
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: narc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - arc_meta
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-08 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: This gem provides a simple API for packing/unpacking NARC (Nitro ARChive)
15
+ files, an archive file format commonly used in Nintendo DS games. It also counts
16
+ with a (very) simple command line interface.
17
+ email: arc_nerotech@hotmail.com
18
+ executables:
19
+ - narc
20
+ extensions: []
21
+ extra_rdoc_files:
22
+ - README
23
+ files:
24
+ - README
25
+ - Rakefile
26
+ - bin/narc
27
+ - lib/narc/cli.rb
28
+ - lib/narc.rb
29
+ homepage: https://rubygems.org/gems/narc
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.6
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: A NARC file packing/unpacking gem.
53
+ test_files: []