*nix 경험이 많다면 BZh91을 보자마자 Bzip2 파일의 signature라는 것을 알 수 있다. 우리가 사용하는 대부분의 파일들 (ZIP, JPEG, Bzip2...)은 파일 고유의 signature를 사용하고 있다. 파일 signature를 사용하면 파일의 종류를 파악하기 용이하다.
Bzip2는 이름에서 알 수 있다시피 compression을 수행한다. 파일이 BZh91으로 시작한다면 bzip2으로 압축이 되어있으므로 압축을 풀어야 한다. Python에서는 bz2 module로 Bzip2 compression, decompression을 처리할 수 있다.
bz2 역시 Python의 built-in module이므로 pip install 없이 import bz2로 바로 사용할 수 없다.
bz2.decompress 함수로 Bzip2 파일의 압축을 풀 수 있다. bz2.decompress는 bytes를 argument로 받으니 un과 pw 앞에 b를 붙여서 byte-string으로 만든 후에 사용하자.
HTML 파일 이름은 channel이고 title은 now there are pairs이다. 아직까지는 도움이 될 만한 힌트가 없어서 page source를 살펴봤다.
HTML 주석에 zip이라고 적혀 있다.
<!-- <-- zip -->
지퍼가 달린 옷 사진 밑에는 PayPal 링크가 하나 있는데 이건 문제풀이와는 상관이 없다고 한다. The Python Challenge를 만든 사람이 기부를 해주면 매우 고맙겠다는 멘트를 남기고 싶어서 적은 것 같다.
<!-- The following has nothing to do with the riddle itself. I just
thought it would be the right point to offer you to donate to the
Python Challenge project. Any amount will be greatly appreciated.
-thesamet
-->
위에서 zip이라는 힌트를 줘서 url 을 zip으로 바꿔 봤다.
http://www.pythonchallenge.com/pc/def/channel.zip
URL을 바꿨더니 zip 파일을 받을 수 있었다.
file channel.zip
channel.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
zip 파일의 압축을 푸니 엄청난 양의 숫자.txt 파일들이 나왔다. readme.txt가 있어서 읽어보니 90052부터 시작하라는 힌트와 답은 zip 파일 안에 있다는 힌트를 주었다.
cat readme.txt
welcome to my zipped list.
hint1: start from 90052
hint2: answer is inside the zip
문제와 연관된 90052가 90052.txt밖에 없어서 90052.txt 파일을 읽어보니 next nothing 값을 알려준다. Level4에서 풀었던 문제와 비슷한 것 같다.
cat 90052.txt
Next nothing is 94191
cat 94191.txt
Next nothing is 85503
더 이상 손으로 모든 txt 파일들을 읽는 것은 어렵다고 생각이 들어서 Python 코드를 작성했다.
number=90052
while True:
with open(f'{number}.txt','r') as f:
content=f.read()
print(content)
number=content.split()[-1]
print(number)
if 'Next nothing is ' not in content:
break
코드를 실행하고 나니 맨 끝에 주석을 모으라고 한다. 주석은 zip 파일에 주석을 모으라는 뜻이 아닌가 싶다.
Collect the comments.
comments.
Python은 zip 파일을 다루는 zipfile module이 있다. zipfile은 built-in module이기 때문에 특별한 설치 없이 import zipfile로 사용할 수 있다.
zipfile.ZipFile Object로 zip 파일을 열 수 있다. ZipFile.getinfo 함수는 zip archive member(zip으로 압축 된 파일)에 대한 정보를 알려준다. ZipFile.comment는 zip 파일의 comment를 bytes로 보여준다. 65535 bytes 보다 긴 comment들은 잘린다고 한다.
참고로 Python에서 with open 으로 파일을 열면 자동으로 close 해주기 때문에 open 함수로 파일을 여는 것보다 with open으로 코드를 작성하는 게 더 Pythonic 하다고 한다.
import zipfile
number=90052
comments=[]
while True:
with open(f'{number}.txt','r') as f:
content=f.read()
print(content)
number=content.split()[-1]
print(number)
if number=='comments.':
break
with zipfile.ZipFile('channel.zip', 'r') as zipf:
print(zipf.getinfo(f'{number}.txt').comment.decode())
comments.append(zipf.getinfo(f'{number}.txt').comment.decode())
print(*comments)
각 txt 파일에서 '*'이 comment로 한개씩 나왔는데 comment를 하나씩 모으다 보니 HOCKEY라고 써져 있는 ascii art가 완성되었다.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * *
* * O O O O X X Y Y Y Y G G G G E E E E E E N N N N * *
* * O O O O X X X X X X Y Y Y Y Y Y G G G G E E E E E E N N N N * *
* * O O O O X X X X X X Y Y Y Y Y G G G G E E N N N N * *
* * O O O O O O O O X X X X Y Y G G G E E E E E N N N N * *
* * O O O O O O O O X X X X Y Y G G G E E E E E N N * *
* * O O O O X X X X X X Y Y Y Y Y G G G G E E N N * *
* * O O O O X X X X X X Y Y Y Y Y Y G G G G E E E E E E N N * *
* * O O O O X X Y Y Y Y G G G G E E E E E E N N * *
* * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
URL을 hockey로 바꿨더니 답은 나오지 않았다. 그러나 공기중에 있고 글자를 살펴보라고 한다.
http://www.pythonchallenge.com/pc/def/hockey.html
it's in the air. look at the letters.
자세히 보면 HOCKEY의 각 글자가 글자로 이루어진 것을 볼 수 있다. 불필요한 '*'를 제거하고 살펴보니 O, X, Y, G, E, N으로 이루어져 있다.
In [1]: ascii='''* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
...: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
...: * * * *
...: * * O O O O X X Y Y Y Y G G G G E E E E E E N N N N * *
...: * * O O O O X X X X X X Y Y Y Y Y Y G G G G E E E E E E N N N N * *
...: * * O O O O X X X X X X Y Y Y Y Y G G G G E E N N N N * *
...: * * O O O O O O O O X X X X Y Y G G G E E E E E N N N N * *
...: * * O O O O O O O O X X X X Y Y G G G E E E E E N N * *
...: * * O O O O X X X X X X Y Y Y Y Y G G G G E E N N * *
...: * * O O O O X X X X X X Y Y Y Y Y Y G G G G E E E E E E N N * *
...: * * O O O O X X Y Y Y Y G G G G E E E E E E N N * *
...: * * * *
...: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
...: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * '''
In [2]: print(ascii.replace('*',''))
O O O O X X Y Y Y Y G G G G E E E E E E N N N N
O O O O X X X X X X Y Y Y Y Y Y G G G G E E E E E E N N N N
O O O O X X X X X X Y Y Y Y Y G G G G E E N N N N
O O O O O O O O X X X X Y Y G G G E E E E E N N N N
O O O O O O O O X X X X Y Y G G G E E E E E N N
O O O O X X X X X X Y Y Y Y Y G G G G E E N N
O O O O X X X X X X Y Y Y Y Y Y G G G G E E E E E E N N
O O O O X X Y Y Y Y G G G G E E E E E E N N
OXYGEN은 산소, 그래서 공기 중에 있다고 한 거였다. URL을 oxygen으로 바꿔보면 다음 레벨로 넘어간다.
옛날 컴퓨터에 2의 38 제곱이 적혀 있다. 컴퓨터 밑에 URL 주소를 바꿔보라는 힌트를 줬다.
Python에서는 다양한 방법으로 거듭제곱을 계산할 수 있다. C, C++와 달리 큰 수도 쉽게 계산할 수 있으니 지수가 커져도 걱정하지 않아도 된다.
ipython
Python 3.13.0 (v3.13.0:60403a5409f, Oct 7 2024, 00:37:40) [Clang 15.0.0 (clang-1500.3.9.4)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.29.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: 2**38
Out[1]: 274877906944
In [2]: 2<<37
Out[2]: 274877906944
In [3]: pow(2,38)
Out[3]: 274877906944
URL 주소는 다음과 같다. 0을 274877906944로 바꾸면 다음 레벨로 넘어가지 않을까 싶다.