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.
- data/README +12 -0
- data/Rakefile +47 -0
- data/bin/narc +9 -0
- data/lib/narc.rb +171 -0
- data/lib/narc/cli.rb +53 -0
- 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.
|
data/Rakefile
ADDED
@@ -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
|
data/bin/narc
ADDED
data/lib/narc.rb
ADDED
@@ -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
|
data/lib/narc/cli.rb
ADDED
@@ -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: []
|