UNMENTIONABLES = Range.new(0, 255).select {|ord|
  ord < 32 || ord > 126
}.map {|ord| ord.chr }.join

def hexdump(block)
  # Format: <address> <8 bytes> <8 bytes> |<16 chars>|
  result = ""

  count = (block.length / 16).to_i
  slack = (block.length % 16).to_i

  Range.new(0, count - 1).each {|index|
    offset = index * 16
    sample = block[offset, 16]

    fmt = "%08x  " + ("%02x " * 8) + " " +
      ("%02x " * 8) + " |%s|\n"
    args = []
    args << (offset % 0x100000000)
    sample.each_byte {|byte| args << byte }
    args << sample.tr(UNMENTIONABLES, '.')
    result += sprintf(fmt, *args)
  }

  # Handle slack
  if slack > 0
    offset = count * 16
    sample = block[offset, slack]

    fmt = "%08x  "
    if slack >= 8
      fmt += ("%02x " * 8) + " "
    end
    fmt += ("%02x " * (slack % 8)) + " "

    args = []
    args << (offset % 0x100000000)
    sample.each_byte {|byte| args << byte }

    tmp = sprintf(fmt, *args)
    tmp += " " * (60 - tmp.length) if tmp.length < 60
    tmp += sprintf("|%s|\n", sample.tr(UNMENTIONABLES, '.'))
    result += tmp
  end
  result
end