[SOLUTION] Whiteout (#34)

  • Thread starter Ryan Leavengood
  • Start date
R

Ryan Leavengood

This was a fun one. If I would consider anything my Ruby forte, text
processing would be it. So this was right up my alley. I learned a good
bit too. For example, Fixnum#to_s can take a radix representing the base
you want the number converted to in the String. String#to_i does the
same thing, just in the opposite direction.

I first wrote a simple binary conversion that was inspired by what I
could figure out from the original Perl ACME::Bleach (which wasn't too
much since I'm not a Perl hacker and it was somewhat obfuscated.) Then I
thought I could probably one-up that by making a ternary conversion. I
considered trying higher radixes, but found at least on my editor (Vim)
that only spaces, tabs and newlines were truly "invisible." So ternary
it was, as shown in the code below.

Since I had written two conversions, I decided to make things
interesting and randomly choose which one I used when creating the
files. That should thoroughly confuse people who try and decode any
files that have been "whited out" without knowing the code :)

Anyhow, here is the code (if this weren't a Ruby Quiz I would make this
code much more compact and obfuscated):

# Ruby Quiz: Whiteout (#34)
# Solution by Ryan Leavengood
#
# There are two ways of "whiting out", one that uses a binary
# encoding of spaces and tabs on each line (preserving the
# original newlines), and a ternary encoding that makes newlines
# part of the code and encodes any of the original newlines. The
# method of encoding is chosen at random. In theory other
# non-printable characters could be added to increase the radix
# used for encoding, but I think the best cross-platform "whiting
# out" can be had using spaces, tabs and newlines.

REQUIRE_LINE = "require 'whiteout'"

class WhiteoutBinary
attr_reader :id

WHITESPACE = " \t"
DIGITS = '01'

def initialize
@id = " \t\t"
end

def paint_on(paper)
paper.map do |line|
line.chomp.unpack('b*')[0].tr(DIGITS, WHITESPACE)
end.join("\n")
end

def rub_off(paper)
paper.map do |line|
[line.chomp.tr(WHITESPACE, DIGITS)].pack('b*')
end.join("\n")
end
end

class WhiteoutTernary
attr_reader :id

WHITESPACE = " \t\n"
DIGITS = '012'
# This allows up to 22222 ternary, which is 242 decimal, enough
# for most of ASCII
DIGIT_LENGTH = 5
RADIX = 3

def initialize
@id = " \t\t\t"
end

def paint_on(paper)
paper.join.gsub(/./m) do |c|
c[0].to_s(RADIX).rjust(DIGIT_LENGTH,'0')
end.tr(DIGITS, WHITESPACE)
end

def rub_off(paper)
paper.join.tr(WHITESPACE, DIGITS).gsub(/.{#{DIGIT_LENGTH}}/) do |d|
d.to_i(RADIX).chr
end
end
end

bottle_holder = [WhiteoutBinary.new, WhiteoutTernary.new]

if $0 == __FILE__
ARGV.each do |filename|
wo_name = "#{filename}.wo"
File.open(wo_name, 'w') do |file|
whiteout = bottle_holder[rand(2)]
paper = IO.readlines(filename)
if paper[0] =~ /^\s*#!/
file.print paper.shift
end
file.puts REQUIRE_LINE
file.puts whiteout.id
file.print whiteout.paint_on(paper)
end
File.rename(filename, filename+'.bak')
File.rename(wo_name, filename)
end
else
paper = IO.readlines($0)
paper.shift if paper[0] =~ /^\s*#!/
paper.shift if paper[0] =~ /^#{REQUIRE_LINE}/
id = paper.shift.chomp
whiteout = bottle_holder.find {|bottle| bottle.id == id}
if whiteout
eval whiteout::rub_off(paper)
else
puts "Error: This does not appear to be a valid whiteout file!"
exit(1)
end
end
__END__

Ryan Leavengood
 
D

Dominik Bathon

Here is my solution. It is quite similar to all the others already posted.

I use Zlib::Deflate to compress the source file, then the bytes are
converted to base 3 and represented by spaces, tabs and newlines.
This way the result is approx. 3 times bigger than the source.

Dominik

The code:

require "zlib"

def encode_to_ws(str)
str=Zlib::Deflate.deflate(str, 9)
res=""
str.each_byte { |b| res << b.to_s(3).rjust(6,"0") }
res.tr("012", " \t\n")
end

def decode_from_ws(str)
raise "wrong length" unless str.length%6 == 0
str.tr!(" \t\n", "012")
res=""
for i in 0...(str.length/6)
res << str[i*6, 6].to_i(3).chr
end
Zlib::Inflate.inflate(res)
end

if $0 == __FILE__
if File.file?(f=ARGV[0])
str=IO.read(f)
File.open(f, "wb") { |out|
if str =~ /\A#!.*/
out.puts $&
end
out.puts 'require "whiteout"'
out.print encode_to_ws(str)
}
else
puts "usage #$0 file.rb"
end
else
if File.file?($0)
str=File.read($0)
str.sub!(/\A(#!.*)?require "whiteout".*?\n/m, "")
eval('$0=__FILE__')
eval(decode_from_ws(str))
else
raise "required whiteout from non-file"
end
end
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top