narc 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []