素敵なおひげですね

PowerShellを中心に気分で書いているブログです。

Code 2015に参加してきました

先週定山渓で行われたCode 2015に参加してきました。

codejapan.jp

全体的なはなし

一応スタッフの立場だったのですが、ほとんど仕事をせずにおり、やったことと言えば名札を作ったくらいでした。すいません...
それでもイベントを開催できて無事終えることができたので良かったです。

Code自体いろいろ楽しかったのですが、今回はその中でも特にイベント中にあった出し物について自分の採った戦法とコードをさらしてみようと思います。*1

出し物について

お題と回答については@tututen師匠のブログを見てください。

tututen.hatenablog.jp

tututen.hatenablog.jp

出し物全体を通して

お題がQRコードを使ったものなのでコードを書く前に手持ちのiPhoneQRコードを読み取ってみて当たりをつけてからコードを書く様にしました。

言語は自分が一番使い慣れているVB.NETにしました。
ただ、最初に自分の使い慣れている言語を選んでしまったためにQRコードを読み取るためのライブラリ選定にかなり苦労しました。
いくつかのライブラリを試してみて最終的に選んだのはZXingの.NET向けポートであるZXing.Netにしました。

zxingnet.codeplex.com

初級編(Q1)について

これは簡単でした。見た瞬間にASCIIコードだとわかります。
これはコードで書かなくても回答可能かと思います。

回答に使ったコードはこんな感じです。

''' <summary>
''' 初級者編の問題を解くクラスです。
''' </summary>
Public Class Q1

    Private Sub New()
        'Private Constructor
    End Sub

    ''' <summary>
    ''' 答えを取得します。
    ''' </summary>
    ''' <returns>答えの文字列</returns>
    Public Shared Function GetAnswer() As String
        'ZXing.NETを使いQRコードを読み込む
        Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader()
        Dim Bitmap = New System.Drawing.Bitmap("[お題のQRコードのあるフォルダ]\01-01.jpg")
        Dim Result = Reader.Decode(Bitmap)

        Return Result.Text.Split(" "c) _
                          .Select(
                                Function(v)
                                    '読み込んだ数字部分をASCII文字に変換
                                    Return System.Text.Encoding.ASCII.GetString(New Byte() {Convert.ToByte(v)})
                                End Function
                            ).Aggregate(Of String)(
                                "",
                                Function(s1, s2)
                                    '各文字を連結して返す
                                    Return s1 + s2
                                End Function
                            )
    End Function

End Class

回答はこんな感じで出てきます。

[CodeJP2015]

中級編(Q2)について

ここが一番苦労しました。
とりあえずBASE64エンコードされてるだろと当たりをつけたものの、デコードしてもそれらしい答えを得ることができず他の暗号化やデータの圧縮方式が無いか暫く探していました。
調べてるなかで、どこの記事かは忘れたのですがCTFの記事でBASE64デコードしたデータをさらにzipかtarで解凍するというのを見つけ、そこからもう一度デコードしてみようという発想を得て答えにたどり着くことができました。

回答のコードは以下。
ただ、実際に答えを探している時はこの様に整形はしておらず、コード片をちょっと使っては修正、さらに使って修正といった感じで汚いコード片の集まりで対処してました。
特にデコードする回数なんかは実際に試してみないとわからないので都度回数を変えたりしてました。

また、ここでQRコードを読み取る際に特定のバーコードが読み取れない現象に遭遇し、Reader.AutoRotate = Trueを設定しないといけないという罠もあり、ライブラリの使い方を理解するのにも苦労させられた感じです。

''' <summary>
''' 中級者編の問題を解くクラスです。
''' </summary>
Public Class Q2

    Private Sub New()
        'Private Constructor
    End Sub

    ''' <summary>
    ''' 答えを取得します。
    ''' </summary>
    ''' <returns>答えの文字列</returns>
    Public Shared Function GetAnswer() As String
        'PNGファイルを読み込んでテキスト文字列を取得する
        Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader()
        Reader.AutoRotate = True
        Dim Base64Text = ""
        Dim Files = System.IO.Directory.GetFiles("[お題のQRコードのあるフォルダ]", "*.png")
        Array.Sort(Files)
        For Each f In Files
            Dim Bitmap = New System.Drawing.Bitmap(f)
            Dim Result = Reader.Decode(Bitmap)
            Base64Text += Result.Text
        Next

        '4回デコードする
        Dim Bytes = New Byte() {}
        For i = 1 To 4
            Bytes = Convert.FromBase64String(Base64Text)
            Base64Text = System.Text.Encoding.ASCII.GetString(Bytes)
        Next

        Return Base64Text
    End Function

End Class

回答は以下

Mt. Moiwa Ropeway,Sapporo Art Park,[Jozankei Hot Springs],Hoheikyo Dam,Marukoma Hot Spring Hotel

上級編(Q3)について

こちらは難しいというよりはひたすら面倒だったという印象です。
あと宴会で結構お酒を飲んでたので上級編に到達した時点くらいで眠さが限界に近付いてたのもあり半分寝ながらの作業でした...次からコードを書くときは飲みすぎない様にします...

