<![CDATA[nenw*]]>https://blog.nenw.dev/https://blog.nenw.dev/favicon.pngnenw*https://blog.nenw.dev/Ghost 5.101Sat, 21 Mar 2026 01:31:05 GMT60<![CDATA[구글 밋이 사용하고 있는 편법들]]>구글 밋을 사용해보다가 문제해결 및 도움말 이라는 버튼을 누를 경우 다음과 같은 UI가 표시되는 것을 확인해볼 수 있었습니

]]>
https://blog.nenw.dev/how-google-meet-shows-users-cpu-usage/67714eb25df352000114f613Wed, 30 Mar 2022 12:21:53 GMT구글 밋을 사용해보다가 문제해결 및 도움말 이라는 버튼을 누를 경우 다음과 같은 UI가 표시되는 것을 확인해볼 수 있었습니다.

놀랍게도 여기에는 디바이스의 CPU 사용량이 표시되고 있었는데, 이를 보고 처음으로 든 생각은 이것이 보안에 위협이 되지 않는가 하는 생각이었습니다. 저게 표시된다면 웹에서 사용자의 동의 없이 자유롭게 CPU 사용량을 얻어갈 수 있다는 뜻이고, 이는 충분히 사이드 채널 어택 등에 이용될 수 있을 것 같아 보였습니다.

위와 같은 표준이 있었다면 분명히 한 번 논란이 되었을 법 하기에 어떤 표준일지 궁금했는데, 아무리 찾아봐도 그런 표준에 대해서는 보이지 않았고, navigator 에도 비슷한 속성은 보이지 않았습니다.

그래서 조금 더 찾아본 결과 크롬 익스텐션에서는 퍼미션이 있다면 chrome.system.cpu 라는 속성을 통해서 CPU 사용량을 획득할 수 있음을 확인했습니다. 이를 통해 저는 구글 밋이 일반적인 방법으로 CPU 사용량을 얻어오는 것은 아님을 확신했는데, 당연히도 쉽게 CPU 사용량을 가져올 수 있는 API가 있다면 굳이 저런 익스텐션용 API를 만들 필요도 없을 것이고 그걸 퍼미션까지 요구하게 설계하지는 않았을 것입니다.

그러고보니 묘하게 구글밋이 파이어폭스, 심지어는 같은 엔진을 사용하고 있는 크로미움 엣지나 웨일 브라우저에서까지도 비디오 가상 배경을 지원하지 않았던 것이 기억났습니다. 그 때 구글링을 했을 때에는 WASM SIMD 지원이 안돼서 그렇다는 말이 있었는데, 상식적으로 같은 크로미움에서까지 동작하지 않는 것이 이상하게 여겨졌습니다.

그래서 혹시나 해서 chrome.runtime.sendMessage를 후킹했더니 다음과 같은 메시지가 지속적으로 전송되는 것을 확인할 수 있었습니다.

nkeimhogjdpnpccoofpliimaahmaaome 이라는 확장 프로그램으로 지속적으로 요청하고 있었습니다.

해당 확장 프로그램을 구글링해보니 Google Hangouts 라는 확장 프로그램이었으며, (현재는 이름이 구글 밋으로 바뀌었을 수도 있겠네요) 크롬에는 그 외에도 숨겨진 확장 프로그램이 훨씬 더 많았습니다. 더 찾아보니 WebRTC 지원이 제대로 되기 전에는 저 확장 프로그램을 가지고 화면공유를 했었나보네요.

이건 편법이라는 생각이 듭니다. 자사가 브라우저를 만든다는 이유만으로 자사 서비스를 일반적인 웹의 바운더리를 넘어서 개발한다니요.

저는 개인적으로 파이어폭스가 "사용자를 위한 브라우저"의 마지막 보루라고 생각하고 있습니다. 그러나 파이어폭스의 점유율이 점점 더 낮아지고 있고, 회사 사정은 더욱 어려워지고 있다는 것을 알고 있었기에, 오늘 알게 되었던 이 사실이 더욱 씁쓸하게 느껴졌던 것 같습니다.

]]>
<![CDATA[윈도우 메모리 누수 대처하기]]>윈도우는 저에게 있어서 참 애증의 관계입니다. 우선 윈도우는 개발하기에 참 불편한 플랫폼이라고 개인적으로 생각합니다

]]>
https://blog.nenw.dev/dealing-with-windows-memory-leak/67714eb25df352000114f609Mon, 03 Jan 2022 13:24:02 GMT윈도우는 저에게 있어서 참 애증의 관계입니다. 우선 윈도우는 개발하기에 참 불편한 플랫폼이라고 개인적으로 생각합니다. 우선은 도커(WSL 사용해야 함), 레디스(Memurai 사용해야 함) 등 네이티브하게 지원되지 않는 것들이 많습니다. 그리고 일반적인 경우에는 Posix 와 호환이 되지 않기 때문에 서버와 유사한 환경을 구축하기도 어렵고 타 OS와 달라도 너무 다르다고 생각합니다. 그래서 모든걸 내려놓고 아치리눅스를 메인으로 쓰고자 몇번이나 다짐하긴 했었는데, 게임 등 여러 프로그램으로 인해 어쩔 수 없이 윈도우를 메인으로 사용하고 있습니다.

그럼에도 몇 번씩 인내심의 한계를 느끼는 일이 있는데, 그 중 하나가 메모리 누수였습니다. 작업관리자에서 분명히 사용하는 프로세스는 없는데 제 메모리는 축나있는 일이 많았습니다. 이로 인해 메모리를 24기가까지 증설했음에도 항상 메모리가 가득차는 것을 보고 칼을 빼들어야겠다고 생각했습니다.

풀 사용량 확인

우선 가장 처음으로 한 일은 풀의 사용량을 확인하는 거였습니다.

제 작업관리자 성능 / 메모리 사진인데, 페이징 풀과 비 페이징 풀이 각각 700MB / 941MB 를 차지하고 있습니다. 이 정도면 정상적인 경우입니다만, 혹시라도 풀이 비 정상적으로 높게 차지할 경우에는 Windows Developer Kit에 딸려오는 Poolmon이라는 툴을 이용해서 어떠한 드라이버에서 메모리를 많이 차지하는지 체크해보시면 될 것 같습니다.

B를 눌러 바이트 순으로 정렬하신 후에 앞에 Tag에 해당하는 데이터로 찾으시면 됩니다. [Pool Tag로 드라이버 찾는 법]

좀비 프로세스 체크하기

두 번째로 많은 이유가 있을 수 있겠지만, 다른 하나의 이유는 좀비 프로세스입니다. 리눅스 계열에만 좀비 프로세스가 있는 줄 알았는데 윈도우에도 좀비 프로세스가 있었습니다. 하지만 놀랍게도 윈도우는 모든 API 등에서 좀비 프로세스를 꽁꽁 숨깁니다. 따라서 작업관리자에서도 보이지 않았고, 발견이 늦었던 것 같습니다.
[좀비 프로세스를 찾아서]

문제는 도대체 어떠한 프로세스가 좀비 프로세스를 만드는지, 그리고 어떤 좀비 프로세스를 만드는지를 찾는 것입니다. 우선 제가 사용한 툴은 RamMap이라는 툴인데, 이는 좀비 프로세스 뿐만 아니라 전반적인 메모리 누수를 해결하기에 굉장히 좋은 툴입니다. 마이크로소프트 도큐먼트 또는 SysInternals 공식 홈페이지에서 받으실 수 있습니다.

RamMap을 처음 켜시면 무엇이 얼만큼 사용하고 있는지 직관적으로 확인하실 수 있습니다. 간략한 사용예가 들어있는 마이크로소프트 테크 커뮤니티 글을 남기도록 하겠습니다.

아무튼, Processes 탭에 수 많은 프로세스가 있다면 좀비 프로세스일 가능성이 높은데, 저같은 경우는 conhost.exe, node.exe, git.exe 등이 정말 많이 들어있었습니다. 따라서 처음에는 clink powerline의 버그라고 생각하였지만 결론은 아니었지 않나 싶습니다.

그러던 중 Random ASCII 라는 블로그의 좀비 프로세스 관련 글을 발견하였는데, 해당 글 작성자분께서 제작하신 FindZombieHandles 라는 툴을 사용하여 찾아본 결과, 약 7만개의 핸들이 열려있었습니다.

결론

저 프로세스들이 어째서 좀비 프로세스로 살아있는 지는 모르겠지만, 왜 생기는 지는 알게 되었습니다. 제 컴퓨터에 pm2를 통해서 내부 서버를 운영하고 있었는데, 스크립트에 버그가 있어서 무한히 재시작하느라 node가 계속 실행되는 것이었습니다. 정확한 원인은 모른채로 그냥 그것만 수정하고 나니 메모리 릭이 잡혀서 그대로 살았는데, 최근에 시그윈 도큐먼트를 읽다가 혹시 이건가? 하는 부분은 생겼습니다.

Cygwin FAQ 에 따르면 복잡한 fork() 구현으로 인해서 가끔씩 프로세스 생성에 이슈가 있다고 하는데 알려진 충돌하는 프로세스 목록에는 제가 사용하는 여러 프로그램들 (Avira Antivir, ConEmu, MacType 등)이 있었습니다. 이로 인한 문제들 중에는 메모리릭/핸들릭과 랜덤한 fork() 실패가 있다고 합니다. 후자는 이미 겪고 있기 때문에 전자도 해당되는게 아닐까 현재로서는 추측만 하고 있습니다.

]]>
<![CDATA[최근에 배운 것들]]>그냥 개인적으로 정리하기 위해서 씁니다.

  • 안드로이드 부트루프
    잘 쓰던 안드로이드가 부트루프가 걸리는 일이 있었습니다
]]>
https://blog.nenw.dev/recently-i-have-learned-2021-07/67714eb25df352000114f610Sat, 03 Jul 2021 06:12:15 GMT그냥 개인적으로 정리하기 위해서 씁니다.

  • 안드로이드 부트루프
    잘 쓰던 안드로이드가 부트루프가 걸리는 일이 있었습니다. dmesg 로 체크해보니 error changing dalvik-cache ownership 이라는 문제가 있길래, 그냥 깔끔하게 달빅 캐시를 날려주니 해결되었습니다.
  • alpine libc
    나이스 체크플러스 바이너리 (CPClient)가 알파인리눅스 도커 이미지 상에서 안 돌아가는 문제가 있었습니다. 분명히 파일이 있음에도 "No such file or directory" 에러를 띄워서 뭐지하고 봤는데, 알파인 특성 상 musl을 사용하는 바람에 libc 링크가 깨져서 일어나는 일이었습니다. Qiita에 올라온 글 을 따라서 libc6-compat 을 설치해서 해결했습니다.
  • innerText
    innerText에 대해서 별 생각을 안 가지고 있었는데, 최근에 MDN을 보다가 textContent와는 달리 숨겨진 요소의 텍스트를 제거하기 위해 리플로우를 일으킨다는 사실을 알았습니다. 원래도 안 썼지만 안 쓸 이유가 하나 더 늘었습니다.
]]>
<![CDATA[좀 더 나은 번들 최적화를 위해]]>https://blog.nenw.dev/optimizing-the-kaede-skin/67714eb25df352000114f60fSat, 06 Mar 2021 11:05:20 GMT이전 글에서는 이 블로그 테마에 적용되는 노토 산스를 조금 더 예쁘게 보이게 하기 위해서 어떻게 해야하는 지를 실험해보았습니다.

이 글에서는 그 폰트를 어떻게 최적화해서 카에데에 실었는지, 또 그 외에 블로그를 조금 더 빠르게 로딩시키기 위해서 어떠한 일들을 했는지 정리를 해보려 합니다.

폰트 개수 줄이기

우선 카에데는 쓸데없이 폰트를 너무 많이 쓰는 경향이 있었습니다. 카에데 1.0에서 사용했던 폰트들을 나열해보면 다음과 같습니다.

Noto Sans KR, Noto Sans JP, Raleway, Nanum Square, Iosevka, RoboNoto

KoPubWorldDotum도 사용하였으나, 이는 따로 서빙은 하지 않고 로컬에서만 불러오게 하였기 때문에 예외입니다.

아무튼, 이렇게 폰트가 너무 많으면 통일감도 없어 보이고 사용자가 너무 많은 폰트를 다운받게 되는 문제도 있어서 과감히까지는 아니지만 몇 개를 쳐냈습니다.

그래서 카에데 1.1 버전부터는 다음과 같은 폰트만을 사용하게 됩니다.

코드 폰트: Iosevka, Noto Sans KR_JP (Fallback)
UI 폰트: Raleway
본문 및 제목폰트: Noto Sans KR_JP

우선 코드 폰트에서는 Fallback으로 사용되던 RoboNoto를 지웠는데, 이는 코드에서 한글은 거의 주석에만 사용되기 때문에 굳이 고정폭을 유지하는 것이 의미가 없어보였기 때문입니다. 또한, 애초에 Iosevka와 RoboNoto의 폭을 통일하지 않아서 원래부터 고정폭이 아니었다는 이유도 있습니다.

UI 폰트로는 이전에 제목 용도로 한정되어 사용하던 나눔스퀘어를 빼버렸습니다. 굳이 몇 자 나눔스퀘어로 적자고 모든 폰트를 받아오는 것이 굉장히 비 실용적이라고 생각했기 때문입니다.

그리고 Noto Sans KR과 JP를 합쳐 Noto Sans KR_JP 를 만들었는데, 이에 대해서는 이 다음 문단에서 설명하겠습니다.

웹폰트 파이프라인 개선

이전에 Noto Sans KR과 JP는 구글폰트로부터 서빙이 되고 있었습니다. 해외에서는 이전부터 구글폰트를 사용하지 말자는 글이 몇 개 있었는데, 특히 최근에 CDN 자원들이 더 이상 cross-site 간 공유되어 캐싱되지 않게 됨에 따라 더더욱 그런 글이 많아졌습니다.

저는 그러한 글들을 보면서 조금 부럽게도 느껴졌는데, 그 이유 중 하나는 한글은 워낙 Glyph 수가 많아서 구글폰트처럼 Subsetting 해서 서빙하지 않으면 너무 고용량이 되어버리기 때문입니다.

구글폰트의 서브셋팅 예시, 1 Noto Sans KR = 119 Chunks

그래서 지금까지 저는 따로 대안이 없다고 생각하고 구글폰트를 썼지만, 카에데를 업데이트 하면서 이전 게시글에서 설명했듯이 폰트의 렌더링을 조금 바꾸기 위해 직접 호스트하게 되었습니다. 그 과정에서 최적화를 위해 제가 직접 서브셋팅을 해보자고 다짐을 하였고, 제가 생각한 서브셋팅의 기본적인 방법은 다음과 같습니다.

  1. 기본적인 ASCII 영역은 하나의 chunk로 묶자
  2. 한글과 한자는 빈도순으로 정렬한 후에, 많이 쓰이는 글자는 많이 쓰이는 글자끼리, 적게 쓰이는 글자는 적게 쓰이는 글자끼리 묶어서 chunk를 만들자
  3. 너무 잘 안 쓰이는 영역은 다 하나로 묶어서 크게크게 chunking하자

3번과 같은 경우는 조금 의아해하실 수도 있는데, 이유는 간단합니다. 서로 멀리 떨어져있는 유니코드끼리 묶어서 Chunk를 하나 만들 때마다 스타일시트의 용량이 너무 많이 늘어납니다.

Noto Sans KR:wght@400의 gzip되지 않은 스타일시트가 92KiB 정도 나가는데, 사실 스타일시트의 용량이 너무 커져버리면 서브셋팅을 안할 때보다 더 다운로드를 많이 해버릴 수 있어서 최대한 스타일시트의 크기도 최적화하고 싶었습니다.

그래서 해당 규칙대로 폰트를 최적화해주는 프로그램을 하나 만들었습니다. 파이썬의 fontTools 라이브러리 기반이고, 직접 파이프라인을 명시하면 해당 파이프라인을 따라서 폰트를 이리저리 수정하는 형태입니다.

debug_pipeline: False
pipeline:
    -
        name: 'input'
        glob:
            - 'files/input/**/*.ttf'
            - 'files/input/**/*.otf'
            - 'files/input/**/*.woff'
            - 'files/input/**/*.woff2'

    -
        name: 'cff_to_glyf'

    -
        name: 'add_gasp'
        mode: 'replace'

    -
        name: 'parse_attribute'
        key: 'path'
        pattern: '^(?P<group>.*?)_.*$'

    -
        name: 'parse_attribute'
        key: 'group'
        pattern: '^.*(?P<fn1>\d)\d{2}$'

    -
        name: 'merge'
        output_template: '{fn1}.ttf'
        merge_by: 'group'
        merge_base:
            key: 'path'
            match: '^.*_kr.*$'

    -
        name: 'replace_attribute'
        keys:
            - 'family_name'
            - 'full_name'
            - 'postscript_name'
            - 'typographic_family_name'
        patterns:
            -
                find: '(?<!\w)[kK][rR](?!\w)'
                replace: 'KR_JP'

    -
        name: 'replace_attribute'
        keys:
            - 'family_name'
            - 'typographic_family_name'
        patterns:
            -
                find: 'KR_JP.*$'
                replace: 'KR_JP'

    -
        name: 'set_font_name'
        attributes:
            - 'family_name'

    -
        name: 'subset'
        group_by:
            # Basic Latin
            -
                name: 'unicode_blocks'
                include:
                    - 'Basic Latin'
                    - 'Latin-1 Supplement'

            # Basic Korean
            - 'hangul_2574'

            # Basic Japanese
            -
                name: 'unicode_blocks'
                merge_blocks: true
                max_chunk_size: 2048
                include:
                    - 'Hiragana'
                    - 'Katakana'

            # Ideographs
            - 'ideograph_jouyou'
            - 'ideograph_frequency'

            # Other CJK-Related
            -
                name: 'unicode_blocks'
                include:
                    - 'Hangul Jamo'
                    - 'Hangul Compatibility Jamo'
                    - 'CJK Symbols and Punctuation'
                    - 'CJK Strokes'
                    - 'CJK Compatibility'

            # Symbols
            -
                name: 'unicode_range'
                max_chunk_size: 2048
                range: 'U+2000-2BFF'

            # Emojis
            -
                name: 'unicode_range'
                max_chunk_size: 1024
                range: 'U+1F000-1FAFF'

            # Remaining Glyphs
            -
                name: 'all'
                max_chunk_size: 65536


        order_by: 'wikipedia_frequency'
        max_chunk_size: 384

    -
        name: 'add_program_info'

    -
        name: 'set_attribute'
        attributes:
            display: 'swap'

    -
        name: 'output'
        extensions:
            - 'woff'
            - 'woff2'

        output_fonts: 'files/output/noto/{root}.{ext}'
        output_css: 'files/output/noto/stylesheet.css'
        output_preview: 'files/output/preview.html'

현재 이 블로그에서 사용중인 노토 산스는 위와 같은 파이프라인을 따라 Noto Sans KR과 JP를 합친 후 서브셋팅을 해 제작이 되었고, HelloWorld017/nenwfont 에서 직접 받아서 실행해보실 수 있습니다.

nenwfont의 폰트 서브세팅 예시

결과 분석

구글폰트에 비해 얼마나 최적화가 잘 되었나 개인적으로 궁금하였고, 그래서 직접 비교분석을 해봤습니다.

구글폰트 nenwfont
폰트[1] 852KiB[2] 732KiB
스타일시트[3] 216KiB[4] 92KiB

  1. 제 블로그 첫 페이지를 끝까지 로딩했을 때 기준입니다. ↩︎

  2. Noto Sans JP 28KiB, Noto Sans KR 824KiB ↩︎

  3. gzip 압축된 기준입니다. ↩︎

  4. Noto Sans JP 122KiB, Noto Sans KR 94KiB ↩︎

물론, 제대로 테스트를 돌린 것도 아니고 그냥 제 블로그 메인 페이지 하나로만 테스트한 거라 신뢰성은 전혀 없습니다. 그리고 CFF를 glyf로 바꾸는 과정이나 기타 다른 과정에서 폰트 자체의 크기가 영향을 받았을 확률도 커서 이 데이터를 일반화하는 건 절대 불가능합니다.

하지만, 어느 정도 쓸만하게는 나온다는 사실을 알 수 있었고, 나중에 서브셋팅을 좀 더 개선해서 제대로 테스트해보고자 하는 마음이 들었습니다.

기타 번들 최적화

이 전부터 카에데는 웹팩의 트리셰이킹 등을 이용해 번들을 최적화해오고 있었습니다. 특히, 번들을 분석했을 때 Moment.js 의 그 악명높은 번들 크기를 한번 체험하고 나서 date-fns 로 전부 갈아엎는 등 최적화가 좀 진행되었습니다.

그럼에도 불구하고, 최근 번들 크기가 커져 확인해본 결과 date-fns/locales 를 import하면 쓸 locale만 import하더라도 전부 번들에 들어가는 현상을 발견하였습니다. 그래서 date-fns/esm/locales import로 변경하여 문제를 해결하였습니다.

date-fns 교체 이전
date-fns 교체 이후

또한, 자꾸만 CDN과 브라우저에 이전 버전이 캐싱되는 문제를 해결하고자 웹팩 설정을 조금 바꿔 뒤에 hash query를 달게 하였습니다.

그러자 문제가 일어나는 건 link[rel="preload"] 였는데, 이전에는 일일이 예상되는 파일들을 직접 html에 link[rel="preload"] 로 박아 넣고 있었습니다. 하지만 이제 빌드할 때마다 파일의 주소가 바뀌어서 그걸 직접 넣을 수 없게 되었습니다. 그래서 @vue/preload-webpack-plugin 를 통해 link[rel="preload"] 가 자동으로 생성되게 하였습니다.

결과는 다음과 같습니다.

Fast 3G에서의 다운로드 (preload 적용 전)
Fast 3G에서의 다운로드 (preload 적용 후)

Waterfall에서 보실 수 있듯이 적용 전에는 HTML을 가져오고, 메인 스크립트와 중요도가 낮은 것들을 먼저 로딩하고, 정작 중요한 스크립트와 스타일은 그 이후에 가져오고 있었습니다.

적용 후에는 HTML만 가져오고 중요한 파일들을 처음에 한꺼번에 로딩해 약 0.5초 정도 (Fast 3G 기준) 더 빨라진 것을 확인할 수 있었습니다.

결론

이 블로그에서 사용하는 카에데 테마는 1.1버전으로 업데이트 하면서 많은 최적화와 기능의 개선이 이루어졌습니다. 다시 한 번 최적화 작업은 굉장히 손이 많이 간다는 사실을 깨닫는 시간이었던 것 같습니다.

]]>
<![CDATA[좀 더 나은 폰트 렌더링을 위해 (수정됨)]]>https://blog.nenw.dev/playing-with-noto-fonts/67714eb25df352000114f60eThu, 25 Feb 2021 07:51:41 GMT발단

개인적인 의견이지만, 저는 윈도우의 ClearType이 심미적으로 굉장히 좋지 않다고 생각합니다. 실제로도 디자인한 작업을 구현했을 때 어딘가 마음에 안 들어서 하나하나 뜯어보면 폰트 렌더링의 문제가 꽤나 큽니다. 저는 이 문제를 해결하기 위해 많은 호환성 이슈를 떠앉으면서까지 MacType을 깔아서 사용하고 있습니다. 하지만, 모든 윈도우에 MacType을 깔 수는 없는 셈이기에 제가 만든 웹 사이트가 타 윈도우 유저 입장에서 안 이쁘게 보이는 문제는 어떻게 해결할 수가 없습니다.

그래서 저는 항상 ClearType 하에서도 잘 보이는 폰트를 찾아 헤매고는 합니다. 그 결과로 KoPub 과 같이 ClearType을 썼을 때도 이쁘게 보이는 폰트들을 몇 개 찾기는 했지만, 가뜩이나 적은 한글 본문용 산세리프 OFL 폰트 중에서 ClearType 하에서도 예쁘게 보이는 폰트는 많지 않습니다.

그 이유로 이 블로그의 스킨인 Kaede는 로컬에 KoPubWorldDotum이 깔려있다면 그걸 먼저 쓰고, 그게 아닌 경우에는 Noto Sans KR을 받아오도록 설정되어 있습니다. (아쉽게도 KoPub 폰트는 출판인회의에 연락해본 결과 임베딩을 현재 허용하고 있지 않다고 합니다.)

하지만 Noto Sans KR도 힌팅의 문제인지 안티 앨리어싱이 어딘가 2% 부족한 느낌이 든다고 개인적으로 생각합니다. 그러던 도중에 한 티스토리 블로그에서 좀 더 나아진 듯 한 외곽선을 가진 Noto Sans KR을 사용하고 있는 사실을 발견했습니다.

notokr vs Google Fonts

그래서 그 Noto Sans KR는 어떤 차이점이 있어서 구글 폰트의 Noto Sans KR과 다르게 렌더링이 되는지 폰트 레벨에서 분석해보기로 마음을 먹었습니다.

참고로 이 글에 첨부된 이미지는 윈도우 10 (1809) 크롬88 기준으로 렌더링된 것입니다. 크롬이니 아마 Skia로 렌더링 됐지 않을까 싶은데 자세히는 모르겠습니다.

그러나 파이어폭스, 엣지(EdgeHTML)와 같은 다른 브라우저에서도 notokr과 Google Fonts 간의 유의미한 렌더링 차이는 존재했습니다.

분석

우선은 해당 파일이 어디서 왔는지부터 알아볼 필요가 있었습니다. 구글에 해당 파일의 파일명인 notokr을 검색해본 결과 다음과 같은 사이트에서 배포하고 있는 것을 발견하였습니다. martian36

폰트를 얻은 후에는 구글폰트 버전과 notokr 버전을 비교해보았습니다.

먼저 fonttools로 주어진 woff2 파일을 읽고, 구글 폰트에서 배포하는 woff2를 읽어 차이점을 확인해보았습니다. 가장 처음 보이는 차이점은 바로 가지고 있는 테이블이 다른 것이었습니다.

  • notokr: 'FFTM', 'GDEF', 'OS/2', 'cmap', 'gasp', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'webf'
  • Noto Sans KR: 'CFF ', 'GPOS', 'GSUB', 'OS/2', 'VORG', 'cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'post', 'vhea', 'vmtx'

둘다 prep 이나 fpgm 과 같은 테이블은 보이지 않았습니다.

조금 더 쉽게 데이터를 확인하기 위해 fonttools를 이용해 xml 형식인 ttx 파일로 내보내기 해서 작업했습니다. TTFont.saveXML 을 이용하면 ttx 파일로 내보내실 수 있습니다.

모르는 테이블은 다음과 같은 도큐먼트를 참고해서 데이터를 확인해 보았습니다.

그런데 커스텀 테이블과 같이 보이는 모르는 테이블이 있어 검색해보니 다음과 같은 좋은 글도 있었습니다.

gasp 테이블

제일 처음 의심한 것은 바로 gasp 테이블이었습니다. notokr에는 \x00\x01\x00\x01\xff\xff\x00\x0f 로 구성되어 있는 gasp 테이블이 들어있는 반면에, 구글 폰트 버전에는 들어있지 않았습니다.

해당 내용을 분석해보면 1개의 gasp range가 들어있는데, FF FF (65535) ppem 까지 GASP_GRIDFIT, GASP_DOGRAY, GASP_SYMMETRIC_GRIDFIT, GASP_SYMMETRIC_SMOOTHING 을 적용하라고 되어 있었습니다.

특히 GASP_SYMMETRIC_SMOOTHING 의 설명을 보면 Use smoothing along multiple axes with ClearType 이라고 되어 있었습니다.

그래서 이것으로 안티 앨리어싱에 차이가 발생했나 하고 gasp 테이블을 바꿔보았습니다.

from fontTools.ttLib import TTFont, newTable

target = "./noto-sans-kr-v13-latin_korean-regular"

def patch_font():
    font = TTFont("./%s.woff2" % target)

    print("Exporting TTX File...")
    font.saveXML("./%s.ttx" % target)
    
    print("Adding gasp table...")
    gaspTable = newTable('gasp')
    gaspTable.gaspRange = {
        0xffff: 0xf
    }
    font['gasp'] = gaspTable
    
    print("Exporting WOFF2 File....")
    font.flavor = 'woff2'
    font.save("./%s_patched.woff2" % target, reorderTables = True)
    
    print("Done! :)")
    

gasp 테이블을 바꿔본 결과 놀랍게도 렌더링 결과에 별 차이가 없었습니다.

제가 봤을 때는 정말 1픽셀의 오차도 없이 같은 것 같은데, gasp 테이블이 렌더링에 미치는 영향이 원래 별로 없는지 아니면 제가 뭔가 실수를 했는지 좀 더 자세한 분석이 필요해 보입니다.

OpenType vs TrueType

아무튼 gasp 테이블은 별로 영향이 없는 것 같고, 다른점을 조금 더 찾아보았습니다. 우선은 구글폰트의 버전은 OpenType (CFF)로 되어 있고, notokr은 TrueType (glyf)로 되어 있습니다.

먼저 알아두어야 할 것은 오픈타입은 3차 베지어 커브를 사용하고 트루타입은 2차 베지어 커브를 사용하기 때문에 완벽하게 오픈타입에서 트루타입으로 변환할 수 있는 방법은 없습니다. 하지만, 최대한 가깝게 근사를 해서 변환할 수는 있습니다.

그래서 일단 Cu2qu를 이용해서 구글폰트의 버전을 트루타입으로 변환해 보았습니다. (다음 프로젝트를 참고했습니다.) 변환한 결과 근사 알고리즘이 달랐는지 notokr과 Contour 정보가 다르긴 했습니다. 하지만, 변환된 파일 또한 notokr과 비슷한 렌더링 결과를 낳았습니다.

라인 메트릭 등이 조금 다르긴 하지만 그래도 거의 유사하게 렌더링 되는 것을 확인할 수 있습니다.

결론

확실하지는 않지만, OTF 글리프 정보 (CFF)를 TTF 글리프 정보 (glyf)로 변환할 경우에 다음과 같은 결과가 일어나는 것을 확인할 수 있었습니다.

결론은 굉장히 간단하긴 한데, 그래도 이렇게 폰트를 뜯어보면서 폰트의 구조에 대해서도 조금 더 자세히 이해할 수 있어서 더 좋았던 것 같습니다.

그리고 처음 알게된 것 중 하나에는 많이 흥미로운 내용도 있습니다. 그것은 바로 fpgm 테이블에 들어가는 인스트럭션은 튜링완전하다는 건데, 참고했던 nixeneko님의 블로그에 이를 이용해 의사난수를 집어넣어 테두리가 랜덤하게 움직이는 폰트를 만드는 글도 있었습니다.

이걸 가지고 신기한 걸 많이 만들 수 있을 것 같은데 나중에 한 번 시도해 봐야겠습니다.

여담

notokr은 메타데이터를 확인해본 결과 FontSquirrel Webfont Generator로 만들어진 것 같습니다. 이 사이트에는 아까 봤던 gasp을 설정해주는 것과 같은 옵션들 또한 있었습니다.

또한, 해당 사이트에서 변환된 파일들이 FFTM 테이블을 가지고 있는 것으로 보아 내부적으로 FontForge를 사용해서 변환하는 것 같습니다. 저는 처음에 FontForge로 폰트를 저장하는 과정에서 폰트에 변형이 일어난다고 생각했는데, 완전 오산이었습니다. 이 글도 사실 그런 잘못된 정보를 담고 있었다가, 실험을 몇 번 더 해보며 잘못됐다는 것을 깨닫고 다시 쓴 글입니다.

사실 변형 자체는 일어나는게 맞는 것 같습니다. Contour 정보가 다르긴 했는데, 렌더링에 유의미한 결과는 주지 못한 것 같습니다.

]]>
<![CDATA[Overlayfs에 대한 간단한 삽질]]>서론

셋업 후 한 동안 유지보수를 해주지 않았던 나스를 최근 열심히 유지보수 하고 있습니다. 오래된 도커 이미지들을 다시 빌

]]>
https://blog.nenw.dev/shoveling-on-overlayfs/67714eb25df352000114f60cMon, 15 Feb 2021 15:17:07 GMT서론

셋업 후 한 동안 유지보수를 해주지 않았던 나스를 최근 열심히 유지보수 하고 있습니다. 오래된 도커 이미지들을 다시 빌드해서 최신 이미지로 업데이트 하고, 새로운 서비스도 설치 과정에 있습니다.

유지보수 과정 중에 진행한 또 다른 작업은 바로 새 하드를 끼우는 것입니다. 예전부터 새 하드를 끼우면 용량 확장을 어떻게 하지에 대한 고민을 많이 했었는데, 고민의 이유는 다음과 같습니다.

  1. RAID 1/5/6 을 사용할만큼의 재정적 여유가 없음
    현재 나스 프로젝트의 구성원이 전부 학생인지라, 나스를 사용하기 위해 하드를 한 번에 여러개 구매한다고 했을 때 재정적인 문제로 구성원의 찬성을 얻기가 힘듭니다.
  2. 이전 하드를 읽기 전용으로 유지하고 싶음
    이건 제 개인적인 바람인데, 백업의 편의성이나 기타 이유로 한번 끝까지 사용한 하드는 더 이상의 내용 수정을 하고 싶지 않습니다.
  3. 각 하드디스크는 따로 떼어놓았을 때도 유효한 파일시스템이었으면 좋겠음
    이 또한 제 개인적인 바람으로, rsync 등으로 백업하기 편했으면 좋겠기 때문입니다.

사실 1번 이유가 제일 중요했고, 2번과 3번 이유는 그냥 그러면 좋고 아니면 말고 수준이긴 했습니다. 어찌됐든, RAID 0이나 JBOD 같은 걸 사용하기에는 레이드 어레이가 깨졌을 때가 걱정이 되었습니다. 그래서 그런 선택지들을 제외하고 2가지 방법을 생각해냈습니다.

  1. 각 하드디스크별로 폴더를 분리 (/HDD1, /HDD2 의 경로로 접근하게)
  2. 전체 하드디스크를 유니온 마운팅

1번 방식은 폴더 관리가 힘들다는 이유로 기각당해서 2번으로 하기로 결정하였습니다.

유니온 마운팅

유니온 마운팅의 장점은 저의 니즈를 전부 충족해줄 수 있다는 것이었고, 단점은 우선 속도가 느리고 완전히 POSIX-Compliant 하지는 않다는 것입니다. 하지만 생각보다 속도의 차이가 얼마 나지 않았고, POSIX-Compliant 하지 않은 것도 정말 atime이 업데이트 되거나 하지 않는 거 빼면 거의 없었기 때문에 유니온 마운팅은 굉장히 매력적인 선택지였습니다.

유니온 마운팅의 대표적인 구현체에는 Aufs와 Overlayfs가 있습니다. Aufs는 우선 여러개의 writable branch를 지정해 어떤 순서로 쓸지도 설정할 수 있는 등 굉장히 세밀하게 옵션 설정이 가능합니다. 하지만 그 대신에 Overlayfs에 비해 살짝 성능이 뒤쳐진다는 말이 있습니다. 그리고 무엇보다도 현재 커널에 머지되어 있지 않은 것으로 알고 있습니다.

저의 사용 케이스는 Overlayfs로도 충분히 커버될만큼 복잡하지 않아서 Overlayfs를 사용했습니다.

Overlayfs

하지만 마지막까지 제가 Overlayfs 사용을 머뭇거리게 했던 것은 바로 위키백과의 한 문장이었습니다.

OverlayFS does not support renaming files without performing a full copy-up of the file; however, renaming directories in an upper filesystem has limited support.

과연 진짜로 그럴까? 하고 서버에서 테스트 해보니 정말로 파일 이름을 바꿀 때마다 전체 파일을 복사하는 현상이 일어났습니다.

현재 나스에는 주로 Makemkv로 리핑을 떠둔 블루레이라던가 라이브 스트리밍을 다운로드 받아놓은 것들 등등이 있습니다. 즉, 파일 하나하나가 굉장히 고용량이며, 혹시라도 폴더 정리를 하다가 실수로 이름을 바꾸면 그것들을 전부 복사하게 되는 것이었습니다.

따라서 Overlayfs에 Copy-Up 을 막는 옵션을 추가할 생각을 해보았습니다. 처음으로는 fuse-overlayfs 를 개조해보았습니다. 실제로 테스트는 해보지 않았지만, 다음과 같이 수정하면 되지 않을까 생각하였습니다.

--- a/main.c
+++ b/main.c

@@ -2892,5 +2892,7 @@
 static int
 copyup (struct ovl_data *lo, struct ovl_node *node)
 {
+  errno = EXDEV; 
+  return -1;
   int saved_errno;
   int ret = -1;

그러나 fuse를 이용하지 말고 그냥 커널 코드 자체에 옵션을 추가하는 것도 나쁘지 않을 것 같아서 커널에 있는 overlayfs 코드를 조금 보았습니다. 그래서 다음과 같이 수정하면 될 것 같았습니다.

--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -20,1 +20,2 @@
 	bool metacopy;
+ 	bool copyup;

--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -436,1 +436,3 @@
 	OPT_METACOPY_OFF,
+	OPT_COPYUP_ON,
+	OPT_COPYUP_OFF,
@@ -458,1 +460,3 @@	{OPT_METACOPY_OFF,		"metacopy=off"},
+	{OPT_COPYUP_ON,		"copyup=on"},
+	{OPT_COPYUP_OFF,	"copyup=off"},
@@ -611,1 +615,8 @@ 
+	case OPT_COPYUP_ON:
+		config->copyup = true;
+		break;
+
+	case OPT_COPYUP_OFF:
+		config->copyup = false;
+		break;

--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -934,1 +934,4 @@
 	int err = 0;
+	struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
+	if (!ofs->config.copyup)
+		return -EXDEV;

물론, 위 코드들은 전부 테스트 되지 않은 코드들입니다.

그리고 실제로 사용할 계획도 없어졌습니다. 그 이유는, 알고보니 파일 이름을 바꾸는 것은 Copy-Up을 필요로 하지 않았기 때문입니다.

Copy-Up 없는 rename

그러면 왜 서버에서 테스트 할 때는 Copy-Up이 일어났냐는 문제가 있는데, 그 이유는 간단히 제 서버의 커널 버전이 낮아서입니다. 실제로, 아치리눅스를 구동중인 제 노트북에서 테스트했을 때에는 Copy-Up 없이 복사가 되었습니다.

심지어 같은 디바이스일 때만 Copy-Up이 안 일어나는 것도 아니라서, 5MB짜리 루프 디바이스를 만들어 이를 upper layer로 설정하고 30MB짜리 파일 이름을 바꿔도 아무런 문제도 일어나지 않았습니다.

debugfs 를 사용해보면 정확하게 어떤 일이 일어나는지 이해하기 쉬운데, 최신 버전에서는 디렉토리 이름을 바꾸는 것 처럼 파일 이름을 바꿀 때에도 xattr을 설정해서 redirect로 처리하는 듯 합니다.

이걸 서버에서 쓰려면 당연히 서버의 커널을 업데이트 해야만 했고, Ubuntu 20.04로 업데이트 한 결과 서버에서도 파일 이름을 바꿀 때 Copy-Up이 일어나지 않았습니다.

결론

그래서 좀 삽질을 하긴 했는데, 결론만 말하면 다음과 같습니다.

최신 커널에 있는 Overlayfs에서는이름 바꿀 때에도 Copy-Up이 일어나지 않습니다.
]]>
<![CDATA[저의 윈도우 개발환경]]>주의 이 글은 저에 대한 내용을 담고 있으며, 별로 영양가가 없는 글입니다.

저는 데스크탑에서 개발만 하는 것이 아니라 게임도

]]>
https://blog.nenw.dev/development-on-windows/67714eb25df352000114f608Tue, 22 Dec 2020 18:59:42 GMT주의 이 글은 저에 대한 내용을 담고 있으며, 별로 영양가가 없는 글입니다.

저는 데스크탑에서 개발만 하는 것이 아니라 게임도 하고 다른 온갖 작업들을 하기 때문에, 어쩔 수 없이 윈도우를 사용합니다. 하지만 개발자들 사이에서 종종 윈도우는 개발하기 어려운 플랫폼이라던가 개발자가 맥을 많이 쓰는 데에는 이유가 있다던가와 같은 말이 들려옵니다.

사실, 완전히 틀린 말은 아닌게 *nix 계열의 서버를 사용하는 것이 대부분이고 윈도우는 어떻게 보나 *nix와는 거리가 있어보입니다. 이 글에서는 제가 그러한 윈도우에서 어떻게 개발을 하고 있는지에 대해 잠시 서술해보려고 합니다.

커맨드라인 인터페이스

Cmder + clink + MSYS
Alacritty + MSYS (zsh)

명령어

우선, 윈도우는 사용하는 명령어부터가 *nix 계열과는 다릅니다. 예를 들어, 주로 사용하는 cp나 ls 같은 명령조차 윈도우에서는 실행되지 않습니다.

왜 WSL을 안 쓰시나요?

보통, 이에 대한 해결책으로 WSL을 많이 사용합니다. 그러나 저는 WSL을 주로 사용하지 않으며, 이 이유는 이는 말 그대로 WSL은 "서브시스템" 이고 윈도우와 긴밀하게 연계되어있지 않다는 느낌을 받았다는 것입니다. 예를 들어, WSL2에 들어가면서 어느정도 해결하려 하는 것으로 알지만 파일시스템은 여전히 NTFS이기 때문에 꼬이는 부분이 있고 리눅스의 파일들을 윈도우에서 수정하기도 까다롭거나 하는 등의 문제가 있습니다. 또한, 제가 WSL을 통해 실행시키지 않은 프로그램에서 보았을 때는 윈도우 그대로이기 때문에 명령어의 실행이 불가능한 점도 있습니다.

왜 MSYS를 쓰시나요?

WSL 이전에는 시그윈을 이에 대한 해결책으로 많이 사용하곤 하였으나, 저는 그냥 가장 간단한 MSYS를 사용합니다. 참고로 MSYS는 Cygwin의 포크인데, cygwin의 dll 없이도 작동하게 한다는 것 같습니다만 저도 자세한 사항은 잘 모릅니다.

아무튼 MSYS는 단순히 윈도우를 위해 미니멀(M)한 시스템(SYS)를 제공해주는 것인데, 단순히 말하자면 컴퓨터에 ls.exe, bash.exe같은 게 있어서 ls 명령을 실행했을 때 제대로 동작하는 것과 같습니다. MSYS를 사용함에 있어서의 이점은 WSL과 비교했을 때 새 시스템을 얹어서 무거워지는 것을 방지하고, 윈도우 시스템을 그대로 가져갈 수 있다는 것입니다.

MSYS가 정답인가요?

물론 단점도 있습니다. 아까 전의 설명의 정반대인, 윈도우에 적절히 리눅스 명령어를 섞어 쓰는 것이 아니라 완전한 POSIX 환경이 필요하시다면 MSYS를 사용하시면 안됩니다. 굳이 완전한 POSIX 환경까진 아니더라도, 예를 들어 같은 이름을 가진 윈도우 명령이 있을 때에는 윈도우 명령이 우선적용되어서 alias를 따로 지정해줘야하는 등의 소소한 문제 또한 있습니다.

그리고 파일이 많아질 경우에 ls 명령어는 굉장히 느려집니다. 그거 뿐만 아니라 무거워지는 것을 방지한다는 이전의 설명과는 다르게 MSYS는 꽤나 느립니다. 저도 왜 느린지에 대해 조사해본 결과, fork() 를 윈도우에서 구현하기 위해 굉장히 많은 일을 해서 느리다는 설이 있는 것 같습니다. 저도 이유에 대해서는 확실히 모르겠습니다.

하지만 저와 같은 경우 윈도우에서 적당히 리눅스 명령어를 사용할 수 있는 정도로만 필요했고, 이런 니즈를 MSYS가 잘 충족시켜 주었기에 MSYS를 사용합니다.

라인 에디팅

윈도우에서는 주요 명령어만 안 되는 것이 아니라, 제 서버에서 습관처럼 사용하는 reverse-i-search 와 같은 라인 에디팅도 당연히 지원되지 않습니다.

라인 에디팅과 같은 기능을 위해서는 저는 Clink를 사용하는데, 이것은 그저 cmder에서 기본으로 줘서 쓰다보니까 쓸만하네 하고 쓰는 것이지 다른 분들은 bash를 쓰시는게 더 좋을 수도 있을 것 같습니다. 특히, bash의 문법은 cmd의 문법과는 꽤 많이 다르기 때문에 bash가 더 나을 것 같습니다. bash를 추천드립니다.

하지만 Clink로도 많은 작업들이 되기 때문에 저는 우선 현재는 Clink를 사용하고 있습니다. lua 확장 또한 지원하기 때문에, 파워라인처럼 깔끔한 상황표시가 필요했던 저는 이를 만들어서 사용하는 것도 가능했습니다.

+ 20.12.30 추가
현재는 MSYS에 포함되어 나오는 zsh를 사용하고 있습니다. 하지만 MSYS에 포함된 bash / zsh는 clink와는 달리 *.cmd나 *.bat을 실행하기 위해 확장자가 필요하다거나, 그 외에도 윈도우와는 소소하게 다른 사항들이 많아서 하위 호환성을 중요하게 여기시는 분들에게는 아직 clink가 유효한 답안이라고 생각합니다.

+ 21.01.21 추가
주의할 점은, zsh-async 가 윈도우에서는 제대로 작동하지 않아 (Cygwin 코드나 zpty 코드 등을 찾아보면서 디버깅을 시도해봤지만 어째선지 제대로 작동하지 않았습니다.) 윈도우를 지원하면서도 비동기가 되는 테마가 별로 없었습니다. 현재 Agkozak 이라는 테마를 찾아 agkozak-nenwflavoured 로 커스터마이징을 해둔 상태입니다.

하지만, 아무리 비동기로 돌아가는 쉘 테마에 빠르기로 유명한 Zinit을 쓰고 하더라도 MSYS로 하는 zsh 실행 자체가 너무 느렸습니다. 이런 이유로 다시 Clink로 돌아간 상태입니다.

터미널

터미널과 같은 경우에는 무엇을 사용할지 굉장히 많이 고민한 부분이기도 하였습니다. 우선 가장 유명한 Hyper 터미널이나 Terminus alpha 등 여러가지를 써보고 그 중에서 차선책으로 Cmder를 사용하고 있습니다.

우선 다른 터미널을 사용하지 않은 이유는 순전히 속도와 UX 때문입니다. Hyper와 Terminus alpha, FluentTerminal과 같은 경우 시작속도가 너무 느려 사용하기 힘들었고, 깃 배시는 기능이 너무 적었습니다.

마이크로소프트의 터미널이 cmder의 대안으로 가장 적합했던 거 같았지만 아직 사용해보지 않았으며 곧 사용해보려고 합니다.

Cmder(ConEmu)의 장점은 우선 빠르고, 설정이 풍부하고, 무난히 잘 돌아간다는 점입니다. 특히 CJK 폰트 지정이나 다른 데에 있던 잔 버그들이 Cmder를 쓸 때는 없었습니다. 그리고 기능이 매우 다양해서 다른 돌아가는 프로세스에 붙인다던가 하는 기능도 지원을 합니다.

Cmder(ConEmu)의 단점은 일단 디자인이 구리고, 너무 오래되어서 Fluent한 투명같은 기능들의 구현이 힘들다고 제작자가 쐐기를 박은 점 등입니다. 그것 말고도 다른 터미널 에뮬레이터와 비교했을 때 매우 빠르고 버그도 거의 없지만, node의 자동완성이나 스크롤이 느려지는 버그 같은 건 있습니다.

+20.12.30 추가
현재에는 Alacritty 라고 하는 터미널을 실험적으로 사용해보는 중에 있습니다. 이 터미널은 러스트 기반에 OpenGL을 통해 돌아가는 물건인데 Cmder보다도 빠르게 구동되는 것이 매력적입니다. 뭐 여전히 Fluent한 투명도 같은 건 지원하지 않지만, 그래도 사용하며 버그를 굉장히 못 느꼈고 만족스럽게 사용할 만한 터미널 같습니다. 무엇보다도 멀티플랫폼 지원이라 리눅스 환경에서도 같은 터미널 에뮬레이터를 사용할 수 있습니다. 대신에 기능을 최대한 절제하는 건지 아직 미구현된 건지는 모르겠지만, 가장 중요한 Fallback Font와 같은 기능들이 없으며 탭과 같은 기능조차도 tmux 등을 대신해서 사용해야 합니다.

물론, MSYS의 tmux는 mintty 외의 터미널 에뮬레이터에서 거의 동작하지 않습니다. 이 문제는 mintty외에는 pseudo-terminal이 생성되지 않아 /dev/pty* 대신에 /dev/cons* 가 현재 터미널로 인식되는데, 해당 파일은 윈도우의 한계로 현재 콘솔 세션 프로세스가 아닌 이상 접근이 되지 않아서입니다. 해당 문제를 해결하기 위해 util-linuxscript.c 파일을 보며 alacritty가 켜질 때 새로운 pseudo-terminal을 만들어 그 위에서 쉘을 실행시키는 프로그램을 만들어보는 중입니다.

+ 21.01.21 추가
Alacritty를 최대한 써보려고 시도했지만, 우선 터미널 자체가 쓰면 쓸 수록 버그가 보였습니다. 특히 복잡한 레이아웃일 수록 레이아웃 전체가 깨지는 일도 많고 해서 어쩔 수 없이 다시 Cmder로 돌아갔습니다.

그리고 무엇보다도, 스크롤바 지원이 없습니다. (!)

Alias

Alias는 다음과 같이 설정해서 사용중입니다.

aliasedit="C:\Program Files (x86)\Notepad++\notepad++.exe" "%CMDER_ROOT%\config\user_aliases.cmd"
clear=cls
e.=explorer .
find="C:\Program Files\Git\usr\bin\find.exe" $*
gl=git log --oneline --all --graph --decorate  $*
history=less +G "%CMDER_ROOT%\config\.history"
historygrep=cat "%CMDER_ROOT%\config\.history" | grep $*
ll=ls -a -l --color $*
ls=ls --show-control-chars -F --color $*
npp="C:\Program Files (x86)\Notepad++\notepad++.exe" $1
pwd=cd $*
su=cmd /k "%ConEmuDir%\..\init.bat" -new_console:a:.
sudo=sudo cmd /c $*
vi=vim $*

다음은 서버에 접속하는 용도로 사용되는 alias 입니다.

(서버 이름)=putty.exe -new_console -load "(서버 이름)"
(서버 이름)pull=scp -i "(개인 키 경로)" $3 -P (포트) (유저명)@(주소):$1 $2
(서버 이름)push=scp -i "(개인 키 경로)" $3 -P (포트) $1 (유저명)@(주소):$2

PuTTY 를 이용해 접속하게 alias를 설정해뒀는데, 큰 이유는 없고 ssh보다 터미널 관련 버그를 적게 만날 수 있어서입니다. (ex: tmux에서 레이아웃이 깨짐)

최근에는 ssh 명령어로도 많이 사용하고 있습니다.

(서버 이름)=ssh (이름)@(주소) -o ProxyCommand="C:/Windows/system32/openssh/ssh.exe -W %h:%p (이름)@(프록시 주소) -p (프록시 포트)"

다음과 같이 프록시 서버를 통해서 접속하는 용도로 많이 사용합니다. PuTTY 자체도 지원하지만 ssh로 alias를 잡아두면 -new_console 플래그 (Cmder 예약)를 붙이지 않아도 되어 새 탭을 열리지 않아 사용합니다.

참고로 윈도우의 ssh가 PuTTY보다 버그가 많긴 합니다. 특히 vim이나 tmux 같은 TUI의 레이아웃이 깨지기 쉽상입니다. 그래도 일단은 쓸만은 하다고 생각합니다.

텍스트 에디터

Jetbrains 사의 IDE, Atom Editor, 또는 Notepad++

우선 텍스트 에디터가 필요한 상황은 그 때 그 때 다릅니다. 사소한 설정 파일 하나를 빠르게 편집하는 상황, IDE와 같은 강력한 기능이 필요한 상황, 텍스트 에디팅만 필요하지만 프로젝트에서 작업하는 상황 등이 있습니다.

뭐 서버에서야 vim을 사용하지만, 저는 GUI 편집기를 조금 더 선호하기 때문에 많은 에디터 중 하나를 선택해야만 했습니다.

우선 IDE를 고르는 건 쉬웠습니다. 옛날에는 이클립스를 주로 사용했으나 젯브레인즈의 IDE를 써본 후에 젯브레인즈 사가 IDE를 굉장히 잘 만든다는 사실을 깨닫고 주로 러스트나 C++, 자바, C# 등을 편집하는데 Clion이나 Rider, IDEA 등을 사용합니다. phpStorm 또한 예전에는 많이 사용하였지만 요즘엔 php를 주로 다루지 않기 때문에 사용하지 않고 있습니다.

사소한 설정 파일 편집과 같은 것은 폴더를 열지 않았으면 좋겠고 매우 빠르게 열리는 에디터를 원했습니다. 예전에 (중학생 시절에) 잠깐 서브라임 에디터를 사용하기도 했었는데 저장할 때 결제에 대해 물어보는 건 원치 않아서 초등학교 시절부터 꾸준히 Notepad++를 사용하고 있습니다.

그리고 그 외의 경우에는 저는 거의 대부분의 프로젝트를 하면서 아톰 에디터를 사용하고, 주변으로부터 질문도 많이 받았습니다.

왜 VSCode 대신에 아톰을 쓰시나요?

와 같은 질문을 말이죠. 이유는 간단히 한마디로 말해서 Legacy 적인 이유입니다. 저에게 누군가 에디터로 뭘 쓰냐고 묻는다면 전 과감히 VSCode를 쓰라고 할 것입니다.

우선 제가 주로 자바나 php개발을 하면서 eclipse를 사용하다가, 중학교 때 node.js 개발을 처음 시작하면서 텍스트 에디터를 하나 정해서 써야겠다고 정했습니다. 그 당시에는 한창 신뢰의 깃허브에서 Atom을 내놔서 너도 나도 사용할 시기였고 저도 Atom 외에는 선택지가 별로 없어보여 Atom을 사용하기 시작했습니다.

그리고 얼마 있다가 VSCode가 나왔는데 Atom Shell (현 일렉트론)을 가지고 개발을 시작했다고 하고 별로 좋아하지도 않는 마이크로소프트에서 만드는 거고, 아톰을 놔두고 굳이 왜 개발을 하지 싶은 생각도 많이 들었기 때문에 VSCode는 곧 망할 것이라고 생각했습니다. 물론, 제 생각은 완전히 빗나갔고 Atom은 그저 Electron을 만들었다는 의의만을 가진 채 점점 인기가 줄어가는 상황입니다.

서론이 길었네요. Atom에서 VSCode로 사람들이 넘어가기 시작한 가장 큰 이유는 느려서라고 다들 말하더라고요. 하지만 저는 VSCode로 쉽사리 넘어갈 수가 없었습니다.

가장 큰 이유는, 에디터 커스터마이징 때문입니다. 저는 아톰을 사용하면서 불편한 점이 하나 있을 때마다 전체 에디터 CSS나 이런것에 손을 대었으며, 제가 원하지만 없는 플러그인이 있을 때마다 만들어서 썼습니다. 물론, atom-discord 와 같은 플러그인은 제가 만든 후에 다른 분들로부터 VSCode용으로 따로 만들어도 되냐고 허락을 구하는 이메일이 날라오기도 했었고, 실제로 VSCode용으로도 구현이 되었습니다. atom-live2d 도 최근에 찾아보니 VSCode용이 생긴 것 같더라고요.

하지만 그 외에 제가 사용하는 수십개의 플러그인 중에 대체하기 어려운 플러그인도 많고 에디터 커마를 워낙 오랜 시간에 걸쳐서 해왔기 때문에 이걸 다시 VSCode용으로 옮기는 작업은 쉽지 않았습니다. 2번 정도 시도를 해보았지만 불편하여 버틸 수 없었습니다.

우선, 아톰이 일반적인 상황에서 그리 느린 것도 아닐 뿐더러 제가 작업하는데 필요한 대부분의 것이 이미 완벽하게 맞춰져있기 때문에 굳이 VSCode로 옮길 이유를 찾지 못했습니다.

아무튼 Atom이 느린 것도 확실하고 저무는 해인 것도 확실하기 때문에 지인이 에디터를 물어볼 때는 항상 VSCode를 추천했습니다.

]]>
<![CDATA[노트북에서 arm64 안드로이드 게임 돌리기]]>이전 글에서 이어지는 글입니다.

서론

예전에 해당 글을 쓸 때에는 Android 9 버전 용 64비트 지원 libhoudini가 없어 arm64-v8a 만 지원하는 게임을 돌

]]>
https://blog.nenw.dev/arm64-on-x86-laptop/67714eb25df352000114f60bSun, 06 Dec 2020 07:39:12 GMT이전 글에서 이어지는 글입니다.

서론

예전에 해당 글을 쓸 때에는 Android 9 버전 용 64비트 지원 libhoudini가 없어 arm64-v8a 만 지원하는 게임을 돌릴 수가 없었습니다.

그런데, 최근에 일본 뱅드림이 구글 플레이에서 데이터 불러오기 기능이 제대로 작동하지 않아 민트자매 이벤트 기간임에도 불구하고 한 판도 플레이하지 못하게 되었습니다. 이런저런 문제를 의심하던 도중에 혹시 이 문젠가 하고 libhoudini를 바꾸는 과정에서 64비트 지원이 최근 추가됐다는 사실을 알게 되게 되어 후속글을 쓰게 됩니다. 물론 뱅드림은 아직도 로그인이 안되는 상태입니다. 해결방법을 아시는 분이 계시다면 도와주시면 감사하겠습니다 :(

+ 12/20: 일본 뱅드림 자체 버그로, 현재는 픽스가 되었습니다.

libhoudini

각설하고 본론으로 들어가서, libhoudini는 ChromeOS를 뜯어서 나온 x86 용 ARM 번역 라이브러리 입니다. 현재 Android-x86이 이 libhoudini를 이용해서 ARM 용 앱을 번역해 실행 중에 있으며, 유니티의 x86 안드로이드 지원이 끊긴 지금 대다수의 노트북 (x86_64)에서 게임을 돌리기 위해 사실상 반 필수가 된 라이브러리입니다. 그리고 번역속도가 꽤 빠른 것 같습니다.

아무튼, 원래는 Android 7버전까지만 arm64가 지원됐고 Android 9버전은 지원이 안되는 상태였는데 최근 ChromeOS 릴리즈 중에 arm64 지원 Android 9용 libhoudini가 포함돼 나온 릴리즈가 있었습니다. 원본 글

추출방법

추출된 파일을 밑에 업로드 해두었습니다.
이 문단은 그저 한 방법을 정리하기 위해서이니, 간단히 스킵하셔도 괜찮습니다.

추출은 다음과 같은 방법으로 하시면 됩니다.

  1. CrOS Updates Serving 에서 ChromeOS 이미지 파일을 가져옴
  2. 이미지를 마운트 후, vendor.raw.img 를 다시 한번 마운트
  3. 중요한 파일들을 가져와 squashfs로 묶기

우선 이미지 파일인데, 저는 chromeos_13310.93.0_drallion_recovery_stable-channel_mp-v2.bin 파일로 작업했습니다. 아마 기기와 버전 명을 맞추셔서 받으시면 될 것 같습니다.

이미지 마운트는 다음과 같이 하시면 됩니다.

$ sudo kpartx -av chromeos_13310.93.0_drallion_recovery_stable-channel_mp-v2.bin

add map loop(숫자)p1
...

$ sudo mount -t ext2 /dev/mapper/loop(숫자)p3 -o ro /(마운트 경로)/c
$ sudo mount /(마운트 경로)/c/opt/google/containers/android/vendor.raw.img /(마운트 경로)/img

가져오실 파일들은 다음과 같습니다. (z버전 기준)

  • lib64/libhoudini.so
  • lib64/arm64/
  • bin/houdini64

y버전을 추출하실 분들은 64를 다 빼면 됩니다. 저 안의 내용물을 다 한 폴더에 넣습니다.

그리고 $ mksquashfs /(내용물이 들은 폴더)/ houdini9_z.sfs 를 통해 squashfs를 만들어주시면 됩니다.

마지막으로, 아까 마운트한 역순으로 umount, umount, kpartx -dv 를 실행하셔서 마운트를 해제해주시면 됩니다.

참고한 글은 다음과 같습니다.
Android x86 Google Groups BlissRoms-x86/android_vendor_google_chromeos-x86

적용방법

houdini9.zip 다운로드

우선은 제가 추출한 houdini9.zip 을 받습니다. 사실 저는 64비트 버전을 사용하려면 무조건 houdini9_z.sfs 를 사용해야하는 줄 알았는데 아니었나 봅니다. 저도 어떻게 했는지는 잘 기억이 나지 않을 정도로 이리저리 잡다하게 시도했었는데 우선 한 일은 다음과 같습니다.

  1. build.prop 수정해서 64비트도 설치되게 변경
  2. enable_nativebridge 수정
  3. houdini9_y.sfs와 houdini9_z.sfs 추가
  4. enable_nativebridge 실행

우선 build.prop 은 다음과 같이 수정해주시면 됩니다.

- ro.product.cpu.abilist=x86_64,x86,armeabi-v7a,armeabi
+ ro.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi

- ro.product.cpu.abilist64=x86_64
+ ro.product.cpu.abilist64=x86_64,arm64-v8a
/system/build.prop
- ro.vendor.product.cpu.abilist=x86_64,x86,armeabi-v7a,armeabi
+ ro.vendor.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi

- ro.vendor.product.cpu.abilist64=x86_64
+ ro.vendor.product.cpu.abilist64=x86_64,arm64-v8a
/system/vendor/build.prop

enable_nativebridge 는 다음과 같이 수정해주시면 됩니다.

@@ -82,4 +82,6 @@ else
 	log -pi -thoudini "houdini$1 enabled"
 fi
 
+[ "$(getprop ro.zygote)" = "zygote64_32" -a -z "$1" ] && exec $0 64
+
 exit 0
/system/bin/enable_nativebridge

houdini9_y.sfshoudini9_z.sfs 는 위 링크에서 받아서 /system/etc 에 추가해주시면 됩니다. 제 개인적인 생각으로는 houdini9_y 만 있어도 작동할 수도 있겠다 싶은데 아무튼 houdini9_z 만 가지고는 안 됐던거 같아서 혹시 몰라 둘 다 넣었습니다. 9_y 만 있어도 가능하지 않을까 싶습니다.

마지막으로, ./enable_nativebridge 64./enable_nativebridge 를 실행시켜 주시고 재부팅해주시면 적용이 됐을 것 같습니다.

결론

이렇게 하시면 안드로이드9 x86 노트북에서도 64비트 게임을 돌릴 수 있게 됩니다. 이전 글에서도 설명했듯이, 렉이 별로 없이 상당히 부드럽게 돌아가긴 하지만 발열은 확실히 조금 심하긴합니다. 아무튼 패드가 없어도 이렇게 사용할 수 있는 게 장점인 것 같습니다.

후디니가 바뀌어도 뱅드림은 여전히 데이터 인계가 안되는 게 아쉽긴 한데, 로그캣을 열심히 뒤져보아도 원인을 찾기가 어려워 어쩔 수 없이 1년 지나 한도리에 해당 이벤트가 나오기를 기다려야할 듯 합니다.

읽어주셔서 감사합니다.

]]>
<![CDATA[노트북에서 안드로이드 게임 돌리기]]>계기

저는 뱅드림을 픽셀3a로 플레이하고 있는데, 매번 플레이할 때마다 노트가 씹히는 문제도 있고, 새로 나온 프로젝트 세카

]]>
https://blog.nenw.dev/bangdream-on-x86-laptop/67714eb25df352000114f607Thu, 10 Sep 2020 09:23:48 GMT계기노트북에서 안드로이드 게임 돌리기

저는 뱅드림을 픽셀3a로 플레이하고 있는데, 매번 플레이할 때마다 노트가 씹히는 문제도 있고, 새로 나온 프로젝트 세카이를 플레이 하고 싶어도 폰의 용량이 부족해 플레이하지 못하는 문제가 있어 아이패드를 살까 고민했었습니다.

하지만, 아이패드의 비싼 가격을 보고서는 터치가 되는 노트북에서 안드로이드 게임을 돌릴 수만 있다면 괜찮지 않을까? 생각을 하게 되었습니다.

결론부터 말씀드리자면 성공하였고, 굉장히 빠릿빠릿하게 돌아갑니다. 하지만 프로젝트 세카이를 돌리는 것에는 실패하였고, 그 이유와 해결방법에 대해서는 후술하겠습니다.

시행착오

주의: 다음 내용들은 제가 하다가 이것으로는 도저히 불가능하다고 생각했던 것들입니다.

빠르게 결론을 보시고 싶으신 분들은 다음 단락으로 넘어가주시기 바랍니다.

Anbox 사용

불가능하다고 생각했던 이유: 너무 불안정해 앱이 제대로 동작하지 않았습니다.

먼저 가장 처음 생각한 방법은 Anbox를 사용하는 것이었습니다. snap을 이용해서 설치하는 걸 권장하던데, 어차피 snap으로 설치하더라도 커널 리빌드는 해줘야하고 저는 개인적으로 snap을 좋아하지 않기 때문에 AUR을 통해 설치했습니다.

커널 리빌드

우선 저는 Arch Build System 에 따라 커널을 재빌드해야만 했습니다.

먼저, 다음 명령어를 통해 패키지를 가져왔고

$ cd ~
$ mkdir build && cd build
$ asp update linux
$ asp export linux

PKGBUILD 파일을 열어서 pkgbase=linux-anbox 라 수정한 후에 docs 생성하는 부분을 다 빼줬습니다.

그리고 나서, config 파일을 열어서 다음과 같이 써줬습니다.

#
# Android
# 
CONFIG_ASHMEM=y
CONFIG_ANDROID=y
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_ANDROID_BINDERFS=y
CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
# end of Android

그 후에 $ updpkgsums를 실행하시고, gpg key를 가져오셔야 합니다. 저는 다음과 같이 가져왔습니다.

$ gpg --keyserver hkp://keys.gnupg.net --recv ABAF11C65A2970B130ABE3C479BE3E430041188
$ gpg --keyserver hkp://keys.gnupg.net --recv 647F28654894E3BD457199BE38DBBDC86092693E
$ gpg --keyserver hkp://keys.openpgp.org --recv A2FF3A36AAA56654109064AB19802F8B0D70FC30

끝으로, 다음 명령으로 커널을 빌드, 설치 후에 Grub까지 업데이트하고 리부팅 했습니다.

$ MAKEFLAGS="-j$(nproc)" makepkg -s
$ sudo pacman -U linux-anbox-5.8.6.arch1-1-x86_64.pkg.tar.zst linux-anbox-headers-5.8.6.arch1-1-x86_64.pkg.tar.zst
$ sudo grub-mkconfig -o /boot/grub/grub.cfg

리부팅 후에 다음 명령어들이 제대로 성공하면 됩니다.

$ ls -1 /dev/ashmem
$ sudo mkdir /dev/binderfs
$ sudo mount -t binder binder /dev/binderfs

아치위키 Anbox 항목Kernel/Arch Build System 항목을 따라하시면 간단히 될 것이라 생각됩니다.

anbox 실행

저는 yay를 사용하기 때문에, $ yay -S anbox-git anbox-image-gapps 명령어로 anbox를 설치했습니다.

이대로 켜면 네트워크가 안되기 때문에 따로 브릿지를 생성해줘야 하는데, 저와 같은 경우는 NetworkManager를 사용하기 때문에 다음 명령어로 브릿지를 생성했습니다.

$ nmcli con add type bridge ifname anbox0 -- connection.id anbox-net ipv4.method shared ipv4.addresses 192.168.250.1/24

이후에 $ sudo systemctl restart anbox-container-manager.service 명령어로 서비스를 다시 켜고 anbox를 실행시키시면 됩니다.

하지만 슬프게도 설정앱부터 튕기는 등 불안한 증세를 많이 보였습니다. 따라서 저는 이 방법을 포기했습니다.

Android-x86 Live 사용

불가능하다고 생각했던 이유: 불가능까지는 아니었던 것 같지만  다른 디바이스에 데이터를 저장할 바에는 OS까지 같이 거기에 저장하는게 나을 것 같아서였습니다.

그 다음으로 생각해낸 방법은 Android-x86이었습니다. 하지만, 설치하기에는 많은 부담이 있었던지라 같은 파티션을 사용해서 해보려고 했습니다. 이를 위해 저는 공식 Android-x86 사이트로부터 android-x86-9.0-r2.x86_64.rpm 을 받았습니다.

이후에, $ rpm2cpio android-x86-9.0-r2.x86_64.rpm > android-x86-9.0-r2.x86_64.cpio명령을 실행해서 cpio 파일로 변환한 후에 내용물을 까보았습니다. 이거면 직접 해볼만하겠다 싶어서 해당 파일들을 전부 /Android-x86로 옮겼습니다.

다음과 같은 폴더구조가 되었습니다.

/Android-x86/
└ android-9.0-r2/
 ├ initrd.img
 ├ kernel
 ├ system.sfs
 └ ...

그리고 /etc/grub.d/40_custom 에 다음과 같은 엔트리를 추가해주었습니다.

menuentry "Android" {
	insmod part_gpt
	search --file --no-floppy --set=root /android-x86/android-9.0-r2/system.sfs
	linux /android-x86/android-9.0-r2/kernel quiet root=/dev/ram0 androidboot.hardware=remix_x86_64 androidboot.selinux=permissive SRC=/android-x86/android-9.0-r2
	initrd /android-x86/android-9.0-r2/initrd.img
}

하지만 grub-mkconfig -o /boot/grub/grub.cfg를 실행시키고 재부팅해보니 슬프게도 부팅애니메이션에서 넘어가지 않았습니다. 원인은 여러가지 있을 것 같은데, Grub 설정이 잘못됐을 수도 있었던 거 같습니다.

하지만 결정적으로 저렇게 설치하게 되면 메모리에 저장하도록 마운트가 되고, 저걸 다른 디바이스로 바꿀 바에는 차라리 그 디바이스에 직접 설치하자는 생각을 해서 다른 방법을 선택하게 되었습니다.

사실 다 끝내고나서 생각한 거긴 한데 어떻게 img파일로 마운트를 잘 하면 가능할 법도 했던 것 같긴 하네요.

방법

그래서 다음과 같은 시행착오를 거친 후에 마지막으로 택한 방식이 직접 Android-x86을 설치하는 것이었습니다.

우선 가장 처음으로 파티션 확보를 먼저 했습니다. 마침 집에 미리 구워둔 우분투 부팅 USB가 있었던지라 GParted로 제 루트 파티션을 32기가 정도 축소시켜서 안드로이드 용으로 ext4 파티션을 하나 만들었습니다.

그 후에 $ sudo dd if=./android-x86_64-9.0-r2.iso of=/dev/(YOUR-DEVICE-HERE) 명령어를 통해 다운받은 android-x86 이미지를 USB에 구웠습니다.

