Python Challenge 18의 Url은 다음과 같다

Python challenge 18 : http://www.pythonchallenge.com/pc/return/balloons.html


구성

Title은 "Can you tell the difference"이다. 번역하면 "차이점을 말할 수 있는가?" 이다.  문제 자체가 조금 번잡한 편에 속하기 때문에, 바로 해결 아이디어로 들어가 보겠다.


해결 아이디어

우선 진짜문제가 담겨 있는 곳은 이곳이 아니다. 차이점은 '밝기', 영어로 하면 'brightness'이다. 일단 진짜 문제의 url인 brightness.html로 이동하자

http://www.pythonchallenge.com/pc/return/brightness.html

구성은 동일하다.

단 주석창에 새로운 힌트가 생겼다.

deltas.gz로 url을 접근하면 gunzip파일을 다운로드 받을 수 있다. 안에는 txt파일인 data가 들어있다 내부는 다음과 같다.

하나의 파일은 아닌것으로 보인다. 제일 위에 줄인 89 50 4e 47은 PNG 파일의 시그니처이다. 중간 \t같은 공백을 기준으로 왼쪽 파일, 오른쪽 파일에 동일한 89 50 4e 47이 있는 것으로 보아 두 파일은 별개의 파일이다. 쪼개서 png를 만든 결과는 다음과 같다..... 이 X발 티스토리 업로드 가 이상하다. 하여간 두 파일을 PNG로 잘 쪼개었다면 축하한다....

 

"필자와 동일하게 문제 잘못 풀고 있는거다"

 

일단 문제를 푸는 희망은 delta라는 gunzip파일의 이름이다. delta라는건 편차라고 흔히 이야기한다. bightness에서 나왔던 difference라는 단어를 같이 구글링 하면 difflib라는 모듈을 찾을 수 있다.

https://docs.python.org/ko/3/library/difflib.html

 

difflib — 델타 계산을 위한 도우미 — Python 3.9.5 문서

difflib — 델타 계산을 위한 도우미 소스 코드: Lib/difflib.py 이 모듈은 시퀀스 비교를 위한 클래스와 함수를 제공합니다. 예를 들어 파일을 비교하는 데 사용할 수 있으며, HTML 및 문맥(context)과 통

docs.python.org

difflib은 두 문자열의 차이점을 표현해주는 모듈로 대충 아래와 같은 동작을 한다.

그러니까 위 delta.gz에 있는 공백 기준 왼쪽과 오른쪽의 차이를 보고 출력을 하는 코드를 만들면 다음과 같은데,

### 18_1.py

import gzip, difflib

if __name__=="__main__" :
    dat = gzip.open("deltas.gz","rb")

    left = list()
    right = list()
    for line in dat :
        left.append(line[:53].decode())
        right.append(line[56:-1].decode())
        

    diff = difflib.Differ().compare(left,right)

    for x in diff :
        print(x)

이것의 출력 결과는 다음과 같다.

  89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00
  
  ...
  
+ 01 50 00 00 00 8f 08 06 00 00 00 ac f7 83 97 00 00 00
  7e 8e f8 2a a1 a1 4b 0c 86 34 2e 31 90 29 8c 16 cd 12
- b7 50 46 d5 db 73 cc d0 7d 03 d3 37 80 52 88 d7 03 76
+ 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 00 9a 9c 18
  f3 6b be 5a e2 f6 7c 9a 63 78 fe a0 03 5a 55 7b fa 30
- 9f bf 06 92 54 a1 02 81 10 e8 8f 25 20 a3 a1 1a 0b dd
+ 00 00 00 07 74 49 4d 45 07 d5 05 07 0a 37 11 2c 30 95
  a3 3c 20 74 68 c1 a4 48 11 c8 b3 5b f3 aa e5 66 c9 f3
- 39 e8 c6 42 b5 16 ca 1a 3a e6 2a 75 f3 b8 ab f6 c4 84
+ e5 00 00 00 1d 74 45 58 74 43 6f 6d 6d 65 6e 74 00 43
  17 fe e9 ab 81 36 57 f9 54 ed 57 34 4a 29 4d 29 eb e7
+ 72 65 61 74 65 64 20 77 69 74 68 20 54 68 65 20 47 49
  65 1b ae 35 cc 54 ed 2f 79 be 08 f3 78 85 ef 2c 08 54
  a9 e1 50 4d 2c 25 29 e5 96 2c 41 31 42 cb 8d 43 07 a0
+ 4d 50 ef 64 25 6e 00 00 20 00 49 44 41 54 78 da ed 7d
  e3 ae e6 d5 07 87 67 03 3b c6 b4 a4 4a c3 58 24 06 96
  
  ...

그러니까 이 +과 -를 판단해서 왼쪽 파일, 오른쪽 파일, 공통 파일을 만들어서 넣으면 되는 거다.( 참 조금 독특한 점은 기존 delta는 -와 +이 아닌 공백(" ")은 공통적인 문자열을 지칭해서 이번 문제에서도 공통+"+". png와 공통+"-". png 2개를 만들면 된다고 생각을 했는데, "+"과 "-"와 " "으로 3가지를 만들어야 해결이 된다. 헤더도 3개가 따로 있다.)

 

이를 구현한 코드는 다음과 같다. 

### 18_2.py

import gzip, difflib

if __name__=="__main__" :
    dat = gzip.open("deltas.gz","rb")

    left = list()
    right = list()
    for line in dat :
        left.append(line[:53].decode())
        right.append(line[56:-1].decode())
        

    diff = difflib.Differ().compare(left,right)
    
    comm = open("comm.png", "wb")
    left = open("left.png", "wb")
    right = open("right.png", "wb")

    for line in diff :
        bs = bytes([int(x, base=16) for x in line[2:].strip().split(" ") if x])
        if(line[0] == '+'):
            left.write(bs)
        elif(line[0] == '-'):
            right.write(bs)
        else:
            comm.write(bs)

    comm.close()
    left.close()
    right.close()

출력은 다음과 같다.

left.png
right.png
comm.png

공통 사진인 comm.png경로부터 찾아보자 들어가려고 하면 자격증명을 물어본다. 국룰인 huge/file을 해보면 틀리다. butter/fly로 접근을 하면 축하한다. 찾은 거다

Answer Url : http://www.pythonchallenge.com/pc/hex/bin.html


비 고사항인데, 필자는 이거 푸는 게 너무 어려웠다.

difflib모듈은 사용해봤어도 생각도 못하고 있었다. 치열한 전투의 흔적을 남기며 다음 글에서 뵙겠습니다.

<전투의흔적.png>

Python Challenge 17의 Url은 다음과 같다

Python challenge 17 : http://www.pythonchallenge.com/pc/return/romance.html


구성

일단 페이지나, 사진이나, 주석이나 특별한 점은 보이지 않는다. Title인 eat? 이라는 글자를 보아 cookies는 cookies인데, 먹는 쿠키는 아닌 거 같다. 혹시 아래 사진을 기억하는가? 조금 힌트가 될지도 모르겠다.

 


 

해결 아이디어

문제의 아래쪽 사진은 4번문제인 LinkedList의 사진이다.

2021.05.16 - [Python/Python Challenge] - [Python Challenge 4] 무한으로 즐겨요 requests

 

[Python Challenge 4] 무한으로 즐겨요 requests

Python Challenge 4의 url은 다음과 같다. Python challenge 4 : http://www.pythonchallenge.com/pc/def/linkedlist.php follow the chain www.pythonchallenge.com 구성 으흠; 그림이 어떤 걸 의미하는지는 모..

tutoreducto.tistory.com

쿠키를 확인하면 다음과 같은 글을 확인할 수 있다.

오호... 그러니까 이전 문제에서 사용하던 parameter인 nothing대신 busynothing을 사용해 보자고 한다. 바꾸어서 접속해보자(시작번호는 역시 12345이다. 주석에 쓰여있는 대로다)

으흠; 4번에서 왔다면 당장 꺼지라고 써있다. cookies를 확인하자

1. busynothing=44827의 cookies

2. busynothing=45439(44827다음거)의 cookies

오호; cookies가 순서대로 BZ를 이루고 있다. 어디서 많이 본 확장자 아닌가, 바로 bz2모듈을 사용하는 문제였던 8번 문제의 해결방법을 그대로 차용한 듯 하자.

2021.05.19 - [Python/Python Challenge] - [Python Challenge 8] 소난다

 

[Python Challenge 8] 소난다

Python Challenge 8의 url은 다음과 같다. Python challenge 8 : http://www.pythonchallenge.com/pc/def/integrity.html working hard? www.pythonchallenge.com 구성 귀여운 야 울고 있는 꿀벌이 있다. 글자는..

tutoreducto.tistory.com

자 이제 코딩 시간이다.

### 17_1.py

from requests import *
import re
import bz2

if __name__ == "__main__" :
    url = "http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing="
    nothing = "12345"
    answer = list()
    
    while(True) :
        new_url = url+nothing
        res = get(new_url)
        if(len(res.cookies["info"])==3) :
            answer.append(int(res.cookies['info'][1:],base=16))
        else :
            if(res.cookies['info'] == "+") :
                answer.append(32)
            else :
                answer.append(ord(res.cookies["info"]))
        try :
            nothing = re.findall("busynothing is (\d+)",res.text, re.DOTALL)[-1]
        except IndexError :
            break
        print(res.text)
        
    print(bz2.decompress(bytes(answer)).decode())
    

 중간에 +를 공백으로(정수 32 >> 헥스 0x20 >> " ")로 치환하는 부분이 있다. urlencode에서는 +가 공백을 의미한다. 만약 정말 헥스값으로 "+"를 표현했다면 "+"로 써야 되지만, 그게 아니라 글자로 +가 되어있다면 그건 공백으로 치환해야 한다. 결과는 다음과 같다.

and the next busynothing is 64994
and the next busynothing is 66109
and the next busynothing is 37855
and the next busynothing is 36383
and the next busynothing is 68548
and the next busynothing is 96070
and the next busynothing is 83051
is it the 26th already? call his father and inform him that "the flowers are on their way". he'll understand.

그렇다. 그니까 한국말로 번역하면

 

"어머 벌써 26일이 되었나? 그 사람 아버지한테 연락해서 '꽃은 각자 살아가는 방법이 있다고' 전달해줘 이해하실 거야"

 

이다. 오호.. 이번엔 26일이다. 저번 문제를 기억하는가

2021.05.29 - [Python/Python Challenge] - [Python Challenge 16] 다 내 밑으로 정렬해

 

[Python Challenge 16] 다 내 밑으로 정렬해

Python Challenge 16의 Url은 다음과 같다 Python challenge 16 : http://www.pythonchallenge.com/pc/return/mozart.html 구성 무슨 노이즈가 화면에 껴있다. 일단 사진을 다운로드하여보자 사진의 이름은 moza..

tutoreducto.tistory.com

맞다 문제의 주인공은 mozart였다. 그의 아버지 이름을 검색해 보자

성함은 Leopold 되시나 보다. 전화는 어떻게 걸 수 있을까? 바로 Bert는 나쁜 놈에서 나왔던 xmlrpc서버의 phone메서드를 이용해야 된다.(와 이 정도면 이번 문제는 종합 선물세트다) 

2021.05.19 - [Python/Python Challenge] - [Python Challenge 13] 자니??.... 그냥 전화해봤어...

 

[Python Challenge 13] 자니??.... 그냥 전화해봤어...

Python Challenge 13의 url은 다음과 같다. Python challenge 13 : http://www.pythonchallenge.com/pc/return/disproportional.html 구성 가운데 영어는 다음과 같은 의미이다. "나쁜 놈에게 전화할 것" 상호작..

tutoreducto.tistory.com

자 오랜만에 사용하는 xmlrpc 코드다.

### 17_2.py

import xmlrpc.client as xc

if __name__ == "__main__" :
    url = "http://www.pythonchallenge.com/pc/phonebook.php"
    serv = xc.ServerProxy(url)

    print(serv.phone("Leopold"))

출력은 다음과 같다.

555-VIOLIN

참 길었다. violin으로 접속해 보자.

IC 아직 안 끝났다. Title은 It's me. what do you want?이다. 

그러니까 처음  linkedlist의 cookies를 이용해서 얻어냈던 문자열인 'the flowers are ib their ways"를 전달해야 한다. 여태까지 사용했던 info를 key로 보내보자 코딩 타임이다.

### 17_3.py

from requests import *

if __name__=="__main__" :
    url = "http://www.pythonchallenge.com/pc/stuff/violin.php"
    param = "the flowers are on their way"
    param = param.replace(" ","+")
    cookies = {"info":param}

    res = get(url,cookies=cookies,auth=('hugt','file'))
    print(res.text)

출력에는 다음과 같은 문자열이 쓰여 있었다.

"noh well, don't you dare to forget the balloons."

그렇군 X바 진짜 끝이다.

 

Answer Url :  http://www.pythonchallenge.com/pc/return/balloons.html 


종합 선물세트였다. 어려운 건 없었는데, 젠장 기억력이 좋은 편이 아니어서 쿠소.

Python Challenge 16의 Url은 다음과 같다

Python challenge 16 : http://www.pythonchallenge.com/pc/return/mozart.html


구성

무슨 노이즈가 화면에 껴있다. 일단 사진을 다운로드하여보자

< mozart.gif> 

사진의 이름은  mozart.gif이다. 단서로 보일 만한 점은 웹페이지의 title인

 

"Let Me get this Stright!"

 

이다.

 


해결 아이디어

저 영어가 번역하면, "우리 확실하게 짚고 가자!" 인데. Straight를 직선으로 번역을 하면, 정렬을 해달라는 의미가 된다. 일단 사진의 픽셀을 뜯어보면 신기하게 하나의 값만을 가지는 것을 알 수 있다.(RGBA가 아니다.)

### 16_1.py

from PIL import Image

if __name__=="__main__" :
    with Image.open("mozart.gif") as img :
        for x in range(img.width) :
            print(img.getpixel((x,0)))

출력은 다음과 같은데, 특이하게 중간에 195라는 값이 있다. 아마 중간에 보이는 저 자주색 선 같다.

...

17
60
249
195
195
195
195
195
252
88
11
60
84

...

일단 mozart.gif의 모드를 img.mode로 구해보면 'P'모드라고 한다. 이 P 모드는 8비트의 값을 가진다고 한다. 그래서 0~255 사이의 Value만 픽셀 별로 가진 것이다. 다음은 이 195를 기준으로 정렬하고 mozart_a.gif로 저장하는 코드이다.

### 16_2.py

from PIL import Image

if __name__=="__main__" :
    with Image.open("mozart.gif") as img :
        new_img = Image.new("P",(640,480))
        for y in range(img.height) :
            row_array_img = list()
            start_index = 0
            for x in range(img.width) :
                row_array_img.append(img.getpixel((x,y)))
            start_index = row_array_img.index(195)
            for x in range(img.width) :
                real_x = (start_index+x)%img.width
                new_img.putpixel((x,y),row_array_img[real_x])
        new_img.save("mozart_a.gif","GIF")

찾았다. romance

Answer Url : http://www.pythonchallenge.com/pc/return/romance.html

Python Challenge 15의 Url은 다음과 같다

Python challenge 15 : http://www.pythonchallenge.com/pc/return/uzi.html


구성

구성으로 달력사진이 나와있다. 혹시나 저번 시간 cat까지만 움직인 사람들을 위해서 그 고양이 이름은 uzi이다. uzi.html로 냉큼 오자


해결 아이디어

모듈을 사용하자. 저거 잘 보면 다음달께 보인다. 그니까 2월에 달력을 조금 자세하게 보자

으흠; DeBlur로 복구하자 다음과 같은 툴을 사용했다.

http://yuzhikov.com/projects.html

전혀 도움은 되지 않았다. 그러나 자세하게 사진을 확인해보면 오호; 29일이 있다. 그러니까 윤년이라는 거다 연도를 보면 1XX6년임을 알 수 있다. 

1. 1000~2000사이의 일의 자리가 "6"인 년도

2. 윤년

3. 1월시작이 목요일

이 세가지를 만족하는 년도를 찾아내면 된다. 이런 문제를 위한 완벽한 모듈이 있으니 바로 Calendar 모듈이다. 

2021.05.25 - [Python/Python 모듈탐구] - [Python] - 모듈탐구 calendar - 야 오늘 며칠이냐?

* 맞다 이 문제 풀이를 위해서 모듈탐구를 올렸다.

### 15.py

import calendar