中級編で複数回デコードする発想を得たのでとりあえずそれっぽい答えを得るまでBASE64デコードし、Zipファイルをゲットするまではあっさりできました。
ここでいくつかのQRコードReader.Options.PureBarcode = Trueにしないと読み取れない事象に遭遇しましたが、こちらは@Emudomeさんに対処法を教えていただきました。

続けてZipファイルを解凍した後ですが、hint.txt! fizzbuzzについては割とすぐ意図はつかめました。
ただ、これがFizz(3の倍数)Buzz(5の倍数)FizzBuzz(15の倍数)の内のNot (Fizz or Buzz or FizzBuzz)Not (Fizz or Buzz)Not (FizzBuzz)のどれに該当するのかを確かめるのに手間取りました。

あと深夜4時ごろにヒントという名の煽りを見せられた時には心が折れかけました。*2
それでもなんとか翌朝の締め切り直前に答えを得ることができ正解することができました。

回答に使ったコードの全体は以下となります。 こちらも整理済みです。

''' <summary>
''' 上級者編の問題を解くクラスです。
''' </summary>
Public Class Q3

    Private Sub New()
        'Private Constructor
    End Sub

    ''' <summary>
    ''' 第一の答えを取得します。
    ''' </summary>
    Public Shared Function Get1stAnswer() As String
        Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader()
        Reader.AutoRotate = True
        Reader.Options.PureBarcode = True
        Dim Words = ""
        Dim Files = System.IO.Directory.GetFiles("[お題のQRコードのあるフォルダ]", "*.png")
        'Get 1st BASE64 words
        For Each f In Files
            Dim Bitmap = New System.Drawing.Bitmap(f)
            Dim Result = Reader.Decode(Bitmap)
            Words += Result.Text
        Next

        '3回デコードする
        Dim ByteArray = New Byte() {}
        For i = 1 To 3
            ByteArray = Convert.FromBase64String(Base64Text)
            Base64Text = System.Text.Encoding.ASCII.GetString(ByteArray)
        Next

        Return Base64Text
    End Function

    ''' <summary>
    ''' 第一の答えからZipファイルを生成します。
    ''' </summary>
    ''' <param name="fileName">生成するZipファイル名</param>
    Public Shared Sub GetZipFileFromAnswer1(fileName As String)
        Dim Base64Text = Get1stAnswer()
        '
        Base64Text = Base64Text.Substring(23)
        Dim Bytes = System.Convert.FromBase64String(Base64Text)
        Using W = New System.IO.BinaryWriter(New System.IO.FileStream(fileName, IO.FileMode.Append))
            W.Write(Bytes)
        End Using
    End Sub

    ''' <summary>
    ''' 第二の答え(最終回答)を取得します。
    ''' </summary>
    Public Shared Function Get2ndAnswer() As Dictionary(Of String, String)
        Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader()
        Reader.AutoRotate = True
        Reader.Options.PureBarcode = True

        Dim Files = System.IO.Directory.GetFiles("[第一の答えを解凍した後のQRコードのあるフォルダ]", "*.png")
        Array.Sort(Files)

        Dim LineNo = 1
        Dim FizzBuzzText = ""
        Dim NotFizzBuzzText = ""
        For Each file In Files
            Dim Bitmap = New System.Drawing.Bitmap(file)
            Dim DecodeResult = Reader.Decode(Bitmap)
            If (LineNo Mod 3 = 0) Or (LineNo Mod 5 = 0) Then
                'FizzBuzzのパターンにマッチする番号の画像のテキスト
                FizzBuzzText += DecodeResult.Text
            Else
                'FizzBuzzのパターンにマッチしない番号の画像のテキスト
                NotFizzBuzzText += DecodeResult.Text
            End If
            LineNo += 1
        Next
        '
        Dim Bytes = New Byte() {}
        'FizzBuzzのパターンにマッチする場合、11回デコードする
        For i = 1 To 11
            Bytes = Convert.FromBase64String(FizzBuzzText)
            FizzBuzzText = System.Text.Encoding.ASCII.GetString(Bytes)
        Next
        'FizzBuzzのパターンにマッチしない場合、12回デコードする
        For i = 1 To 12
            Bytes = Convert.FromBase64String(NotFizzBuzzText)
            NotFizzBuzzText = System.Text.Encoding.ASCII.GetString(Bytes)
        Next

        Dim Result = New Dictionary(Of String, String)
        Result.Add("FizzBuzz", FizzBuzzText)
        Result.Add("! FizzBuzz", NotFizzBuzzText)
        Return Result
    End Function

End Class

最終回答は以下

FizzBuzz   : [https://goo.gl/xGFW4m]
! FizzBuzz : [Eat.Drink.Sleep.]

とりあえずこんな感じです。

*1:いちおう時間内に上級者編まで回答できたのが自分だけだったのと、@tututen師匠からのコールもありましたので...

*2:実際それでふて寝しましたし...