그리고 Android-x86 Installation 과 같이 생긴 항목으로 들어가서 아까 전에 만든 파티션을 눌러 설치했습니다.

Grub을 설치하겠냐는 다이얼로그도 나왔던 거 같은데, 이미 제 컴퓨터에 Grub이 설치돼있어서 설치하지 않았고, 대신에 다시 아치리눅스로 부팅해서 엔트리를 하나 추가시켜줬습니다.

menuentry "Android" {
	insmod part_gpt
	search --file --no-floppy --set=root /android-9.0-r2/kernel
	linux /android-9.0-r2/kernel quiet SRC=/android-9.0-r2
	initrd /android-9.0-r2/initrd.img
}

다시 grub-mkconfig를 돌리고 부팅했더니 성공하였습니다. 하지만, 그럼에도 여러가지 문제가 남아있었고 해당 문제를 해결한 방법들을 서술하도록 하겠습니다.

문제해결

네이티브 앱에서 "앱이 중단되었습니다."

우선, 뱅드림을 다운받고 켜보니 "앱이 중단되었습니다." 만 표시되었습니다. 로그캣을 열어보니 유니티에서 x86의 지원을 중단한 것이 문제였습니다. 당연히도 제 컴퓨터는 x86_64이기 때문에 ARM 타겟으로만 컴파일된 앱을 실행할 수 없었습니다.

다행히도, 이를 해결하기 위해 libhoudini라는 번역 라이브러리가 있었고 android-x86도 이를 열심히 지원하고 있었습니다. 설정에서 "Enable Nativebridge" 를 체크하면 되는데, 열심히에 취소선을 친 이유는 이를 지원하는 스크립트와 미러에 문제가 있어 제대로 작동하지 않았기 때문입니다.

실제로 저 옵션을 체크하면 /storage/emulated/0/arm에 제대로된 파일이 아닌 이상한 중국어가 적힌 html만 받아집니다. 그 html에 적힌 링크에서 받을 수도 있었지만, 너무 느려서 저는 이 곳에서 받았습니다. houdini9_y.sfs

저 파일이 마운트되어 작동을 하는 방식인데, 해당 파일을 받고 이를 /system/etc/houdini9_y.sfs에 위치시키시고 root:root로 chown 돌리신 후에 /system/bin/enable_nativebridge를 실행시키시고 재부팅하시면 됩니다.

그러나, 이렇게 할 경우에는 프로젝트 세카이는 돌아가지 않습니다. 그 이유는 y로 끝나는 후디니 버전에서는 arm64-v8a가 지원되지 않기 때문인데, 이를 위해서는 z로 끝나는 버전이 필요합니다. 그러나 어디에서도 이 파일을 구할 수 없었습니다. 검색을 해본 결과 크롬OS에서 추출한 파일인 듯 합니다. 참조 링크 1 참조 링크 2

+ 12/20: 안드로이드 9 버전에서도 이제 x86_64가 지원되게 되었습니다.
후속 글

android-x86 구글 그룹을 뒤져본 결과 해당 파일을 가지고 있는 크롬OS가 아직 배포된 게 없어서 못 구하고 있는 듯 합니다. 따라서, 안드로이드 7.0을 다시 설치하고 이 곳에서 houdini7_z.sfs를 구하고 build.prop을 수정해서 ro.product.cpu.abilistro.product.cpu.abilist64arm64-v8a를 추가하시면 됩니다.

하지만 저는 일단 뱅드림이 돌아가는 것만으로 감사하기 때문에 하지 않았습니다.

타 OS 루트 디렉토리 소유자가 1023:1023

주의

이 내용은 저와 같이 리눅스를 USB와 같은 "제거 가능한 미디어"에 설치하신 분만 해당됩니다.
이 방법을 사용하시면 문제는 해결되는 대신 USB와 같은 미디어 자동 마운트가 작동하지 않게 됩니다.

제가 안드로이드로 부팅후에 아치리눅스로 부팅했더니 X11이 동작하지 않았습니다. 일단 놀라서 부팅 옵션에 nomodeset을 주고 부팅 후에 tty에 접근해서 뭐가 문제인지 journalctl을 분석해본 결과 조금 충격적인 일이 벌어졌습니다.

문제는 제 아치리눅스가 메인OS임에도 외장 SSD에 깔려있었고, 안드로이드가 이를 제거 가능한 미디어로 인식해서 마운트 후에 루트 디렉토리의 소유자를 1023:1023 으로 바꾼 것이었습니다. 루트 디렉터리에서 $ chown root:root . 를 입력한 후에 다시 부팅했더니 정상적으로 부팅되었습니다.

하지만 이와 같은 일이 벌어지지 않게 하기 위해서는 vold의 설정을 조금 조정해서 장치가 연결돼도 자동으로 마운트하지 않게 만들어줘야 합니다. 다음 글을 참고해서 하였습니다.

우선 ramdisk.img를 수정해야 합니다. 첫번째로 android-x86을 설치한 드라이브를 마운트 한 후에 android-9.0-r2/ramdisk.img를 복사해옵니다. 그 후에 다음과 같은 명령어로 cpio + gz를 분해하면 됩니다.

$ mkdir ramdisk
$ cd ramdisk
$ zcat ../ramdisk.img | cpio -idmv

그리고 fstab.android_x86_64파일을 수정해서 다음과 같이 3번째 줄부터 모두 주석처리해주시면 됩니다.

none			/cache		tmpfs	nosuid,nodev,noatime	defaults

# /devices/*/block/sr*		auto	auto	defaults		voldmanaged=cdrom:auto
# /devices/*/usb*/*		auto	auto	defaults		voldmanaged=usb:auto,encryptable=userdata
# /devices/*/mmc0:a*/*		auto	auto	defaults		voldmanaged=sdcard1:auto,encryptable=userdata
# /devices/*/*sdmmc*/*		auto	auto	defaults		voldmanaged=sdcard1:auto,encryptable=userdata
#
# ...

그 후에 다음 명령어로 다시 ramdisk.img를 만들어주시면 됩니다.

$ find . ! -name . | LC_ALL=C sort | cpio -o -H newc -R root:root | gzip > ../ramdisk-modified.img

새로 생성된 ramdisk-modified.img 를 android-x86이 설치된 드라이브의 android-9.0-r2/ramdisk.img로 복사하시면 됩니다. 이 전에 있던 파일을 백업해두시는 것을 권장드립니다.

결론

  1. 뱅드림은 정말 잘 됩니다.
  2. 64비트 ARM만 지원하는 게임을 하실 예정이시라면 후속 글을 참고해주세요.

여담

확실히 화면이 크니까 슬라이드 뭉게기도 힘들고, 대륙횡단 슬라이드도 틀리네요. 그래도 일단 터치 씹힘은 확실히 줄어들어서 너무 기분이 좋습니다.

두번째 여담으로, 일도리는 아쉽게도 조금의 문제가 있었습니다. 앱을 닫고 열 때마다 사용자 정보가 계속 날라가는 문제인데, 저도 어떻게 해결하지 못해서 구글 플레이랑 연동 후 시작할 때마다 데이터 불러오기를 통해 플레이하고 있습니다.

]]>
<![CDATA[꿈을 꿰뚫는 순간에 메이플2 악보]]>Poppin' Party - 夢を撃ち抜く瞬間に!

Sheet Host에 있던 원본 악보를 조금 수정해서 그냥 그대로 MML로 옮겼습니다. 중간에 틀린 부분이

]]>
https://blog.nenw.dev/yume-uchinuku-shunkanni-maple2/67714eb25df352000114f606Fri, 19 Jun 2020 07:58:04 GMTPoppin' Party - 夢を撃ち抜く瞬間に!

Sheet Host에 있던 원본 악보를 조금 수정해서 그냥 그대로 MML로 옮겼습니다. 중간에 틀린 부분이 있을 수도 있습니다. 3000자 악보입니다. 이전에 올린 글과 똑같이 형식은 MML로 같아서 메이플2 외의 다른 게임에서도 연주가 가능할 것 같습니다.

악보

t203r2b+2bb+bag+ag+eb+2.e8d8t156c2.ed.d16e16dc2c-2.t203r>fel8rdrdrdl4derfel8rdrdrdl4der<aagab8b+.r2aagfe8e.r2aagab8>c.rc2c-2l8c<eeee4<ab>c2e4ga4.g4edc4d4edl4cde2rd8e8fe8d.<a>c2rl8ccd2r4<bag4r2ab>c2e4ga4.g4edc4d4edc4d4e2.def4fe4.d4c2.<ab>c2d4cc4.rv6>>dr<g>cdl4<gv8<ceg>c2.cc-c8<b8aga2gf8g.ceg>c2.cc-c8<b8aggc8e.ed2d8c8c-v6gl8dcgcdcgcd>cr2gcdgb+cdgb+dg>gr2v8<<d4cd4.e4fefg&g2g+4eg+4.a4barb&b2>c4cl4<b.gaa8g.ag1r>gg2c2b+2bb+bag2d2e<ede>c2a2gagfef8e8dcd<efac2b+2bb+bag+abeb+2.e8d8c2.ede8d8cc-c1&c1v6>a8e8g8aga8&a1r1
멜로디 (511자)
t203c1e1e2brg2.rt156r1r1r1t203r>dcl8r<brbrbl4b>crdcl8r<brbrbb4b+4l1.rrrrr1r8c8c8c8c4rrrrr1l4r<dr2.a2r.b+.l2.rb4r>cl4rdr8c.<ba2.ra2gg8g.r1.r>a2.agr8g8fef2ed8e.r2.a2.afr8f8frer8c.c<b2r2l8ccccl4crc-c8c.rl8ccccl4crc-c8c.r1rl8>dcrd2&de4r2.g+rg+2.g4rl4g.rff8e.f+d1r>dd2<c2b+2bb+bal2gde4r.cal4gagfef8e8dcdr2.g2>g2gagfef+g+ea2.r1r1rv6<el8gar2aggl4a.r>>e8rere8&e1r1
화음A (356자)
t203<g1a1b2>ere2.rt156l1<ag&gt203c&cc&cl4<f>f<g>g<a>a<a8a8>a<f>f<g>g<a>g<a8a8>a<f>f<g>g<a>a<a8a8>a<f>f<g>gl8rcccc4r4<<a>a<a>a<a>a<a>af>f<f>f<f>f<f>f<g>g<g>gn32g+n32g+<a>a<a>a<a>a<a>a<d>d<d>d<d>d<d>d<<a>a<a>a<a>a<a>a<g>g<g>g<g>g<g>gg4r4gggg<a>afa<a>aeaf>fcf<f>fcfgrg4g+rg+r<a>aea<a>aea<d>d<a>d<d>d<a>d<<a>afa<a>afa<ff>f4<ggl4>g>cc8cc8cc8cc8<bba.a8>a<ae.e8bef.f8>f<f>c.c8>cc<<a.a8>a<af.f8b+f>c.c8gc<g.g8>g<gl8ccccl4cr<bb8b.rl8>ccccl4crc-c8c.rl1ggg+g+l2agff+g1l4rgggcb+c8c8b+f>f<f8f8>f<g>g<g8g8>g<cb+c8c8b+<a>a<a8a8>af>f<f8f8>f<cb+c8c8b+<g>g<g8g8>g>ccccffffeeee<aa8>a8&a2l1<fgf&fv6>>c4r8c4.r8c8&cr
화음 B (594자)
t203<e1f1g+2brg2.rt156l1fd&dt203<c&cc&cl4r>crdrerercrdrerercrdrerercrdl8r<cccc4l1.rrrrr1l4rdrl8ddddr1r1gg>d4<g+g+>en32l1rrrf2r2l4<gr8gr8gr8gr8gg<a.r8>er<e.r8n40rf.r8>b+rc.r8>gg>e2.edr8d8drcr1r1r1.rl8ccccl4crc-c8c.rl1<<ddeel2edcdd1l4rdddrgrgr>crcrdrdr<grgrerer>crcr<grgrdrdgggg>cccc<bbbber8>e8&e2l1<cd<f&frrr
화음 C (307자)
t203l1rrrrt156rrrt203l1.rrrrrrrr2r8<g8g8g8g4rrrrr1r4<<g4r4g8g8g8g8rrrrl4r>crd<aa8aa8aa8aa8ggo4e2.eer2.c2cr8c.r2.o1a.r8n40rf.r8>>fr<c.r8>er<<g.r8n38l1rrrrr4ggg+g+l2agff+g1r4o4gg4l1.rrrrrr2l4<<ccccffffeeee<aa8>>c8&c2<<f1g1v6o4ar2.e8re.r1r1r1r
화음 D (240자)
t203l1rrrrt156rrrt203l1.rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrl4rc+r1ro1gggl1.rrrrrl2ro5cb+l4bb+bag+abr>c2.l1.rrrrr1r4
화음 E (111자)

전체 총 2111자 / 3000자

]]>
<![CDATA[Initial 메이플2 악보]]>Poppin' Party - Initial

똑같이 MML 형식이라서 메이플2 말고도 마비노기 같은 데에서도 연주할 수 있을 것 같습니다. 원본 악보를 몇 군데

]]>
https://blog.nenw.dev/initial-maplestory2-mml/67714eb25df352000114f605Thu, 18 Jun 2020 15:18:24 GMTPoppin' Party - Initial

똑같이 MML 형식이라서 메이플2 말고도 마비노기 같은 데에서도 연주할 수 있을 것 같습니다. 원본 악보를 몇 군데 빼고서는 그냥 그대로 MML로 옮겼습니다. 사실 3000자보다 훨씬 적기에 마음만 같아서는 화음을 하나 더 넣고 싶은데 너무 귀찮아서 그냥 이대로 놔두려고 합니다. 중간에 잘못 옮긴게 있을 수도 있습니다.

악보