if __name__ == "__main__" :

    ans = list()
    
    ### 1000 ~ 2000 사이 일의자리가 "6"인 순서로 탐색
    for i in range(1006,2000,10) :
        cur_year = i
        
        ### isleap함수는 윤년이면 True 반환
        if(calendar.isleap(cur_year)) :
        	### weekday는 년,월,일 순서로 파라미터를 받아 요일을 반환, 목요일은 3
            if(calendar.weekday(cur_year,1,1)==3) :
                ans.append(cur_year)

    print(ans)

 

출력은 다음과 같다.

[1176, 1356, 1576, 1756, 1976]

주석을 잠깐 보고오자

1. 가장 젊은 사람이 아니라고 하니 1756년달력인가 보다

2. 내일을 위한 꽃을 사라는 것을 보니 달력에 동그라미가 되어있는 26일 다음, 즉 1월 27일을 찾는것이다.

 

그러니까 우리가 찾는 날짜는 1756년 1월 27이다. 이게 어떻게 답일까? 구글링 해보자

WOW mozart 찾았다.

Answer Url : http://www.pythonchallenge.com/pc/return/mozart.html

Python Challenge 13의 url은 다음과 같다.

Python challenge 13 : http://www.pythonchallenge.com/pc/return/disproportional.html


구성

 

가운데 영어는 다음과 같은 의미이다.

 

"나쁜 놈에게 전화할 것"

 

상호작용은 5번 다이얼에 href로 phonebook.php로 이동할 수 있다. 주석 소스를 확인하자

저번 꿀벌 문제처럼 area태그에 걸려있는 coords와 href로 이동을 시켜주는구나 싶다.

phonebook.php의 페이지는 다음과 같다.

응? XML Error Code 105로 오류 페이지가 확인된다. 어허... 어디서부터 접근을 해야 되는 것일까


해결 아이디어

모르면 검색이다. 에러에 출력된 대로 XML faultCode 105를 검색하자. 검색에 많이 올라오는 것이 XML-RPC라는 keyword가 압도적으로 많이 보인다. 이게 무엇인지부터 알아보자

 

XML-RPC

RPC프로토콜을 아는가? 원격 프로시저 호출이라는 프로토콜이다. 분산 프로그램에서 많이 이용하는 방법으로 다른 주소 공간에서 함수나 프로시저를 실행시킬 수 있게 해주는 프로토콜로 JSON을 이용한 방법과 XML을 이용한 방법이 있다고 한다. 그럼 Python에서 XML-RPC를 사용할 수 있는 방법을 알아보자

 

Python에서 xmlrpc의 사용

일단 다음의 Doc에서 자세한 사용법을 설명한다.

https://docs.python.org/ko/3/library/xmlrpc.html

 

xmlrpc — XMLRPC 서버와 클라이언트 모듈 — Python 3.9.5 문서

