pax_global_header00006660000000000000000000000064123517120610014510gustar00rootroot0000000000000052 comment=e1a0827b859ff02f35b8d0ca268e5b050ee8085f dns323-firmware-tools-0.7.3/000077500000000000000000000000001235171206100155035ustar00rootroot00000000000000dns323-firmware-tools-0.7.3/CHANGELOG000066400000000000000000000011671235171206100167220ustar00rootroot000000000000000.6 --- Now supports Ruby 2.0. 0.3 --- Add -s option to mkdns323fw, to specify the type of firmware to build. New tool, splitdns323fw, to parse a firmware image, verify checksums, print IDs, and write out the various parts of the firmware image. Support DNS-321 ("Chopper") and DNS-343 ("Gandolf") firmware images. 0.2 --- Fix a manpage copy-paste error, -c is for custom_id, -m is for model_id, and never the twain shall meet. Remove debugging output that wasn't properly tidied up before release. Thanks to Luk Claes for pointing these problems out. 0.1 --- initial release, makes firmware images, nothing special. dns323-firmware-tools-0.7.3/README.md000066400000000000000000000013101235171206100167550ustar00rootroot00000000000000# dns323-firmware-tools This package contains programs for manipulating the firmware images used by the D-Link DNS-323 and similar devices. These firmware images are what are used to "bundle" the kernel, initrd, and other data when uploading custom firmware images using the "stock" firmware update interface. If you are already running a custom firmware, it is likely that these tools will be of no use to you, and you will need to use whatever update mechanism is provided by your firmware. ## Requirements In order to run, this program requires: * Ruby 1.9 or above; * The `ffi` gem (`gem install ffi`); * A compatible firmware file, or the input files (kernel/initrd) to make into a firmware. dns323-firmware-tools-0.7.3/dns323fw-tool000077500000000000000000000523761235171206100177720ustar00rootroot00000000000000#!/usr/bin/ruby # Create and dismantle firmware files suitable for upload to a range of # D-Link (and compatible) NAS devices. # # Copyright (C) 2008,2012,2014 Matt Palmer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # This tool is based on information provided by Leschinsky Oleg. Many # thanks go to him for deciphering (and publishing) his analysis of the file # formats involved. # # This file should not be called as dns323fw-tool; create links to this file # named 'mkdns323fw' and 'splitdns323fw'. require 'optparse' begin require 'ffi' rescue LoadError $stderr.puts "The 'ffi' gem is not available." $stderr.puts "Perhaps try running `gem install ffi`" exit 1 end # This class handles all the grunt work for decoding and encoding firmware # files. class DnsFirmware # Represents a file which is a part of the firmware blob class IncludedFile # The contents of the file attr_reader :contents # The stored / calculated checksum attr_reader :checksum # This class can be instantiated in two different ways: # # * By passing in `:filename => "some file name"` as the second # argument; or # # * By passing in `:contents => "some file contents"` and `:checksum # => ` as the second argument. # def initialize(opts) if opts[:filename] and (opts[:contents] or opts[:checksum]) raise ArgumentError, "Must pass either :filename or :contents and :checksum to #{self.class}.initialize" end if opts[:filename] @filename = File.expand_path(opts[:filename]) @contents = File.read(@filename).force_encoding("BINARY") elsif opts[:contents] @contents = opts[:contents] if opts[:checksum] @checksum = opts[:checksum] end else raise ArgumentError, "Must pass exactly one of :filename or :contents to #{self.class}.initialize" end # Calculate a checksum if one was not provided @checksum ||= calculate_checksum end def validate if @filename and !File.exists?(@filename) log_error "does not exist" end unless @checksum == self.calculate_checksum $stderr.puts "Checksum of #{type} file is incorrect (expected #{@checksum}, got #{checksum(@contents)}" end true end def log_error(s) if @filename $stderr.print "#{@filename}: " end $stderr.puts s end def size @contents.length end def calculate_checksum @contents.unpack("V*").inject(0) { |v, i| v ^= i } end def write(f) File.write(f, @contents) end end # Any included file that should be a uBoot file class UBootFile < IncludedFile def validate super or return false unless @contents[0..3] == "\x27\x05\x19\x56" $stderr.puts "#{type} file #{@filename} does not appear to be a uBoot image file" return false end true end end class KernelFile < UBootFile def type :kernel end end class InitrdFile < UBootFile def type :initrd end end class DefaultsFile < IncludedFile def type :defaults end def validate super or return false unless @contents[0..1] == "\x1f\x8b" $stderr.puts "defaults file does not appear to be gzipped" return false end true end end class SquashfsFile < IncludedFile def type :squashfs end def validate super or return false unless @contents[0x800..0x803] == "hsqs" or @contents[0x800..0x803] == "shsq" $stderr.puts "filesystem image file does not appear to be a squashfs filesystem" return false end true end end class GenericHeader < FFI::Struct def initialize(content = nil) super() if content if content.length != self.size raise ArgumentError, "Content passed to #{self.class}.new must be #{self.size} bytes" end self.to_ptr.write_string(content) end end def data self.to_ptr.read_string(self.size) end end class FrodoHeader < GenericHeader layout :kernel_offset, :uint32, # 0 :kernel_size, :uint32, # 4 :initrd_offset, :uint32, # 8 :initrd_size, :uint32, # 12 :defaults_offset, :uint32, # 16 :defaults_size, :uint32, # 20 :kernel_sum, :uint32, # 24 :initrd_sum, :uint32, # 28 :defaults_sum, :uint32, # 32 :magic1, [:uint8, 2], # 36 :sig, [:uint8, 8], # 38 :magic2, [:uint8, 2], # 46 :product_id, :uint8, # 48 :custom_id, :uint8, # 49 :model_id, :uint8, # 50 :compat_flag, :uint8, # 51 :compat_id, :uint8, # 52 :_1, [:uint8, 11] # 53 def signature self[:sig].to_ptr.read_string.strip end def signature=(s) if s.length > 8 raise ArgumentError, "Signature can be no more than 8 bytes" end self[:sig].to_ptr.write_string(s) end def valid? self[:magic1].to_s == "\x55\xAA".force_encoding("BINARY") and self[:magic2].to_s == "\x55\xAA".force_encoding("BINARY") end end class LongHeader < GenericHeader layout :kernel_offset, :uint32, # 0 :kernel_size, :uint32, # 4 :initrd_offset, :uint32, # 8 :initrd_size, :uint32, # 12 :squashfs_offset, :uint32, # 16 :squashfs_size, :uint32, # 20 :defaults_offset, :uint32, # 24 :defaults_size, :uint32, # 28 :kernel_sum, :uint32, # 32 :initrd_sum, :uint32, # 36 :squashfs_sum, :uint32, # 40 :defaults_sum, :uint32, # 44 :magic1, [:uint8, 2], # 48 :sig, [:uint8, 8], # 50 :magic2, [:uint8, 2], # 58 :product_id, :uint8, # 60 :custom_id, :uint8, # 61 :model_id, :uint8, # 62 :compat_flag, :uint8, # 63 :compat_id, :uint8, # 64 :_1, [:uint8, 63] # 65 def signature self[:sig].to_ptr.read_string.strip end def signature=(s) if s.length > 8 raise ArgumentError, "Signature can be no more than 8 bytes" end self[:sig].to_ptr.write_string(s) end def valid? self[:magic1].to_s == "\x55\xAA".force_encoding("BINARY") and self[:magic2].to_s == "\x55\xAA".force_encoding("BINARY") end end HEADER_FORMATS = [FrodoHeader, LongHeader] # This class can be initialized two ways: # # - with a single string argument, which is the filename of an existing # firmware file to be dissected; or # # - with a hash containing the following keys: # * `:kernel_file` (required) -- A file containing a raw bzImage kernel # # * `:initrd_file` (required) -- A file containing an initrd # # * `:defaults_file` (optional) -- A file containing a compressed # tarball of configuration data; take a look at an existing # firmware file for your device to see what can be in there. # # * `:squashfs_file` (optional) -- A file containing a squashfs # filesystem image. Only some newer devices (DNS-320, etc) # make use of such a file. # # * `:product_id` (required) -- The "product ID" to embed in the # firmware file. What this needs to be set to is entirely # dependent on your device and what it expects. It *appears* # that hardware that is substantially identical will tend to # share a common product ID, even if it is badged differently. # # * `:custom_id` (required) -- The "customisation ID" to embed in the # firmware file. What this needs to be set to is entirely # dependent on your device and what it expects. It appears that # hardware that is substantially identical, but shipped by # different vendors, will usually have a different custom ID. # # * `:model_id` (required) -- The "model ID" to embed in the firmware # file. What this needs to be set to is entirely dependent on # your device and what it expects. # # * `:compat_id` (optional) -- The "compatibility version ID" to embed # in the firmware file. As far as can be determined, some # firmware loaders will not load a firmware file which contains a # compatibility ID lower than an arbitrary figure. # # * `:signature` (required) -- The signature to embed in the firmware # file. This is some sort of magic string that lets the firmware # loader know that its reading a real firmware file, as opposed # to some random gibberish. What you need to set this to is # determined entirely by what your device expects to see. The # signature is also used to determine what kind of header to place # in front of the firmware. # def initialize(opts) @files = {} if opts.is_a? String @firmware_file = opts candidate_formats = HEADER_FORMATS.select do |hf| h = hf.new(File.read(@firmware_file, hf.size)) h.valid? end if candidate_formats.empty? $stderr.puts "Unable to recognise the format of firmware file #{@firmware_file}" $stderr.puts "The file may be corrupted, or this firmware may be a format I am not familiar with." exit 1 end if candidate_formats.length > 1 $stderr.puts "This firmware is of an ambiguous format." $stderr.puts "Please inform theshed+dns323-firmware-tools@hezmatt.org, and include a" $stderr.puts "link to the firmware file for testing." exit 1 end @header = candidate_formats.first.new(File.read(@firmware_file, candidate_formats.first.size)) @files[:kernel] = KernelFile.new( :contents => File.read( @firmware_file, @header[:kernel_size], @header[:kernel_offset] ), :checksum => @header[:kernel_sum] ) @files[:initrd] = InitrdFile.new( :contents => File.read( @firmware_file, @header[:initrd_size], @header[:initrd_offset] ), :checksum => @header[:initrd_sum] ) if @header[:defaults_size] > 0 @files[:defaults] = DefaultsFile.new( :contents => File.read( @firmware_file, @header[:defaults_size], @header[:defaults_offset] ), :checksum => @header[:defaults_sum] ) end if @header.members.include?(:squashfs_size) and @header[:squashfs_size] > 0 @files[:squashfs] = SquashfsFile.new( :contents => File.read( @firmware_file, @header[:squashfs_size], @header[:squashfs_offset] ), :checksum => @header[:squashfs_sum] ) end elsif opts.is_a? Hash [:kernel_file, :initrd_file].each do |f| unless opts[f] raise ArgumentError, "No #{f.inspect} provided" end end @files[:kernel] = KernelFile.new(:filename => opts[:kernel_file]) @files[:initrd] = InitrdFile.new(:filename => opts[:initrd_file]) if opts[:defaults_file] @files[:defaults] = DefaultsFile.new(:filename => opts[:defaults_file]) end if opts[:squashfs_file] @files[:squashfs] = SquashfsFile.new(:filename => opts[:squashfs_file]) end @product_id = opts[:product_id] or raise ArgumentError.new("No :product_id provided") @custom_id = opts[:custom_id] or raise ArgumentError.new("No :custom_id provided") @model_id = opts[:model_id] or raise ArgumentError.new("No :model_id provided") @compat_id = opts[:compat_id] @signature = opts[:signature] or raise ArgumentError.new("No :signature provided") # This logic will probably have to be improved somewhat, likely via # another option, in the future if @signature =~ /^DNS32/ @header = LongHeader.new else @header = FrodoHeader.new end @header[:magic1] = @header[:magic2] = "\x55\xAA" else raise ArgumentError.new("Incorrect type passed to DnsFirmware#initialize. String or Hash expected, got #{opts.class}") end end def product_id @header[:product_id] end def custom_id @header[:custom_id] end def model_id @header[:model_id] end def compat_id @header[:compat_flag] ? @header[:compat_id] : nil end def kernel @files[:kernel] end def initrd @files[:initrd] end def defaults @files[:defaults] end def squashfs @files[:squashfs] end # Return the signature of this firmware file. def signature @header.signature end # This method works from the kernel/initrd/defaults/etc data and writes out # a complete firmware file to the destfile of your choosing. def write_firmware_file(destfile) @header.signature = @signature @header[:product_id] = @product_id @header[:custom_id] = @custom_id @header[:model_id] = @model_id @header[:compat_flag] = @compat_id.nil? ? 0 : 1 @header[:compat_id] = @compat_id.to_i file_offset = @header.size file_types = @header. members. grep(/_sum$/). map { |m| m.to_s.sub(/_sum$/, '').to_sym } file_types.each do |f_type| @header["#{f_type}_offset".to_sym] = file_offset if @files[f_type] @header["#{f_type}_size".to_sym] = @files[f_type].size @header["#{f_type}_sum".to_sym] = @files[f_type].checksum file_offset += @files[f_type].size else @header["#{f_type}_size".to_sym] = 0 @header["#{f_type}_sum".to_sym] = 0 end end File.open(destfile, 'w') do |fd| fd.write @header.data file_types.each do |f_type| fd.write @files[f_type].contents if @files[f_type] end end end def validate @files.values.each { |f| f.validate } end end def mkdns323fw(args) devices = { 'DNS-325' => { :signature => 'DNS-325', :product_id => 0, :custom_id => 8, :model_id => 5 }, 'DNS-323' => { :signature => 'FrodoII', :product_id => 7, :custom_id => 1, :model_id => 1 }, 'DNS-321' => { :signature => 'Chopper', :product_id => 10, :custom_id => 1, :model_id => 1 }, 'DNS-320' => { :signature => 'DNS323D1', # No, seriously... :product_id => 0, :custom_id => 8, :model_id => 7 }, 'DNS-320B' => { :signature => 'DNS320B', :product_id => 0, :custom_id => 8, :model_id => 12 }, 'DNS-320L' => { :signature => 'DNS320L', :product_id => 0, :custom_id => 8, :model_id => 11 }, 'CH3SNAS' => { :signature => 'FrodoII', :product_id => 7, :custom_id => 2, :model_id => 1 }, } opts = OptionParser.new optargs = {} opts.on('-h', '--help', "Print this help") { puts opts.to_s; exit 0 } opts.on('-k KERNEL', '--kernel KERNEL', "Specify the kernel to include in the firmware image", String) { |k| optargs[:kernel_file] = k } opts.on('-i INITRD', '--initrd INITRD', "Specify the initrd to include in the firmware image", String) { |i| optargs[:initrd_file] = i } opts.on('-d DEFAULTS', '--defaults DEFAULTS', "Specify the defaults.tar.gz to include in the firmware image (optional)", String) { |d| optargs[:defaults_file] = d } opts.on('--squashfs SQUASHFS', "Specify a squashfs filesystem image to include in the firmware image (optional)", String) { |d| optargs[:squashfs_file] = d } opts.on('-o OUTPUT', '--output OUTPUT', "Specify where to put the resulting firmware image", String) { |o| optargs[:output] = o } opts.on('-p PROD_ID', '--product-id PROD_ID', "The product ID to embed in the firmware image", Integer) { |p| optargs[:product_id] = p } opts.on('-c CUSTOM_ID', '--custom-id CUSTOM_ID', "The custom ID to embed in the firmware image", Integer) { |c| optargs[:custom_id] = c } opts.on('-m MODEL_ID', '--model-id MODEL_ID', "The model ID to embed in the firmware image", Integer) { |m| optargs[:model_id] = m } opts.on('-s SIGNATURE', '--signature SIGNATURE', "The firmware signature type", String) { |s| optargs[:signature] = s } opts.on('-t DEVICE', '--device-type DEVICE', "Specify all the IDs and signature by choosing a device", "Known devices: #{devices.keys.join(' ')}", String) { |s| optargs[:device] = s } opts.parse(args) if optargs[:device] optargs.merge!(devices[optargs.delete(:device)]) end opts = optargs %w{kernel_file initrd_file output product_id custom_id model_id}.each do |k| if opts[k.to_sym].nil? $stderr.puts "Missing required argument #{k}" exit 1 end end opts[:signature] ||= "FrodoII" # We just hardwire this to be the maximum value for now, since there is # no indication that it does any harm in the wild opts[:compat_id] = 255 begin fw = DnsFirmware.new(opts) fw.validate and fw.write_firmware_file(opts[:output]) rescue StandardError => e $stderr.puts "Firmware generation failed: #{e.class}: #{e.message}" e.backtrace.each { |l| puts " #{l}" } else puts "Firmware generation completed successfully." end end def splitdns323fw(args) opts = OptionParser.new optargs = {} opts.on('-h', '--help', "Print this help") { $stderr.puts opts.to_s; exit 0 } opts.on('-k KERNEL', '--kernel KERNEL', "Write out the kernel to the specified file", String) { |k| optargs[:kernel] = k } opts.on('-i INITRD', '--initrd INITRD', "Write out the initrd to the specified file", String) { |i| optargs[:initrd] = i } opts.on('-d DEFAULTS', '--defaults DEFAULTS', "Write out the defaults.tar.gz to the specified file", String) { |d| optargs[:defaults] = d } opts.on('-s SQUASHFS', '--squashfs SQUASHFS', "Write out the squashfs filesystem image to the specified file", String) { |s| optargs[:squashfs] = s } image = opts.parse(args) if image.nil? or image.empty? $stderr.puts "No firmware image provided!" exit 1 end if image.length > 1 $stderr.puts "I can only read one firmware image!" exit 1 end image = image[0] fw = DnsFirmware.new(image) puts "'#{fw.signature}' firmware signature found" fw.kernel.validate or $stderr.puts "Kernel data failed validation" puts "Kernel is #{fw.kernel.size} bytes" fw.initrd.validate or $stderr.puts "Initrd data failed validation" puts "initrd is #{fw.initrd.size} bytes" if fw.defaults fw.defaults.validate or $stderr.puts "Defaults data failed validation" puts "defaults.tar.gz is #{fw.defaults.size} bytes" else puts "No defaults.tar.gz in this firmware file" end if fw.squashfs fw.squashfs.validate or $stderr.puts "squashfs data failed validation" puts "squashfs is #{fw.squashfs.size} bytes" else puts "No squashfs in this firmware file" end puts "Product ID: #{fw.product_id}" puts "Custom ID: #{fw.custom_id}" puts "Model ID: #{fw.model_id}" puts "Compat ID: #{fw.compat_id}" if optargs[:kernel] fw.kernel.write(optargs[:kernel]) puts "Kernel data written to #{optargs[:kernel]}" end if optargs[:initrd] fw.initrd.write(optargs[:initrd]) puts "initrd data written to #{optargs[:initrd]}" end if optargs[:defaults] if fw.defaults fw.defaults.write(optargs[:defaults]) puts "defaults.tar.gz written to #{optargs[:defaults]}" else $stderr.puts "This firmware file does not have a defaults.tar.gz; not written" end end if optargs[:squashfs] if fw.squashfs fw.squashfs.write(optargs[:squashfs]) puts "squashfs filesystem image written to #{optargs[:squashfs]}" else $stderr.puts "This firmware file does not have a squashfs filesystem image; not written" end end end if $0 == __FILE__ case File.basename($0) when 'mkdns323fw' then mkdns323fw(ARGV) when 'splitdns323fw' then splitdns323fw(ARGV) else $stderr.puts "Please call me as either 'mkdns323fw' or 'splitdns323fw'; symlinks are good" exit 1 end end dns323-firmware-tools-0.7.3/mkdns323fw000077700000000000000000000000001235171206100215712dns323fw-toolustar00rootroot00000000000000dns323-firmware-tools-0.7.3/mkdns323fw.1000066400000000000000000000102431235171206100174660ustar00rootroot00000000000000.TH MKDNS323FW "1" "October 2008" "dns323-firmware-tools 0.1" "User Commands" .SH NAME mkdns323fw \- build firmware images for the DNS-323 from a kernel and initrd .SH SYNOPSIS .B mkdns323fw -k KERNEL -i INITRD [-d DEFAULTS] -p PRODUCT_ID -c CUSTOM_ID -m MODEL_ID -o OUTPUTFILE .SH DESCRIPTION mkdns323fw creates firmware images suitable for upload to the D-link DNS-323 and other, similar devices based on the same basic firmware image, such as the Conceptronics CH3SNAS. These firmware images contain a kernel and initrd, as well as various product-specific values and checksums. .PP This command can be very dangerous; although it attempts to do some very, very basic sanity checking, it is still quite easy to generate a firmware file that, when loaded into your device, will kill it stone dead. This program is not able to check that you're uploading valid data to your device; if you brick it, you're on your own. .HP \fB\-k\fR kernel, \fB\-\-kernel\fR=\fIkernel\fR .IP specify the file containing the kernel image to embed in the firmware image. This must be a uBoot image file, as produced by .BR mkimage (1) with appropriate options to specify it as a kernel image. Attempts to provide a non-uBoot file will fail, while specifying a non-kernel uBoot file may well brick your device. This option is required. .HP \fB\-i\fR initrd, \fB\-\-initrd\fR=\fIinitrd\fR .IP the initrd file to embed in the firmware image. This must be a uBoot image file, as produced by .BR mkimage (1) with appropriate options to specify it as a ramdisk. The tool will refuse to embed a non-uBoot file, however a dodgy ramdisk will likely brick your device. .HP \fB\-d\fR defaults.tar.gz, \fB\-\-defaults\fR=\fIdefaults.tar.gz\fR .IP The firmware format has the ability to embed a tarball with a default configuration; if you want to do this, you may use this option to do so. However, the devices that the author has dealt with do not require such a thing, and leaving it out still produces a valid firmware (and one that is a bit smaller, to boot). .HP \fB\-s\fR signature, \fB\-\-defaults\fR=\fIsignature\fR .IP For reasons that will probably remain unknown until the ends of time, there are (at least) two different firmware signatures running around that are otherwise identical in their internal structure, which are used for different devices. This option exists to allow you to specify the signature that you want to use in your firmware build. Valid values for this option are currently .I FrodoII .R (the default if this option is not specified), .I Chopper .R or .I Gandolf .R which is used in some devices. See the table at the top of the script if you don't know which value to use for your device. .HP \fB\--p product_id\fR, \fB\-\-product-id\fR=\fIproduct_id\fR .HP \fB\--c custom_id\fR, \fB\-\-custom-id\fR=\fIcustom_id\fR .HP \fB\--m model_id\fR, \fB\-\-model-id\fR=\fImodel_id\fR .IP Specify the product, custom, and model ID that this firmware image is intended for. As several different devices share the same firmware format, the intended device type is encoded in these fields. If you do not specify the correct values for the device that you are targetting with your firmware, it is quite likely that the device will refuse the upload. .IP Known-good values for various devices are provided in the header of the script; please look there for more information. You can also obtain the values you need by downloading an existing firmware for the device you're targetting and doing a bit of digging. .HP \fB\-o\fR outputfile, \fB\-\-output\fR=\fIoutputfile\fR .IP Where to write the completed firmware image. Will overwrite any existing file of the same name. .SH BUGS .PP E-mail bug reports to .BR theshed+dns323-firmware-tools@hezmatt.org . I don't guarantee to be able to help, but I'll give it a shot. Patches are far more helpful. .SH AUTHOR .BR mkdns323fw was written by Matt Palmer, based on reverse-engineering work done by Leschinsky Oleg. .SH COPYRIGHT Copyright \(co 2008 Matt Palmer. .br This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law. .SH "SEE ALSO" .BR mkimage (1), .BR splitdns323fw (1). dns323-firmware-tools-0.7.3/splitdns323fw000077700000000000000000000000001235171206100223152dns323fw-toolustar00rootroot00000000000000dns323-firmware-tools-0.7.3/splitdns323fw.1000066400000000000000000000032041235171206100202110ustar00rootroot00000000000000.TH SPLITDNS323FW "1" "November 2008" "dns323-firmware-tools 0.3" "User Commands" .SH NAME splitdns323fw \- extract data from DNS-323 (and other) firmware images .SH SYNOPSIS .B splitdns323fw [-k KERNEL] [-i INITRD] [-d DEFAULTS] firmwarefile .SH DESCRIPTION splitdns323fw parses an existing firmware image for a device such as the D-Link DNS-323, DNS-321, and Conceptronics CH3SNAS. .PP By default, if only the .I firmwarefile .R option is given, the script verifies the checksums of the included components and prints a bunch of useful info about the image. If you specify one or more of the other options, however, the corresponding section of the firmware image will be written to that file. .HP \fB\-k\fR kernel, \fB\-\-kernel\fR=\fIkernel\fR .IP Write the kernel part of the firmware image to the specified file. .HP \fB\-i\fR initrd, \fB\-\-initrd\fR=\fIinitrd\fR .IP Write the initrd part of the firmware image to the specified file. .HP \fB\-d\fR defaults.tar.gz, \fB\-\-defaults\fR=\fIdefaults.tar.gz\fR .IP Write the defaults part of the firmware image to the specified file. .SH BUGS .PP E-mail bug reports to .BR theshed+dns323-firmware-tools@hezmatt.org . I don't guarantee to be able to help, but I'll give it a shot. Patches are far more helpful. .SH AUTHOR .BR splitdns323fw was written by Matt Palmer, based on reverse-engineering work done by Leschinsky Oleg. .SH COPYRIGHT Copyright \(co 2008 Matt Palmer. .br This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law. .SH "SEE ALSO" .BR mkimage (1), .BR mkdns323fw (1).