t192ra.b.>c2l8cc-c<b4ba4g.a.b4bag2l4.rgab2l8ed+eb4ba4>d.d.dc+2r2<<ff4.gg4a4aga2f4ff4ggba4>g32a16.ger>ea>ee2r<<ea>c4c<ba4ab>c4ccc<bgba&a1reeeeeee4edcd4c4rcccc-c<ba4>g32a16.ged+dcr4eeeeege4edcd4c4rcccdc<ba4r2>eg>er4ga4ga4ga4ga4g4ragedcge4<g32a16.ged+dcr4.>ga4gab+4c<aga4r4.>edcdc<ba&a1r4a4.b4.>c2cc-c<b4ba4g.a.b4bag2l4.rgab2l8ed+eb4ba4b.a.bb+1r4a4.g+4.b2eg+abl8.>ccc-8c2ccc-8c2r8<aaaaa8ab>c8c<bl8ab+b4b+b4b+b4b+b4b4bb4eg+b>e4fe4.<e4g+4b>d4dcc-c4.d4dcc-c4.<b4bbabbbb4bab>cl32c<bagfedcl4r.>ddc8dd8ec.<bb8a8b+b8>c8c+l16<ea>c+ea>c+l4e.<dl8dcc-c4.d4dcc-c4.d4ddef.e.c-d4cc4<ga>e4<ga>e4.ed4.<b4g4eg4aa2r1ga>e4<ga>e4.el4d.<bgel8ga.r16ga.r16ga4.
멜로디
t192r<f.f.f2.r8gg8>dl8<gdgg4g>d4<gdgfere4.e4.e2.ra4ab+4a.a.aa1<f4b+4g4ba4a>e4d+dc<f4fb+4g4ba4ea4>b+bl4a<fb+gb8aa8>el8d+dc<f4fb+4g4ba4aaaararf4b+4g4ba4a>e4d+dc<f4fb+4g4ba4a>e4<aeaf4fb+4g4ba4ae4d+db+f4fb+4g4ba4ea>cl4e<afb+gb8aa8>el8d+dc<f4fb+4g4ba4a>e4<beaf4fb+4g4ba4a>e4d+dc<f4fb+4g4ba4ea>cec<aearf4.f4.f2.rg4gb4gdgg4g>d4<gdgfere4.e4.e2.ra4a>e4<a.b+.a4a>e4<aea>cer<bf+4bl4f+>e2er<a>e<g>d<fb+ebl2>dcc-d+l8<ee4ee4ee4ee4e4ee4b>eg+bg+e<b&b2.r>f4fa4fcfg4gb4gdg<a4a>e4<aeaa4a>e4<aea>f4fa4fcfe4ea4ec-e<a4ab+a4eaa4ea>c+ec+<a>f4fa4<f>cfg4gb4gdg<g+4g+>d4<g+.g+.g+a4b+4afa>d4da4d<a>de4.g4b4ec-efcccc<bbba2>aead4da4d<a>de4eg4e4e4<ga.r16ga.r16ga4.
화음 1
t192rf.g.a2r.gg8rd8rgg8r1e.e.g2r.ee8ra8.a8.a8a2l32b+bagfedcl4ro1f>f<g>g8<ar8>b+r.<fr8>a<g>g8<ar2.r8f>a<g>g8<ar8>b+r.<fr8>g<g>g8<al8aaaaaral4f>a<g>g8<ar8>b+r.<fr8>a8r8<g>g8<ar8>b+<a8rfr8>a<g>g8<ar8b+r.fr8>a<g>g8<ar2r8af>a<g>g8<ar8>b+r.<fr8>a<g>g8<ar8>b+<a8rfr8o4g8ag8a8b+r1a8<<a<g>g8<ar1r8o4f.g.a2r.gg8<<g<g8rgr8>b<g8r2r8o4e.e.g2r.ee8<c>e8.e8.e8a1rd.d.g+2n44rl8.aar8a2aar8a2r8fffff8fr4r16f+l4rr16g+8g+g+8g+g+8g+g+8g+g+g+8g+r.>>ef8e.r2r8<<aa8c<<f8r>>bb8d<<g8r>>el8eereeee4ereer2rl4aar8g+g+8ee.ee8<e8>erer2.aa8c<f8r>bb8d<<g8r>>g+l8g+g+ro1g+.g+.g+o4a4al4arar8<f<d8r>>b.<b<e8r>>el8rccccddd<a2<ao5ga>e4ro3f4<dro6el4d.<bgel8ga.r16ga.r16ga4.
화음 2
]]>
<![CDATA[리눅스 키보드 설정]]>저는 현재 그램에서 아치리눅스 + GNOME3 (On X11)를 사용 중이고, IME로는 fcitx를 사용 중입니다. CapsLock이 두 글자씩 적용되는 버그와 한영전환

]]>
https://blog.nenw.dev/linux-xkb-settings/67714eb25df352000114f602Sun, 31 May 2020 09:42:50 GMT저는 현재 그램에서 아치리눅스 + GNOME3 (On X11)를 사용 중이고, IME로는 fcitx를 사용 중입니다. CapsLock이 두 글자씩 적용되는 버그와 한영전환이 제대로 되지 않는 버그를 해결한 방법을 서술하고자 합니다.

CapsLock 버그 수정

CapsLock이 빠르게 전환 시에 두 글자씩 쳐지는 버그는 xkb_symbols 를 수정해서 해결했는데, 원래는 따로 파일을 만들고 하려고 했으나, 귀찮아서 그냥 /usr/share/X11/xkb/symbols/kr 파일을 수정했습니다.

xkb_symbols "kr106" 안에 다음과 같은 내용을 추가해뒀습니다.

replace key <CAPS> {
    repeat=no,
    type[group1]="ALPHABETIC",
    symbols[group1]=[ Caps_Lock, Caps_Lock ],
    actions[group1]=[ LockMods(modifiers=Lock), Private(type=3,data[0]=1,data[1]=3,data[2]=3) ]
};

한영키 / 한자키 수정

한영키와 한자키도 비슷한 방법으로 수정했습니다. 사실 그램이라서 101/104키 호환으로 했었어야 했는데 (이미 한자키 / 한글키 수정이 적용돼있음) 그걸 모르고 바보같이 kr106 을 수정했습니다.

replace key <RCTL> { [ Hangul_Hanja ] };
replace key <RALT> {
    type[group1]="ONE_LEVEL",
    symbols[group1]=[ Hangul ]
};

그리고 setxkbmap -layout "kr(kr106)" 으로 레이아웃을 설정하고, fcitx 설정과 그놈 설정에서 기본 키맵을 한국어로 바꿔두었습니다.

그 후에 fcitx의 전역 설정에서 입력기 전환을 Hangul 로 하시면 해결됩니다.

그럼에도...

그런데 이렇게 설정하고도 xev 명령어로 테스트 해보니 여전히 한글키가 ISO_Level3_Shift 로 잡혔습니다. 그래서 xkbcomp -xkb $DISPLAY "my-file-name" 을 한 후에 xmodmap -e 'keycode 108 = Hangul' 을 실행하고 (임시로 RALT를 한글키로 잡히게 함) 또 xkbcomp 를 실행해서 둘을 비교해보았습니다.

제가 아무리 <RALT>type[group1]Hangul로 바꿔도 계속 ISO_Level3_Shift 로 잡히는 것을 확인하고, 또한 xkb_symbolspc+kr+kr(kr106):2+inet(evdev)+level3(ralt_switch) 로 잡히는 것도 확인했습니다. 이 후 구글링 한 결과 한 스택오버플로우 글에서 해결책을 찾았습니다.

TL;DR) dconf-editor 를 실행해서 xkb-options 를 검색한 후에 lv3:alt_switch 를 제거해주시면 해결됩니다.


드디어 한글이 제대로 나오네요...

]]>
<![CDATA[2020년 일본 여행 (1)]]>https://blog.nenw.dev/2020-01-japan-travel-1/67714eb25df352000114f600Sat, 11 Apr 2020 08:36:06 GMT종강을 맞이해 1월 달 즈음에 일본 여행을 다녀왔습니다. 혹시라도 또 일본에 갈 일이 생기면 그 때는 조금 더 편안하게 계획할 수 있도록 있었던 일들을 써놓고자 합니다.

기간 2020년 01월 08일 ~ 2020년 01월 16일
지역 오사카, 교토, 타카야마, 유가와라, 도쿄
인원 2인 (본인 포함)
예산 42만원 (경비), 32만원 (JR패스), 13만원 (비행기), 34만원 (숙소)

친구랑 어쩌다보니 일본여행을 가기로 생각하였고 중간고사 이후부터 계속 일정에 대해 생각하다가 결정적으로 친구의 오사카여행이 제주항공에 의해 캔슬되어서 그 때부터 제대로 계획하기 시작했습니다. 당장 계획하면서부터 진짜 갈 수 있을까 걱정됐었지만 일단 비행기와 숙소를 예약하고 나니까 그 후로는 점점 현실화됐던 것 같습니다.

전체적으로 뭔가 어딜 가기보다는 길거리나 골목같은 데를 많이 구경했던 것 같고, 쿄애니 관련된 곳도 많이 돌아다녔던 것 같습니다.

1월 8일

우선 그 전날, 비행기가 아침 일찍이었던 관계로 여행 가기 전날에 먼저 인천으로 올라가서 캡슐호텔에서 잤습니다. 작년에 갔을 때랑 똑같이 굿스테이인을 이용했었는데, 아침 3시 40분에 일어나서 공항으로 갔습니다. 그 때에 공항에서는 문을 연 식당이나 이런 것이 하나도 없어서 대충 CU편의점에서 아침을 떼웠네요. 비행기 타기 전에 캐리어 규격을 분명 집에서 잴 때는 딱 맞춰서 가져왔는데 체크인하기 전에 시험삼아 넣어보니 정말 아슬아슬하게 안 들어갔었습니다. 셀프 체크인해서 캐리어를 검사하는 일은 없었지만 그래도 혹시라도 돈을 더 물게 될까봐 조마조마 했었는데, 다행히도 그런 일은 없었습니다.

칸사이 국제 공항에 내렸고, 칸사이 JR역에서 미리 예매해둔 JR패스를 바꾸고 하루카를 타고 덴노지 - 이마미야 - 난바로 난바역까지 왔습니다. 코인락커에 짐을 넣은 후에 홋쿄쿠세이에 가서 점심을 먹었는데, 제 생각에는 굉장히 괜찮았던 것 같네요.

1000엔 정도였는데, 약간 전통 일본 가옥? 같은 분위기였습니다. 첫 끼를 되게 맛있게 먹어서 기분이 되게 좋았던 걸로 기억합니다.

애플스토어에 들려서 $999 짜리 모니터 스탠드도 구경해보고 신사이바시 - 모리노미야로 오사카성에 갔습니다. 천수각도 올라가봤는데 내부는 현대적으로 개조돼있었습니다. 이 시국에 도요토미 히데요시 유물 같은 걸 보니 기분이 조금 (안 좋은 쪽으로) 묘했고, 올라가는 게 600엔이었나 그랬는데 전망대 말고 별로 볼 건 없었던 것 같습니다.

그 후엔 오사카조코엔 - 이마미야 - 난바에서 짐을 찾고 다시 덴노지 - 신오사카로 가서 신칸센을타고 교토로 갔습니다. 교토시야쿠쇼마에에서 내려서 숙소 (이즈츠 호텔)에 체크인을 하고 주위를 돌아다녔는데, 호텔 로케이션이 생각보다 너무 좋아서 어디 가기는 편했던 것 같습니다.

저녁은 톤친칸에서 오코노미야키(부타타마)로 저녁을 먹었고, 애니메이트나 멜론북스, 타워레코드 같은 데를 돌아다녔습니다.

정리

음료수 + 코인락커 1000엔
IC카드 충전 1000엔
점심 1000엔
천수각 600엔
호텔세금 800엔
저녁 1100엔
편의점 230엔

1월 9일

우선 이 날은 아침으로 규동을 근처 식당에서 먹었고, 데마치야나기에 갔습니다. 징검다리를 지나서 데마치야나기 상점가에 갔는데, 뭔가 상점가 딱 들어서자마자 아 여기가 그 상점가구나 싶었네요. 데마치야나기 역에 간 김에 그 두단식 승강장도 보려고 했었는데 어딨는지 몰라서 못 봤습니다. 나무위키에 따르면 1번 승강장이라고 하네요.

뭐 별로 본 건 없었고 상점가 한 바퀴 돌고 앞의 떡집 후타바에서 마메다이후쿠 사서 먹었습니다. 줄은 좀 길었는데 (몇 분 정도 기다렸었나...?) 확실히 맛있긴 했던 것 같습니다. 하나에 200엔이었는데, 이거 말고도 되게 맛있어 보이는 떡들이 많았습니다.

그리고 다시 숙소로 돌아왔다가 산죠 - 니죠 - 사가아라시야마로 가서 토게츠 다리랑 사가노 치쿠린을 봤습니다.

우선, 저 대나무숲 사진을 찍은 타이밍이 굉장히 좋았습니다. 원래는 중국인이랑 서양인 관광객들이 정말 많은데 (네, 유명한 관광지답게 정말 많았습니다.) 이리저리 돌아다니다가 잠깐 사람들이 없길래 운 좋게 사람이 별로 안 나온 사진을 찍을 수 있었습니다. 그 외에도 이리저리 돌아다니다가 이상한 언덕같은 데도 올라가보고 좀 주변을 열심히 걸었습니다.

저녁은 근처에 우나기 히로카와에서 먹었습니다. 여기는 가기 전에 미리 온라인으로 예약해야 하는 곳이었는데, 친구 말로는 미슐랭 스타 받은 데라네요. 확실히 맛있긴 했었던 것 같습니다.

대신에 가격은 확실히 좀 비싸긴 했던 것 같네요. 3900엔이었는데, 친구는 좀 더 비싼 거 먹어서 조금 얻어먹었습니다.

거기서는 란덴타고 란덴텐진가와 까지 왔습니다. 뭔가 내릴 때 누르라고 돼있는 버튼이 있었는데 다들 안 누르길래 그냥 안 누르고 내렸습니다. 호텔에 와서 잠깐 쉬다가 라운드원 가서 츄니즘을 좀 했는데, 리겜 있는 층에 안 보이길래 라운드원 트위터 찾아보니까 4층에 있대서 거기서 했습니다. 2층은 무슨 빠칭코 느낌이던데...

정리

아침 규동 570엔
마메다이후쿠 2개 400엔
IC카드 충전 1000엔
저녁 3900엔
리듬게임 200엔

1월 10일

친구가 금요일의 아침인사를 틀어서 깼습니다. (...)
그 유명하다는 이치란 라멘에 가서 먹어봤는데 뭐 그저 그랬던 거 같습니다. 그냥 먹을만 했던 것 같네요.

아침을 먹고서는 쿄애니 본사랑 쿄애니샵에 가보려고 산죠 - 토호쿠지 - 코하타로 전철을 탔습니다.