xmlrpc — XMLRPC 서버와 클라이언트 모듈 XML-RPC는 HTTP를 트랜스포트로 사용해서 전달되는 XML을 사용하는 원격 프로시저 호출 방법입니다. 이를 통해, 클라이언트는 원격 서버(서버는 URI로 지정됩니

docs.python.org

 

서버는 Python Challenge에 있으니 우리는 client를 이용해서 붙으면 된다.

필자는 문제에 필요한 함수만을 사용할 것이며 그 항목은 다음과 같다.

  • xmlrpc.client.ServerProxy(url) : 서버에 붙을 수 있도록 client객체를 리턴한다.
  • ServerProxy.system.listMethods() : 서버에서 사용할 수 있는 매머드의 종류를 list로 리턴한다.
  • ServerProxy.system.methodSignature(method) : method의 인자 값의 종류와 리턴 값을 보여준다.
  • ServerProxy.system.methodHelp(method) : method의 사용법을 리턴한다.

다음은 phonebook.php에서 사용할 수 있는 함수를 찾는 코드이다.

### 13.py

import xmlrpc.client as xc

if __name__ == "__main__" :
    url = "http://www.pythonchallenge.com/pc/phonebook.php"
    serv = xc.ServerProxy(url)
    print(serv.system.listMethods())

결과는 다음과 같다.

['phone', 'system.listMethods', 'system.methodHelp', 
'system.methodSignature', 'system.multicall', 'system.getCapabilities']

phone이라는 매더스가 보인다. 사용법을 확인하기 위해 다음과 같은 코드를 작성하자

### 13_2.py

import xmlrpc.client as xc

if __name__ == "__main__" :
    url = "http://www.pythonchallenge.com/pc/phonebook.php"
    serv = xc.ServerProxy(url)

    ### phone메서드의 사용법을 알아낸다.
    print("Usage :",serv.system.methodHelp("phone"))

    ### phone메서드의 인자와 리턴값을 확인한다.
    ### 첫 인덱스는 입력, 두 번째 인덱스는 출력이다.
    sig = serv.system.methodSignature("phone")
    print("In :",sig[0][0]," / out :",sig[0][1])

결과는 다음과 같다.

Usage : Returns the phone of a person
In : string  / out : string

으흠 즉 phone매서드는 string을 받아들여서 string을 반환하는 매서드이다. 예시로 다음을 입력해보자

>>> serv.phone("REDUCTO")
'He is not the evil'

그렇구먼 이제 나쁜 놈을 찾으면 된다. 그런데 나쁜 놈이 누구지? 정답은 저번 글에 있다.

2021.05.19 - [Python/Python Challenge] - [Python Challenge 12] 나쁜놈은 누구인가

 

[Python Challenge 12] 나쁜놈은 누구인가

Python Challenge 12의 url은 다음과 같다. Python challenge 12 : http://www.pythonchallenge.com/pc/return/evil.html 구성 위와 같은 사진이 있다. 사진의 파일명은 evil1.jpg(응? 왜 1이지? 점 심나 가서 먹..

tutoreducto.tistory.com

Bert 네이놈!

>>> serv.phone("Bert")
'555-ITALY'

정답은 ITALY이다. 옛날 *023#을 알고 있는가? 장난전화할 때 많이 쓰던 번호인데(-틀-).... 되었다 하여튼 555는 미국식으로 없는 번호를 의미할 때 뭐 저렇게 한다고 한다. 정답 "ITALY" 찾았다.

Answer Url? : http://www.pythonchallenge.com/pc/return/ITALY.html

더럽게 깐깐하게 구네

Answer Url : http://www.pythonchallenge.com/pc/return/italy.html

Python Challenge 12의 url은 다음과 같다.

Python challenge 12 : http://www.pythonchallenge.com/pc/return/evil.html


구성

위와 같은 사진이 있다. 사진의 파일명은 evil1.jpg(응? 왜 1이지? 점 심나 가서 먹을 거 같아) 웹상에서 상호작용은 특별히 없는 것 같으니 주석을 확인해 보자

title이 dealing evil이다. 돌린다는 것에 이번문제를 해결할 key가 있는 거 같다.


해결 아이디어

사진이름에 이상함을 느끼자 evil1.jpg가 있다는 것은 evil2.jpg도 있다는 것이다. 아래는 evil2.jpg를 요청한 결과이다.

gfx로 다운로드하라고 한다. evil2.gfx를 요청하면 파일을 다운로드할 수 있다. gfx라는 확장자는 애니메이션 파일이지만 문제에서는 이를 표현하고자 한 것은 아닐 것이다. 파일을 hxd로 뜯자

잘 보면 헤더에 간격이 띄워진 상태로 JFIF 등의 문자열이 있는 것을 확인할 수 있다. evil1.jpg에서 5등분이 난 카드처럼 우리도 바이트를 5개로 나누어보자 아래는 구현 코드이다.

### 12.py

if __name__=="__main__" :
    with open("evil2.gfx","rb") as img :
        data = img.read()
        for i in range(5) :
            with open(str(i)+".dat","wb") as f:
                f.write(data[0+i::5])

오호 잘 쪼개졌다. 헤더 확인 결과 각각의 폴더는 다음의 확장자를 가진다.

  • 0.dat : jpg
  • 1.dat : png
  • 2.dat : gif
  • 3.dat : png
  • 4.dat : jpg

사진을 첨부하고 싶은데 알올라간다. 삭선 되어있는 4.jpg를 제외하면 글자는 disproportional이다. 찾았다.

Answer Url : http://www.pythonchallenge.com/pc/return/disproportional.html


참고사항이다. evilN.jpg를 계속 탐색하다 보면 evil4.jpg까지 도달한다. 이미지처럼 보이지 않아도 다운로드하여서 파일을 헥스로 보면 다음의 글이 써져있다.

그렇다. 바트가 나쁜놈이다. 기억하자

+ Recent posts