여기가 그 쿄애니 본사였는데 그 사고가 나고 얼마 지나지 않아서라 마음이 굉장히 아팠습니다. 앞에 종이로 뭔가 붙어있었는데 그 내용은 대충 쿄애니샵도 2020년 3월까지는 운영하지 않는다는 내용이었습니다.

다시 역으로 들어가서 좀 기다리다가 JR우지역까지 갔는데, 나오니까 비가 내리길래 츠치리라고 근처 찻집에서 시간을 떼웠습니다.

우지말차 그린티 플로트

이거 맛있었습니다. 특히 당도가 적당해서 맛있었던 거 같네요.

조금 걷다가 우지다리를 건너 우지 신사로 가서 조금 등산해 전망대까지 갔습니다. 올라갈 때 한 이십분 정도는 언덕을 올라간다고 생각해야할 것 같습니다.

구두신고 트럼펫 들고서는 힘들어서 도저히 못 오를 것 같아 보이네요

힘들긴 좀 힘들었는데 교토 시내가 한 눈에 보여서 좋았던 것 같습니다. 사실 조금 더 둘러볼까 했는데 내려오니까 저나 친구나 다리가 아파서 케이한 우지역 - 쥬소지마 - 산죠역으로 다시 돌아왔습니다.

케이한 타고 왔는데 케이한 우지역이 이뻤던 것 같습니다. 그리고서는 회전초밥집 스시노무사시였나 거기서 저녁을 먹었는데 스시 퀄리티는 좀... 아니었습니다. 친구 말로는 상한 건 줄 알았다고 하네요.

이후에 다시 라운드원 들렸다가 타워레코드에서 요루시카 앨범을 샀습니다. 이 때는 스포티파이를 안 쓸 시점이었는데, 나중에 보니까 스포티파이에 앨범이 다 올라와있었습니다. 다른 앨범을 살 걸 그랬나 싶었네요.... 그래도 요루시카 노래가 좋아서 별로 후회는 안 했습니다.

돌아오니까 숙소에서 BS11 나오길래 그걸로 사랑하는 소행성 2화랑 어과초 T를 봤습니다.

정리

아침 라멘 940엔
우지말차그린티플로트 550엔
IC카드 충전 1000엔
저녁스시 1030엔
리듬게임 500엔
요루시카 앨범 3000엔

1월 11일

이 날은 좀 늦게 깨서 아점으로 카이리키야에서 라멘을 먹었습니다. 실수로 양념장(진짜 가타카나로 ヤンニョムジャン인가 그렇게 써있었습니다.) 통을 엎어서 휴지로 닦긴 했는데 많이 죄송했습니다.

그 후에 토요사토에 가려고 교토시야쿠쇼마에 - 야마시나 - 히코네로 가서 거기서 오미 철도를 타고 갔습니다. 거의 시골열차 느낌이라 배차간격이 1시간이었는데, 원맨열차였습니다. 내릴 때 무조건 맨 앞칸에서 내려야 하며 티켓을 보여주고 내려야 했습니다. 한국에는 없는 형태라서 신기하다고 느꼈습니다.

내려서 역을 나오자마자 튀어나오는 소년 소녀 표지판이 있었습니다. 아쉽게도 그 유명한 토스트 물고 달리는 유이 표지판이랑 리츠 표지판은 부식으로 철거했다고 하네요. 미오도 머리 부분이 살짝 뜯어진 게 곧 철거되지 싶습니다.

토요사토 초등학교 구교사 안으로도 들어가봤습니다. 1층에는 이 건물의 역사 같은걸 전시해둔 게 있었고 도서관도 있었는데, 도서관은 들어가보려 하였으나 강당쪽으로 들어가야하는 걸 모르고 문 잠겨있길래 그냥 포기했습니다.

위로 쭉 올라가면 경음악부실이 있었습니다. 뭐랄까 봤었던 그대로라 되게 신기했었습니다. 소품도 다 비치해둔 거 같던데... 피아노에는 후와후와 타임 악보도 있었습니다. 그리고 한 구석에는 쿄애니를 추모하는 노트가 있었고, 칠판에도 쿄애니 간바레라던가 그런 내용이 적혀있었습니다.

강당은 일요일 날 미오 생일 준비하느라 뭐 있었던 거 같은데 하루 일찍 온게 잘한 건지 못 한 건진 모르겠네요. 구 도서관은 카페처럼 운영되고 있었는데 아저씨 몇 분께서 모여 계셨습니다. 구석으로 가니까 '이별 그리고 뜻을 잇는 식' 에 대한 공지도 있었고 (이미 2달 전이지만) 케이온 거의 모든 만화나 피규어 같은 걸 모아둔 곳도 있었습니다. 마지막으로, 기타도 여기서 전시하고 있었습니다.

토요사토는 굉장히 시골느낌이 나는 동네였던 것 같네요. 개인적으로 이런 분위기 나쁘지 않았습니다.

오미 전철은 배차간격이 극악이라서 놓치지 않게 조심해야 합니다. 히코네에서 탈 때는 그 앞에 승차권 자동발매기가 있어서 티켓을 사서 갔지만, 토요사토에서 갈 때는 타면서 승차위치 티켓을 뽑고 내리면서 돈을 같이 내는 형식이었습니다. 히코네역은 게이트가 있어서 내려서 게이트를 통과하면서 돈을 냈고, 미리 500엔 준비해놔서 40엔 거슬러받긴 했는데 거스름돈을 바구니? 같은데서 꺼내서 주셔서 그냥 460엔 준비하는게 마음 편할 것 같습니다.

히카리 신칸센 타고 내려와서 교토역 근처에서 먹을 데를 찾다가 좀 다들 비싸고 그래서 야요이였나 거기서 카츠동을 먹었습니다.

하루 종일 아침만 먹고 아무것도 못 먹어서 배가 고팠던지라 굉장히 맛있게 먹었습니다. 토요사토역 앞에 빵집이 있었는데 돌아오면서 거기서 뭐라도 먹었었으면 싶었습니다.

정리

아침 라멘 825엔
기차표 460엔 * 2
칼피스 140엔
저녁 가츠동 690엔

(2)에서 이어집니다.

]]>
<![CDATA[Object.prototype의 주의점]]>만약 어떤 봇을 만든다고 생각을 해보자. users[userName]이 있으면 등록된 유저라고 판단하는 로직이 들어있으면 겉으로 봤을 때에는 아

]]>
https://blog.nenw.dev/object-prototype/67714eb25df352000114f5ffSun, 25 Aug 2019 11:44:47 GMT만약 어떤 봇을 만든다고 생각을 해보자. users[userName]이 있으면 등록된 유저라고 판단하는 로직이 들어있으면 겉으로 봤을 때에는 아무런 문제가 없어보인다.

하지만, 만약 클라이언트 측에서 userName으로 __proto__ 라는 값을 줬다고 한다면? 굳이 __proto__ 가 아니라 Object.prototype 에 등록된 아무런 키라도 좋다. 해당 값은 이미 정의가 돼있기 때문에 falsy로 취급되지 않으며, 따라서 없는 유저임에도 있는 유저로 취급될 수 있다. 심지어 __proto__ 라는 이름을 썼다면, 프로토타입을 오염시킬 수도 있을 듯 하다.

지금까지 저런 형태의 코드를 굉장히 많이 사용한 듯 해 많이 걱정되었다. (다행히 내가 만든 서비스를 많이 문닫았기 때문에 영향이 클 듯 하진 않다.) 다음부터라도 저런 부분을 신경써서 짜야겠다.

해결방안은 굉장히 많은데, hasOwnProperty를 사용하는 방법도 있고 users[userName] 예시에서는 저게 진짜 유저타입인지 체크해보는 방법도 있다. 그 외에도 ES6 에 추가된 Map 을 사용하는 것도 좋은 방안이 될 수 있을 듯 하다.

아무튼 별 의식을 안하고 많이 쓴 패턴이라 굉장히 충격을 많이 받았다.

]]>
<![CDATA[한국에서 Jellyfin 사용하기]]>https://blog.nenw.dev/using-jellyfin-in-korea/67714eb25df352000114f5feFri, 26 Jul 2019 06:29:29 GMT

친구들끼리 NAS를 새로 구축하고서는 미디어서버로 뭘 쓸까 고민하다가 Open Source 인 Jellyfin을 사용하기로 했다. Jellyfin은 원래 오픈소스였던 Emby의 포크인데, 구글 검색해보니까 Emby에 대해 좋은 평이 나오는 걸 못 보긴 했다. Emby가 까이는 주된 단점은 아래와 같았다.

  1. 자막이 지원되지 않는다. -> 이 글에서 해결할 것이다.
  2. 라이브러리 업데이트가 느리다. -> 이상하게도 파일시스템 감시가 동작하기는 하는데 잘 안되는 것 같긴 하다. 나와 같은 경우 라이브러리 업데이트 간격을 스케쥴러에서 조정해주었다. 수동업데이트가 있으니 이건 그리 치명적인 단점이 아니라 생각한다.

문제는 1번인데, 한글자막을 해결하기 위한 삽질한 내용을 여기에 적어볼까 한다.

발단

원래는 그냥 자막이 안돼서 PLEX나 다른 서버로 갈아탈까 생각을 했다. 하지만, Jellyfin 로그를 봤더니 내부적으로 smi를 브라우저에서 나오는 형식(WebVTT)로 변환하는 과정에서 srt로 변환을 하는데, 그 과정에 오류가 나는 것을 보았다. 근데 뭔가 고칠 수 있을 듯 하여서 손을 대봤다. 오류 로그에서 문제가 된다고 여겨지는 부분만 뽑아보면 다음과 같았다.

Jul 14 20:21:53 ubuntu jellyfin[29088]: [sami @ 0xaaaad79e65f0] UTF16 is automatically converted to UTF8, do not specify a character encoding

Jul 14 20:21:53 ubuntu jellyfin[29088]: [sami @ 0xaaaad7a0f1f0] Unable to recode subtitle event

다음 오류는 오류같아 보이긴 했는데, 무시해도 괜찮은 내용이었다.

Jul 14 20:21:53 ubuntu jellyfin[29088]: Error while decoding stream #0:0: Invalid argument

원인

원인은 FFmpeg에서 잘못된 Charset을 공급받고 있는 것이었다. 에러 로그에 보니까 UTF-16에 대해서는 -sub_charenc 플래그로 자막 Charset을 공급하지 말라고 떠있길래 직접 ffmpeg로 자막 변환을 해보았다. charset 입력을 안하니까 진짜 제대로 변환되더라.

좀 더 자세한 원인으로 들어가보자면, ffmpeg는 ff_text_r8 을 통해 자체적으로 UTF-16 텍스트를 UTF-8로 읽는다. 이 명령어는 ff_text_read 에서 호출이 되고, sami 자막을 디코딩하는 samidec.c 에서 ff_text_read 를 호출한다. 즉, 이미 자체적으로 UTF-16을 번역하는데 또 번역하려고 시도돼서 생기는 오류였다.

해결

직접 문제가 되는 부분을 픽스해서 빌드해보니까 잘 작동이 되었다. 현재 jellyfin#1540 풀 리퀘스트로 해당 수정을 제안해둔 상태인데, 잘 풀린다면 smi 자막을 볼 수 있을 것이라 생각한다.

끝나지 않은 문제

여기까진 참 좋은 이야기였지만, 문제는 또 한 번 더 발생했다. EUC-KR 에서 사용 불가능한 문자가 들어있는 CP949 로 인코딩 된 자막이 들어가면 또 인식이 안되더라. 사실 완성형 2,350 자 정도만 인코딩하면 CP949EUC-KR 와 호환되기에 별 문제가 없다. 문제는 그 범위를 벗어난 문자인데, 해당 문자가 들어있으면 Jellyfin에서 사용하는 CharsetDetector/UTF-Unknown 이  EUC-KR이 아니라고 판단을 하고, charset을 제대로 인식을 못한다. 즉, UTF-Unknown 은 CP949를 인식하지 못한다.

해결2

Puzzlet Chung 님이 파이썬 용 charset detector인 charde 에 커밋하신 내용을 그대로 UTF-Unknown으로 가져왔다. (해당 커밋링크)

현재 UTF-Unknown을 픽스한 내용은 HelloWorld017/UTF-Unknown/tree/cp949 올라와있다.

사실 얘도 Pull Request를 넣어볼 생각이었는데, 윈도우에서 CP949의 인코딩 명이 ks_c_5601-1987 로 괴상망측한 이름을 사용하고 있었다. 그래서 test 를 패스하기 위해 소스에 호환성에 문제될법한 수정을 꽤 가했기 때문에 PR을 넣지 않았다.

jellyfin/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj 파일에 보면, NuGet에서 <PackageReference> 로 UTF.Unknown 을 가져오는 부분이 있다. 나는 이를 삭제하고 새로 <ProjectReference> 를 추가한 후에, 직접 git submodule 로 UTF.Unknown 을 집어넣어뒀다.

이제 이걸로 잘 해결되었으면 한다.
이 문제를 해결하면서 처음으로 docker를 이용해서 빌드해봤는데, 디펜던시로 골치를 앓을 일이 없어서 참 편안했던 것 같다. 도커 짱짱

(19.08.01 수정)
문제가 하나 생겼다. 자막 중에 '춉' (CP949 0xAD68) 이라는 글자가 들어가니 제대로 파악하지 못했다. 그래서 디버깅을 한 결과 총 두가지 문제가 있었다.

1. Puzzlet Chung님의 코드를 옮기는 과정에서 실수를 한번 범함
2. 원본 코드에서도 제대로 인식되지 않는 걸로 보았을 때, 거기에도 문제가 있어 보인다. 문제를 분석해본 결과, 0xAD을 Class 8로 해두셨는데 Start State (ASCII) 이후에 Class 8 이 나올 경우 Error State 로 가게 만들어두신 듯 했다.

그래서 결국에 State 테이블을 다시 작성해서 현재 HelloWorld017/UTF-Unknown cp949 브랜치에 다시 force-push 해둔 상태이다.

(19.08.14 수정)
정말 놀랍게도, UTF-Unknown에 보낸 Pull Request 가 받아들여졌다!! 이제 jellyfin 측에서 해당 디펜던시를 업데이트한다면 순정 jellyfin에서도 smi 자막이 잘 보일 듯 하다.

결론

오픈소스 좋아요 jellyfin 좋아요
EUC-KR/CP949 싫어요 smi 싫어요

]]>