├── .gitignore ├── .dockerignore ├── assets └── moonsnap.png ├── .env.sample ├── node_configs ├── reth │ ├── 1.0.1 │ │ └── docker-compose-full.yml │ └── 1.0.3 │ │ └── docker-compose-archive.yml ├── lighthouse │ └── v5.2.1 │ │ └── docker-compose-full.yml └── erigon │ └── v3 │ ├── 12c2732ad92733a6a6aae9db7259062182799674 │ └── docker-compose-full.yml │ └── 7931ce92ccbe1be5c16c183962cdbaa916aaeb14 │ └── docker-compose-archive.yml ├── Dockerfile ├── index.proto ├── go.mod ├── banner.go ├── .github └── workflows │ └── docker.yaml ├── README.md ├── go.sum ├── resume.go ├── main.go ├── moonproto └── index.pb.go └── fonts └── block.flf /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .env 3 | moonsnap-downloadoor 4 | testing 5 | -------------------------------------------------------------------------------- /assets/moonsnap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crebsy/moonsnap-downloadoor/HEAD/assets/moonsnap.png -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | export MOONSNAP_API_BASE_URL= 2 | export MOONSNAP_SNAP_KEY= 3 | export MOONSNAP_OUT_DIR=/tmp 4 | -------------------------------------------------------------------------------- /node_configs/reth/1.0.1/docker-compose-full.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | networks: 4 | default: 5 | name: moonsnap_default 6 | 7 | services: 8 | reth: 9 | image: ghcr.io/paradigmxyz/reth:v1.0.1 10 | command: | 11 | node 12 | --full 13 | --authrpc.addr 0.0.0.0 14 | --http 15 | --http.addr 0.0.0.0 16 | --http.api all 17 | --http.corsdomain * 18 | ports: 19 | - "8545:8545" 20 | volumes: 21 | - /data/reth:/root/.local/share/reth 22 | restart: always 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | RUN apk update && apk add --no-cache git binutils 3 | WORKDIR $GOPATH/src/moonsnap-downloadoor 4 | COPY . . 5 | RUN go get -d -v 6 | RUN go build -o /go/bin/moonsnap-downloadoor 7 | RUN strip /go/bin/moonsnap-downloadoor 8 | 9 | FROM scratch 10 | COPY --from=builder /go/bin/moonsnap-downloadoor /go/bin/moonsnap-downloadoor 11 | COPY --from=builder /go/src/moonsnap-downloadoor/fonts/block.flf /go/bin/ 12 | COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/ 13 | ENTRYPOINT ["/go/bin/moonsnap-downloadoor"] 14 | -------------------------------------------------------------------------------- /node_configs/reth/1.0.3/docker-compose-archive.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | networks: 4 | default: 5 | name: moonsnap_default 6 | 7 | services: 8 | reth: 9 | image: ghcr.io/paradigmxyz/reth:v1.0.3 10 | command: | 11 | node 12 | --authrpc.addr 0.0.0.0 13 | --http 14 | --http.addr 0.0.0.0 15 | --http.api all 16 | --http.corsdomain * 17 | --rpc.max-blocks-per-filter 0 18 | --rpc.max-logs-per-response 0 19 | --rpc.eth-proof-window 216000 20 | ports: 21 | - "8545:8545" 22 | volumes: 23 | - /data/reth:/root/.local/share/reth 24 | restart: always 25 | -------------------------------------------------------------------------------- /index.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package moonsnap; 3 | option go_package = ".;moonproto"; 4 | 5 | message Index { 6 | message File { 7 | string filePath = 1; 8 | uint64 fileMode = 2; 9 | uint64 fileSize = 3; 10 | bytes fileHash = 4; 11 | optional string fileLinkTarget = 5; 12 | } 13 | message Library { 14 | string name = 1; 15 | } 16 | repeated File files = 1; 17 | repeated Library libraries = 2; 18 | } 19 | 20 | message LibraryChunk { 21 | int64 libraryIndex = 1; 22 | uint64 startOffset = 2; 23 | uint64 length = 3; 24 | repeated int64 fileIndex = 4; 25 | repeated uint64 fileOffset = 5; 26 | repeated uint64 fileLibraryChunkLength = 6; 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/crebsy/moonsnap-downloadoor 2 | 3 | go 1.22.1 4 | 5 | require ( 6 | github.com/hashicorp/golang-lru/v2 v2.0.7 7 | github.com/pierrec/lz4 v2.6.1+incompatible 8 | github.com/schollz/progressbar/v3 v3.14.4 9 | ) 10 | 11 | require github.com/klauspost/cpuid/v2 v2.2.3 // indirect 12 | 13 | require ( 14 | github.com/mbndr/figlet4go v0.0.0-20190224160619-d6cef5b186ea 15 | github.com/minio/sha256-simd v1.0.1 16 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect 17 | github.com/rivo/uniseg v0.4.7 // indirect 18 | golang.org/x/sys v0.21.0 // indirect 19 | golang.org/x/term v0.20.0 // indirect 20 | google.golang.org/protobuf v1.34.2 21 | ) 22 | -------------------------------------------------------------------------------- /node_configs/lighthouse/v5.2.1/docker-compose-full.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | networks: 4 | default: 5 | name: moonsnap_default 6 | 7 | services: 8 | lighthouse: 9 | image: sigp/lighthouse:v5.2.1 10 | command: | 11 | lighthouse beacon_node 12 | --checkpoint-sync-url=https://beaconstate.info 13 | --debug-level=info 14 | --execution-endpoint=http://:8551 15 | --execution-jwt=/root/.lighthouse/jwt.hex 16 | --http 17 | --http-address=0.0.0.0 18 | ports: 19 | - "5052:5052" 20 | volumes: 21 | - /data/lighthouse:/root/.lighthouse 22 | - /data/jwt.hex:/root/.lighthouse/jwt.hex 23 | restart: always 24 | -------------------------------------------------------------------------------- /banner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/mbndr/figlet4go" 7 | ) 8 | 9 | //go:embed fonts/block.flf 10 | var embedFS embed.FS 11 | 12 | func getBanner() string { 13 | ascii := figlet4go.NewAsciiRender() 14 | 15 | tc, err := figlet4go.NewTrueColorFromHexString("885DBA") 16 | if err != nil { 17 | panic(err) 18 | } 19 | // Adding the colors to RenderOptions 20 | options := figlet4go.NewRenderOptions() 21 | options.FontColor = []figlet4go.Color{ 22 | tc, 23 | } 24 | options.FontName = "block" 25 | fontBytes, err := embedFS.ReadFile("fonts/block.flf") 26 | if err != nil { 27 | panic(err) 28 | } 29 | ascii.LoadBindataFont(fontBytes, "block") 30 | renderStr, _ := ascii.RenderOpts("moonsnap", options) 31 | return renderStr 32 | } 33 | -------------------------------------------------------------------------------- /node_configs/erigon/v3/12c2732ad92733a6a6aae9db7259062182799674/docker-compose-full.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | networks: 4 | default: 5 | name: moonsnap_default 6 | 7 | services: 8 | erigon: 9 | image: thorax/erigon:12c2732ad92733a6a6aae9db7259062182799674 10 | command: | 11 | --chain=mainnet 12 | --datadir=/data 13 | --http 14 | --http.addr=0.0.0.0 15 | --http.vhosts=* 16 | --http.api=eth,erigon,engine,web3,net,trace,txpool,debug,ots,overlay 17 | --authrpc.addr=0.0.0.0 18 | --authrpc.vhosts=* 19 | --maxpeers=20 20 | --torrent.download.rate=300mb 21 | --db.read.concurrency=1024 22 | --prune=hrtc 23 | --externalcl 24 | user: 1001:1001 25 | ports: 26 | - "8545:8545" 27 | volumes: 28 | - /data/erigon:/data 29 | restart: always 30 | -------------------------------------------------------------------------------- /node_configs/erigon/v3/7931ce92ccbe1be5c16c183962cdbaa916aaeb14/docker-compose-archive.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | networks: 4 | default: 5 | name: moonsnap_default 6 | 7 | services: 8 | erigon: 9 | image: thorax/erigon:7931ce92ccbe1be5c16c183962cdbaa916aaeb14 10 | command: | 11 | --chain=mainnet 12 | --datadir=/data 13 | --http 14 | --http.addr=0.0.0.0 15 | --http.vhosts=* 16 | --http.api=eth,erigon,engine,web3,net,trace,txpool,debug,ots,overlay 17 | --authrpc.addr=0.0.0.0 18 | --authrpc.vhosts=* 19 | --maxpeers=20 20 | --torrent.download.rate=300mb 21 | --db.read.concurrency=1024 22 | --externalcl 23 | --sync.loop.block.limit=10_000 24 | --batchSize=2g 25 | user: 1001:1001 26 | ports: 27 | - "8545:8545" 28 | volumes: 29 | - /data/erigon:/data 30 | restart: always -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Build & publish docker image 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | packages: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | # Login against a Docker registry 24 | # https://github.com/docker/login-action 25 | - name: Log into registry ${{ env.REGISTRY }} 26 | uses: docker/login-action@v3 27 | with: 28 | registry: ${{ env.REGISTRY }} 29 | username: ${{ github.actor }} 30 | password: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | # Extract metadata (tags, labels) for Docker 33 | # https://github.com/docker/metadata-action 34 | - name: Extract Docker metadata 35 | id: meta 36 | uses: docker/metadata-action@v5 37 | with: 38 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 39 | tags: | 40 | type=sha 41 | type=sha,format=long 42 | # set latest tag for default branch 43 | type=raw,value=latest,enable={{is_default_branch}} 44 | 45 | # Build and push Docker image with Buildx 46 | # https://github.com/docker/build-push-action 47 | - name: Build and push Docker image 48 | uses: docker/build-push-action@v6 49 | with: 50 | context: . 51 | push: true 52 | tags: ${{ steps.meta.outputs.tags }} 53 | labels: ${{ steps.meta.outputs.labels }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

moonsnap-downloadoor

3 |
4 | 5 | This is a tool for snappy bootstrapping of new full and archive nodes. 6 | It [saves between 66% and 99.2%](#sync-comparison) of the time required to sync a new node from scratch. 7 | 8 | ## Usage 9 | 1. Get a `SNAP_KEY` from https://warpcast.com/crebsy/0xc021a0cf 10 | 2. Compile or download the binary from https://dl.moonsnap.xyz/moonsnap 11 | 3. Download the snap to your infra with max speed 🚀 12 | 4. Start your node and let it sync only a small diff to reach the head of the chain 13 | 14 | ## Configuration 15 | ### Run with pre-built binary 16 | There's a pre-built binary which can be used directly: 17 | ``` 18 | curl https://dl.moonsnap.xyz/moonsnap -o moonsnap 19 | chmod +x moonsnap 20 | ./moonsnap 21 | ``` 22 | 23 | ### Run with docker 24 | You can also use our pre-built docker image: `ghcr.io/crebsy/moonsnap-downloadoor` 25 | To start, set the following env variables: 26 | ``` 27 | docker run -e MOONSNAP_SNAP_KEY= -e MOONSNAP_OUT_DIR= -d ghcr.io/crebsy/moonsnap-downloadoor 28 | ``` 29 | 30 | ### Build locally 31 | You can also build the binary yourself: 32 | ``` 33 | git clone https://github.com/crebsy/moonsnap-downloadoor.git 34 | cd moonsnap-downloadoor 35 | go build -o moonsnap 36 | ./moonsnap 37 | ``` 38 | 39 | ## Performance 40 | ### Sync comparison 41 | | Node | sync time | with moonsnap | time saving | snap size | 42 | | ---------------- | --------- | ------------- | ----------- | --------- | 43 | | [erigon 3 archive](https://github.com/erigontech/erigon/commit/7931ce92ccbe1be5c16c183962cdbaa916aaeb14) | ~12 hours | ~4 hours | 66% | 1.6 TB | 44 | | [erigon 3 full](https://github.com/erigontech/erigon/commit/12c2732ad92733a6a6aae9db7259062182799674) | ~36 hours | ~55 minutes | 97.5% | 978 GB | 45 | | [reth 1.0.3 archive](https://github.com/paradigmxyz/reth/releases/tag/v1.0.3) | ~72 hours | ~3 hours | 96% | 2.2 TB | 46 | | [reth 1.0.1 full](https://github.com/paradigmxyz/reth/releases/tag/v1.0.1) | ~48 hours | ~70 minutes | 97.5% | 1.1 TB | 47 | | lighthouse 5.2.1 | ~17 hours | ~8 minutes | 99.2% | 140 GB | 48 | 49 | ### Hardware specs 50 | network: 2.5Gbps 51 | 52 | disks: RAID0 4x WD Black SN850X NVME 4TB 53 | 54 | cpu: AMD Ryzen 9 5900X 12-Core Processor 55 | 56 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 4 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 5 | github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= 6 | github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= 7 | github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 8 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 9 | github.com/mbndr/figlet4go v0.0.0-20190224160619-d6cef5b186ea h1:mQncVDBpKkAecPcH2IMGpKUQYhwowlafQbfkz2QFqkc= 10 | github.com/mbndr/figlet4go v0.0.0-20190224160619-d6cef5b186ea/go.mod h1:QzTGLGoOqLHUBK8/EZ0v4Fa4CdyXmdyRwCHcl0YbeO4= 11 | github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 12 | github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 13 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= 14 | github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= 15 | github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= 16 | github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 19 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 20 | github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74= 21 | github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 24 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 27 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 28 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 29 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 30 | golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= 31 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 32 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 33 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 34 | -------------------------------------------------------------------------------- /resume.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | ) 11 | 12 | type Bitset []byte 13 | 14 | type RangeResumeEntry struct { 15 | fileOffset int64 16 | chunkBitset Bitset 17 | length int 18 | done int 19 | } 20 | 21 | type ResumeCtx struct { 22 | lock sync.Mutex 23 | resumeFile *os.File 24 | ranges Bitset 25 | rangeEntries map[int]*RangeResumeEntry 26 | opCount int 27 | } 28 | 29 | const RANGE_BITSET_SIZE = 1024 * 1024 / 8 30 | 31 | const AUTOSAVE_AFTER_N_CHUNKS = 128 32 | 33 | func (b Bitset) Set(idx int) { 34 | b[idx/8] |= 1 << (idx % 8) 35 | } 36 | 37 | func (b Bitset) Get(idx int) bool { 38 | return b[idx/8]&(1<<(idx%8)) > 0 39 | } 40 | 41 | func LoadResumeFile() *ResumeCtx { 42 | resumeFileName := filepath.Join(OUT_DIR, ".moonsnap_resume") 43 | _, err := os.Stat(resumeFileName) 44 | var resumeFile *os.File 45 | if err != nil && errors.Is(err, os.ErrNotExist) { 46 | resumeFile, err = os.Create(resumeFileName) 47 | if err != nil { 48 | panic(err) 49 | } 50 | } else if err != nil { 51 | panic(err) 52 | } else { 53 | resumeFile, err = os.OpenFile(resumeFileName, os.O_RDWR, 0) 54 | if err != nil { 55 | panic(err) 56 | } 57 | } 58 | 59 | ranges := make(Bitset, RANGE_BITSET_SIZE) 60 | n, err := io.ReadFull(resumeFile, ranges) 61 | if err != nil || n != RANGE_BITSET_SIZE { 62 | err = resumeFile.Truncate(0) 63 | if err != nil { 64 | panic(err) 65 | } 66 | ranges = make(Bitset, RANGE_BITSET_SIZE) 67 | resumeFile.Seek(0, io.SeekStart) 68 | _, err := io.Copy(resumeFile, bytes.NewReader(ranges)) 69 | if err != nil { 70 | panic(err) 71 | } 72 | } 73 | resumeFile.Seek(RANGE_BITSET_SIZE, io.SeekStart) 74 | 75 | return &ResumeCtx{ 76 | resumeFile: resumeFile, 77 | ranges: ranges, 78 | rangeEntries: map[int]*RangeResumeEntry{}, 79 | } 80 | } 81 | 82 | func (r *ResumeCtx) loadNextRange(rangeIndex int, numChunksInRange int) { 83 | r.lock.Lock() 84 | defer r.lock.Unlock() 85 | 86 | numBytes := (numChunksInRange + 7) / 8 87 | rangeBitset := make(Bitset, numBytes) 88 | 89 | offset, err := r.resumeFile.Seek(0, io.SeekCurrent) 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | n, err := io.ReadFull(r.resumeFile, rangeBitset) 95 | if err != nil || n != numBytes { 96 | err = r.resumeFile.Truncate(offset) 97 | if err != nil { 98 | panic(err) 99 | } 100 | rangeBitset = make(Bitset, numBytes) 101 | r.resumeFile.Seek(offset, io.SeekStart) 102 | _, err := io.Copy(r.resumeFile, bytes.NewReader(rangeBitset)) 103 | if err != nil { 104 | panic(err) 105 | } 106 | } 107 | r.resumeFile.Seek(offset+int64(numBytes), io.SeekStart) 108 | 109 | done := 0 110 | for i := range numChunksInRange { 111 | if rangeBitset.Get(i) { 112 | done++ 113 | } 114 | } 115 | 116 | r.rangeEntries[rangeIndex] = &RangeResumeEntry{ 117 | fileOffset: offset, 118 | chunkBitset: rangeBitset, 119 | length: numChunksInRange, 120 | done: done, 121 | } 122 | } 123 | 124 | func (r *ResumeCtx) isRangeDone(rangeIdx int) bool { 125 | r.lock.Lock() 126 | defer r.lock.Unlock() 127 | return r.ranges.Get(rangeIdx) 128 | } 129 | 130 | func (r *ResumeCtx) getNumChunksDoneInRange(rangeIdx int) int { 131 | r.lock.Lock() 132 | defer r.lock.Unlock() 133 | entry := r.rangeEntries[rangeIdx] 134 | 135 | consecutiveDone := 0 136 | for i := 0; i < entry.length; i++ { 137 | if entry.chunkBitset.Get(i) { 138 | consecutiveDone++ 139 | } else { 140 | break 141 | } 142 | } 143 | 144 | return consecutiveDone 145 | } 146 | 147 | func (r *ResumeCtx) setChunkDone(rangeIdx int, chunkIdx int) { 148 | r.lock.Lock() 149 | defer r.lock.Unlock() 150 | 151 | entry := r.rangeEntries[rangeIdx] 152 | 153 | if !entry.chunkBitset.Get(chunkIdx) { 154 | entry.chunkBitset.Set(chunkIdx) 155 | entry.done++ 156 | } 157 | 158 | if entry.done >= entry.length { 159 | r.ranges.Set(rangeIdx) 160 | go r.persist() 161 | } else { 162 | r.opCount++ 163 | if r.opCount >= AUTOSAVE_AFTER_N_CHUNKS { 164 | r.opCount = 0 165 | go r.persist() 166 | } 167 | } 168 | } 169 | 170 | func (r *ResumeCtx) persist() { 171 | r.lock.Lock() 172 | defer r.lock.Unlock() 173 | 174 | if r.resumeFile == nil { 175 | return 176 | } 177 | 178 | offset, err := r.resumeFile.Seek(0, io.SeekCurrent) 179 | if err != nil { 180 | panic(err) 181 | } 182 | 183 | _, err = r.resumeFile.Seek(0, io.SeekStart) 184 | if err != nil { 185 | panic(err) 186 | } 187 | _, err = io.Copy(r.resumeFile, bytes.NewReader(r.ranges)) 188 | if err != nil { 189 | panic(err) 190 | } 191 | 192 | dropRangeIndexes := []int{} 193 | for rangeIdx, entry := range r.rangeEntries { 194 | _, err = r.resumeFile.Seek(entry.fileOffset, io.SeekStart) 195 | if err != nil { 196 | panic(err) 197 | } 198 | _, err := io.Copy(r.resumeFile, bytes.NewReader(entry.chunkBitset)) 199 | if err != nil { 200 | panic(err) 201 | } 202 | 203 | if entry.done >= entry.length { 204 | dropRangeIndexes = append(dropRangeIndexes, rangeIdx) 205 | } 206 | } 207 | 208 | for _, rangeIdx := range dropRangeIndexes { 209 | /*if rangeIdx == 0 { 210 | entry := r.rangeEntries[rangeIdx] 211 | fmt.Printf("Dropping rangeIdx %d, entry.done=%d, entry.length=%d\n", rangeIdx, entry.done, entry.length) 212 | }*/ 213 | delete(r.rangeEntries, rangeIdx) 214 | } 215 | 216 | _, err = r.resumeFile.Seek(offset, io.SeekStart) 217 | if err != nil { 218 | panic(err) 219 | } 220 | } 221 | 222 | func (r *ResumeCtx) close() { 223 | r.persist() 224 | 225 | r.lock.Lock() 226 | err := r.resumeFile.Close() 227 | if err != nil { 228 | panic(err) 229 | } 230 | r.resumeFile = nil 231 | r.lock.Unlock() 232 | } 233 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/hex" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/fs" 12 | "net/http" 13 | "net/url" 14 | "os" 15 | "path" 16 | "path/filepath" 17 | "strconv" 18 | "sync" 19 | "time" 20 | 21 | "github.com/crebsy/moonsnap-downloadoor/moonproto" 22 | lru "github.com/hashicorp/golang-lru/v2" 23 | "github.com/minio/sha256-simd" 24 | "github.com/pierrec/lz4" 25 | "github.com/schollz/progressbar/v3" 26 | "golang.org/x/sys/unix" 27 | "google.golang.org/protobuf/encoding/protodelim" 28 | ) 29 | 30 | type Chunk struct { 31 | FileIndex int 32 | FileOffset int 33 | Data []byte 34 | RangeIndex int 35 | ChunkIndex int 36 | } 37 | 38 | type SnapUrlResponse struct { 39 | Url string `json:"url"` 40 | Authorization string `json:"authorization"` 41 | FileName string `json:"file_name"` 42 | Curl string `json:"curl"` 43 | } 44 | 45 | type DownloadItem struct { 46 | chunk *moonproto.LibraryChunk 47 | rangeIndex int 48 | } 49 | 50 | var API_BASE_URL = "https://api.moonsnap.xyz" 51 | 52 | var SNAP_KEY = os.Getenv("MOONSNAP_SNAP_KEY") 53 | var OUT_DIR = os.Getenv("MOONSNAP_OUT_DIR") 54 | 55 | var CHUNK_SIZE = 8192 56 | var MAX_RETRIES = _getMaxRetries() 57 | 58 | func main() { 59 | fmt.Println(getBanner()) 60 | 61 | apiBaseUrl := os.Getenv("MOONSNAP_API_BASE_URL") 62 | if len(apiBaseUrl) > 0 { 63 | API_BASE_URL = apiBaseUrl 64 | } 65 | if len(SNAP_KEY) == 0 && len(os.Args) > 1 { 66 | SNAP_KEY = os.Args[1] 67 | } 68 | 69 | if len(OUT_DIR) == 0 && len(os.Args) > 2 { 70 | OUT_DIR = os.Args[2] 71 | } else { 72 | OUT_DIR = "." 73 | } 74 | 75 | if len(SNAP_KEY) == 0 { 76 | panic("Please provide a MOONSNAP_SNAP_KEY") 77 | } 78 | 79 | err := os.MkdirAll(OUT_DIR, 0755) 80 | if err != nil { 81 | panic(err) 82 | } 83 | 84 | indexFileName := filepath.Join(OUT_DIR, ".moonsnap_index") 85 | _, err = os.Stat(indexFileName) 86 | if err != nil && errors.Is(err, os.ErrNotExist) { 87 | downloadIndexFile(indexFileName) 88 | } 89 | 90 | file, err := os.Open(indexFileName) 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | resumeCtx := LoadResumeFile() 96 | 97 | reader := bufio.NewReader(file) 98 | index := moonproto.Index{} 99 | err = protodelim.UnmarshalOptions{MaxSize: -1}.UnmarshalFrom(reader, &index) 100 | if err != nil { 101 | panic(err) 102 | } 103 | totalBytes := createFileStructure(&index, OUT_DIR) 104 | bar := progressbar.DefaultBytes(int64(totalBytes), "downloading files") 105 | fileCache, err := lru.NewWithEvict(4096, func(_ int, file *os.File) { 106 | err := file.Close() 107 | if err != nil { 108 | panic(err) 109 | } 110 | }) 111 | if err != nil { 112 | panic(err) 113 | } 114 | numThreads := 128 115 | downloadChan := make(chan DownloadItem) 116 | persistChan := make(chan Chunk, 1024) 117 | downloadWg := sync.WaitGroup{} 118 | persistWg := sync.WaitGroup{} 119 | for range numThreads { 120 | persistWg.Add(1) 121 | go func() { 122 | chunkSavor(&index, resumeCtx, bar, fileCache, OUT_DIR, CHUNK_SIZE, persistChan) 123 | persistWg.Done() 124 | }() 125 | } 126 | 127 | for range numThreads { 128 | downloadWg.Add(1) 129 | go func() { 130 | downloadoor(&index, resumeCtx, persistChan, downloadChan) 131 | downloadWg.Done() 132 | }() 133 | } 134 | 135 | for rangeIdx := 0; ; rangeIdx++ { 136 | chunk := moonproto.LibraryChunk{} 137 | err = protodelim.UnmarshalOptions{MaxSize: -1}.UnmarshalFrom(reader, &chunk) 138 | if err != nil { 139 | if err == io.EOF { 140 | break 141 | } 142 | panic(err) 143 | } 144 | 145 | resumeCtx.loadNextRange(rangeIdx, len(chunk.FileIndex)) 146 | if resumeCtx.isRangeDone(rangeIdx) { 147 | sumBytes := int64(0) 148 | for i := 0; i < len(chunk.FileIndex); i++ { 149 | if chunk.FileIndex[i] < 0 { 150 | continue 151 | } 152 | file := index.Files[chunk.FileIndex[i]] 153 | 154 | remainingFileSizeAfterOffset := int64(file.FileSize - chunk.FileOffset[i]) 155 | maxChunkSize := int64(CHUNK_SIZE) 156 | if maxChunkSize < remainingFileSizeAfterOffset { 157 | sumBytes += maxChunkSize 158 | } else { 159 | sumBytes += remainingFileSizeAfterOffset 160 | } 161 | } 162 | bar.Add64(sumBytes) 163 | go resumeCtx.persist() 164 | continue 165 | } 166 | downloadChan <- DownloadItem{&chunk, rangeIdx} 167 | } 168 | close(downloadChan) 169 | downloadWg.Wait() 170 | close(persistChan) 171 | persistWg.Wait() 172 | 173 | fileCache.Purge() 174 | resumeCtx.close() 175 | bar.Close() 176 | verifyFiles(&index, OUT_DIR) 177 | } 178 | 179 | func downloadIndexFile(indexFileName string) string { 180 | retries := 0 181 | bar := progressbar.Default(int64(100), "downloading index file") 182 | retry_index: 183 | snapUrlCreds, err := getSnapUrlCreds("") 184 | if err != nil { 185 | retries += 1 186 | if retries <= MAX_RETRIES { 187 | time.Sleep(1 * time.Second) 188 | goto retry_index 189 | } 190 | } 191 | if snapUrlCreds == nil { 192 | panic(err) 193 | } 194 | 195 | client := http.Client{} 196 | u, err := url.Parse(snapUrlCreds.Url) 197 | if err != nil { 198 | panic(err) 199 | } 200 | bar.Add(25) 201 | res, err := client.Do(&http.Request{ 202 | Method: "GET", 203 | Header: http.Header{ 204 | "Authorization": []string{snapUrlCreds.Authorization}, 205 | }, 206 | URL: u, 207 | }) 208 | if err != nil { 209 | retries += 1 210 | if retries <= MAX_RETRIES { 211 | time.Sleep(1 * time.Second) 212 | goto retry_index 213 | } 214 | panic(err) 215 | } 216 | defer res.Body.Close() 217 | if res.StatusCode != http.StatusOK { 218 | //fmt.Printf("%+v\n", snapUrlCreds) 219 | dfn := "/tmp/index.failed" 220 | df, _ := os.Create(dfn) 221 | defer df.Close() 222 | _, err = io.Copy(df, res.Body) 223 | if err != nil { 224 | panic(err) 225 | } 226 | 227 | //fmt.Printf("Bad status while downloading index: %s, dumped to %s\n", res.Status, dfn) 228 | retries += 1 229 | if retries <= MAX_RETRIES { 230 | time.Sleep(1 * time.Second) 231 | goto retry_index 232 | } 233 | } 234 | bar.Add(50) 235 | 236 | f, err := os.Create(indexFileName) 237 | if err != nil { 238 | panic(err) 239 | } 240 | defer f.Close() 241 | _, err = io.Copy(f, res.Body) 242 | if err != nil { 243 | panic(err) 244 | } 245 | bar.Add(25) 246 | bar.Close() 247 | 248 | return f.Name() 249 | } 250 | 251 | func getSnapUrlCreds(fileName string) (*SnapUrlResponse, error) { 252 | client := http.Client{} 253 | u, err := url.Parse(API_BASE_URL) 254 | if err != nil { 255 | return nil, err 256 | } 257 | values := u.Query() 258 | values.Set("snapKey", SNAP_KEY) 259 | if len(fileName) > 0 { 260 | values.Set("fileName", fileName) 261 | } 262 | u.RawQuery = values.Encode() 263 | res, err := client.Do(&http.Request{ 264 | Method: "GET", 265 | URL: u, 266 | }) 267 | if err != nil { 268 | return nil, err 269 | } 270 | defer res.Body.Close() 271 | body, err := io.ReadAll(res.Body) 272 | if err != nil { 273 | return nil, err 274 | } 275 | 276 | if res.StatusCode != http.StatusOK { 277 | return nil, errors.New(string(body)) 278 | } 279 | 280 | snapUrlResponse := SnapUrlResponse{} 281 | err = json.Unmarshal(body, &snapUrlResponse) 282 | if err != nil { 283 | return nil, err 284 | } 285 | return &snapUrlResponse, nil 286 | } 287 | 288 | func verifyFilesWorker(fileChan <-chan *moonproto.Index_File, bar *progressbar.ProgressBar, outDir string, wg *sync.WaitGroup) { 289 | for file := range fileChan { 290 | if !fs.FileMode(file.FileMode).IsRegular() { 291 | bar.Add(1) 292 | continue 293 | } 294 | f, err := os.Open(path.Join(outDir, file.FilePath)) 295 | if err != nil { 296 | panic(err) 297 | } 298 | hash := sha256.New() 299 | _, err = io.Copy(hash, f) 300 | if err != nil { 301 | panic(err) 302 | } 303 | fileHash := hash.Sum(nil) 304 | if !bytes.Equal(fileHash, file.FileHash) { 305 | panic(fmt.Errorf("fileHash mismatch for %s: fileHash: %s, expected: %s", 306 | file.FilePath, hex.EncodeToString(fileHash), hex.EncodeToString(file.FileHash))) 307 | } 308 | f.Close() 309 | bar.Add(1) 310 | } 311 | 312 | wg.Done() 313 | } 314 | 315 | func verifyFiles(index *moonproto.Index, outDir string) { 316 | bar := progressbar.Default(int64(len(index.Files)), "verifying files") 317 | fileChan := make(chan *moonproto.Index_File) 318 | wg := sync.WaitGroup{} 319 | 320 | for range 8 { 321 | wg.Add(1) 322 | go verifyFilesWorker(fileChan, bar, outDir, &wg) 323 | } 324 | 325 | for _, file := range index.Files { 326 | fileChan <- file 327 | } 328 | close(fileChan) 329 | wg.Wait() 330 | } 331 | 332 | func chunkSavor(index *moonproto.Index, resumeCtx *ResumeCtx, bar *progressbar.ProgressBar, fileCache *lru.Cache[int, *os.File], outDir string, chunkSize int, chunkChan <-chan Chunk) { 333 | for chunk := range chunkChan { 334 | f, ok := fileCache.Get(chunk.FileIndex) 335 | if !ok { 336 | var err error 337 | f, err = os.OpenFile(path.Join(outDir, index.Files[chunk.FileIndex].FilePath), os.O_WRONLY, 0) 338 | if err != nil { 339 | panic(err) 340 | } 341 | fileCache.Add(chunk.FileIndex, f) 342 | } 343 | bar.Add(len(chunk.Data)) 344 | n, err := f.WriteAt(chunk.Data, int64(chunk.FileOffset*chunkSize)) 345 | if err != nil || n != len(chunk.Data) { 346 | panic(err) 347 | } 348 | 349 | resumeCtx.setChunkDone(chunk.RangeIndex, chunk.ChunkIndex) 350 | } 351 | } 352 | 353 | func downloadoor(index *moonproto.Index, resumeCtx *ResumeCtx, chunkChan chan<- Chunk, downloadChan <-chan DownloadItem) { 354 | client := http.Client{} 355 | for item := range downloadChan { 356 | libChunk := item.chunk 357 | // contains already the url_prefix but missing the leading "/" 358 | libName := "/" + index.Libraries[libChunk.LibraryIndex].Name 359 | retries := 0 360 | //fmt.Printf("start=%s, startOffset=%d\n", u.Path, libChunk.StartOffset) 361 | localStartOffset := libChunk.StartOffset 362 | localLength := libChunk.Length 363 | localIdx := 0 364 | 365 | alreadyDone := resumeCtx.getNumChunksDoneInRange(item.rangeIndex) 366 | for ; localIdx < alreadyDone; localIdx++ { 367 | len := libChunk.FileLibraryChunkLength[localIdx] 368 | if len > 0 { 369 | localStartOffset += len 370 | localLength -= len 371 | } 372 | } 373 | 374 | retry: 375 | // get url for lib 376 | libUrlCreds, err := getSnapUrlCreds(libName) 377 | if err != nil { 378 | retries += 1 379 | if retries <= MAX_RETRIES { 380 | time.Sleep(1 * time.Second) 381 | goto retry 382 | } 383 | panic(err) 384 | } 385 | 386 | req, err := http.NewRequest( 387 | "GET", 388 | libUrlCreds.Url, 389 | nil, 390 | ) 391 | if err != nil { 392 | retries += 1 393 | if retries <= MAX_RETRIES { 394 | time.Sleep(1 * time.Second) 395 | goto retry 396 | } 397 | panic(err) 398 | } 399 | 400 | req.Header["Authorization"] = []string{libUrlCreds.Authorization} 401 | req.Header["Range"] = []string{ 402 | fmt.Sprintf( 403 | "bytes=%d-%d", 404 | localStartOffset, 405 | localStartOffset+localLength-1, 406 | ), 407 | } 408 | 409 | res, err := client.Do(req) 410 | if err != nil { 411 | retries += 1 412 | if retries <= MAX_RETRIES { 413 | time.Sleep(1 * time.Second) 414 | goto retry 415 | } 416 | panic(err) 417 | } 418 | if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusPartialContent { 419 | //fmt.Printf("%+v\n", req) 420 | //fmt.Printf("%d, %d\n", localLength, localStartOffset) 421 | retries += 1 422 | if retries <= MAX_RETRIES { 423 | time.Sleep(1 * time.Second) 424 | goto retry 425 | } 426 | panic(fmt.Sprintf("Download failed with status %d %s", res.StatusCode, res.Status)) 427 | } 428 | var chunkBytes []byte 429 | 430 | for ; localIdx < len(libChunk.FileIndex); localIdx++ { 431 | if libChunk.FileLibraryChunkLength[localIdx] > 0 { 432 | chunkBytes = make([]byte, libChunk.FileLibraryChunkLength[localIdx]) 433 | offset := 0 434 | for offset < len(chunkBytes) { 435 | n, err := res.Body.Read(chunkBytes[offset:]) 436 | if err != nil && err != io.EOF { 437 | retries += 1 438 | if retries <= MAX_RETRIES { 439 | time.Sleep(1 * time.Second) 440 | goto retry 441 | } 442 | panic(err) 443 | } 444 | offset += n 445 | if err == io.EOF && offset < len(chunkBytes) { 446 | retries += 1 447 | if retries <= MAX_RETRIES { 448 | time.Sleep(1 * time.Second) 449 | goto retry 450 | } 451 | panic(err) 452 | } 453 | } 454 | } 455 | if libChunk.FileIndex[localIdx] < 0 { 456 | localStartOffset += uint64(len(chunkBytes)) 457 | localLength -= uint64(len(chunkBytes)) 458 | continue 459 | } 460 | dst := make([]byte, CHUNK_SIZE) 461 | decompressedLength, err := lz4.UncompressBlock(chunkBytes, dst) 462 | if err != nil { 463 | //fmt.Printf("%+v\n", req) 464 | //fmt.Printf("%d, %d\n", localLength, localStartOffset) 465 | retries += 1 466 | if retries <= MAX_RETRIES { 467 | time.Sleep(1 * time.Second) 468 | goto retry 469 | } 470 | panic(err) 471 | } 472 | /* else { 473 | fmt.Printf("DUPE! %d\n", i) 474 | }*/ 475 | 476 | chunkChan <- Chunk{ 477 | FileIndex: int(libChunk.FileIndex[localIdx]), 478 | FileOffset: int(libChunk.FileOffset[localIdx]), 479 | Data: dst[0:decompressedLength], 480 | RangeIndex: item.rangeIndex, 481 | ChunkIndex: localIdx, 482 | } 483 | 484 | if libChunk.FileLibraryChunkLength[localIdx] > 0 { 485 | localStartOffset += uint64(len(chunkBytes)) 486 | localLength -= uint64(len(chunkBytes)) 487 | } 488 | } 489 | //fmt.Printf("end=%s, startOffset=%d\n", u.Path, libChunk.StartOffset) 490 | } 491 | } 492 | 493 | func createFileStructure(index *moonproto.Index, outDir string) int { 494 | totalBytes := 0 495 | for _, file := range index.Files { 496 | //fmt.Println(file) 497 | totalBytes += int(file.FileSize) 498 | if file.FileMode&uint64(fs.ModeSymlink) > 0 { 499 | //fmt.Println(file.FileLinkTarget) 500 | 501 | newLinkTarget, err := filepath.Abs(path.Join(outDir, *file.FileLinkTarget)) 502 | if err != nil { 503 | panic(err) 504 | } 505 | os.Symlink(newLinkTarget, path.Join(outDir, file.FilePath)) 506 | continue 507 | } else if file.FileMode&uint64(fs.ModeDir) > 0 { 508 | os.Mkdir(path.Join(outDir, file.FilePath), fs.FileMode(file.FileMode&uint64(fs.ModePerm))) 509 | continue 510 | } 511 | f, err := os.OpenFile(path.Join(outDir, file.FilePath), os.O_CREATE|os.O_RDWR, fs.FileMode(file.FileMode)) 512 | if err != nil { 513 | panic(err) 514 | } 515 | 516 | //TODO handle 0-byte files (e.g. LOCK) 517 | if file.FileSize > 0 { 518 | err := unix.Fallocate(int(f.Fd()), 0, 0, int64(file.FileSize)) 519 | if err != nil { 520 | fmt.Printf("Error with: path=%s, size=%d\n", file.FilePath, file.FileSize) 521 | panic(err) 522 | } 523 | } 524 | err = f.Close() 525 | if err != nil { 526 | panic(err) 527 | } 528 | } 529 | return totalBytes 530 | } 531 | 532 | func _getMaxRetries() int { 533 | retries := 16 534 | var err error 535 | retryStr := os.Getenv("MOONSNAP_MAX_RETRIES") 536 | if len(retryStr) > 0 { 537 | retries, err = strconv.Atoi(retryStr) 538 | if err != nil { 539 | fmt.Printf("Cannot convert string to int %s", retryStr) 540 | } 541 | } 542 | return retries 543 | } 544 | -------------------------------------------------------------------------------- /moonproto/index.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0-devel 4 | // protoc v3.14.0 5 | // source: index.proto 6 | 7 | package moonproto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type Index struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Files []*Index_File `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"` 29 | Libraries []*Index_Library `protobuf:"bytes,2,rep,name=libraries,proto3" json:"libraries,omitempty"` 30 | } 31 | 32 | func (x *Index) Reset() { 33 | *x = Index{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_index_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *Index) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*Index) ProtoMessage() {} 46 | 47 | func (x *Index) ProtoReflect() protoreflect.Message { 48 | mi := &file_index_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use Index.ProtoReflect.Descriptor instead. 60 | func (*Index) Descriptor() ([]byte, []int) { 61 | return file_index_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *Index) GetFiles() []*Index_File { 65 | if x != nil { 66 | return x.Files 67 | } 68 | return nil 69 | } 70 | 71 | func (x *Index) GetLibraries() []*Index_Library { 72 | if x != nil { 73 | return x.Libraries 74 | } 75 | return nil 76 | } 77 | 78 | type LibraryChunk struct { 79 | state protoimpl.MessageState 80 | sizeCache protoimpl.SizeCache 81 | unknownFields protoimpl.UnknownFields 82 | 83 | LibraryIndex int64 `protobuf:"varint,1,opt,name=libraryIndex,proto3" json:"libraryIndex,omitempty"` 84 | StartOffset uint64 `protobuf:"varint,2,opt,name=startOffset,proto3" json:"startOffset,omitempty"` 85 | Length uint64 `protobuf:"varint,3,opt,name=length,proto3" json:"length,omitempty"` 86 | FileIndex []int64 `protobuf:"varint,4,rep,packed,name=fileIndex,proto3" json:"fileIndex,omitempty"` 87 | FileOffset []uint64 `protobuf:"varint,5,rep,packed,name=fileOffset,proto3" json:"fileOffset,omitempty"` 88 | FileLibraryChunkLength []uint64 `protobuf:"varint,6,rep,packed,name=fileLibraryChunkLength,proto3" json:"fileLibraryChunkLength,omitempty"` 89 | } 90 | 91 | func (x *LibraryChunk) Reset() { 92 | *x = LibraryChunk{} 93 | if protoimpl.UnsafeEnabled { 94 | mi := &file_index_proto_msgTypes[1] 95 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 96 | ms.StoreMessageInfo(mi) 97 | } 98 | } 99 | 100 | func (x *LibraryChunk) String() string { 101 | return protoimpl.X.MessageStringOf(x) 102 | } 103 | 104 | func (*LibraryChunk) ProtoMessage() {} 105 | 106 | func (x *LibraryChunk) ProtoReflect() protoreflect.Message { 107 | mi := &file_index_proto_msgTypes[1] 108 | if protoimpl.UnsafeEnabled && x != nil { 109 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 110 | if ms.LoadMessageInfo() == nil { 111 | ms.StoreMessageInfo(mi) 112 | } 113 | return ms 114 | } 115 | return mi.MessageOf(x) 116 | } 117 | 118 | // Deprecated: Use LibraryChunk.ProtoReflect.Descriptor instead. 119 | func (*LibraryChunk) Descriptor() ([]byte, []int) { 120 | return file_index_proto_rawDescGZIP(), []int{1} 121 | } 122 | 123 | func (x *LibraryChunk) GetLibraryIndex() int64 { 124 | if x != nil { 125 | return x.LibraryIndex 126 | } 127 | return 0 128 | } 129 | 130 | func (x *LibraryChunk) GetStartOffset() uint64 { 131 | if x != nil { 132 | return x.StartOffset 133 | } 134 | return 0 135 | } 136 | 137 | func (x *LibraryChunk) GetLength() uint64 { 138 | if x != nil { 139 | return x.Length 140 | } 141 | return 0 142 | } 143 | 144 | func (x *LibraryChunk) GetFileIndex() []int64 { 145 | if x != nil { 146 | return x.FileIndex 147 | } 148 | return nil 149 | } 150 | 151 | func (x *LibraryChunk) GetFileOffset() []uint64 { 152 | if x != nil { 153 | return x.FileOffset 154 | } 155 | return nil 156 | } 157 | 158 | func (x *LibraryChunk) GetFileLibraryChunkLength() []uint64 { 159 | if x != nil { 160 | return x.FileLibraryChunkLength 161 | } 162 | return nil 163 | } 164 | 165 | type Index_File struct { 166 | state protoimpl.MessageState 167 | sizeCache protoimpl.SizeCache 168 | unknownFields protoimpl.UnknownFields 169 | 170 | FilePath string `protobuf:"bytes,1,opt,name=filePath,proto3" json:"filePath,omitempty"` 171 | FileMode uint64 `protobuf:"varint,2,opt,name=fileMode,proto3" json:"fileMode,omitempty"` 172 | FileSize uint64 `protobuf:"varint,3,opt,name=fileSize,proto3" json:"fileSize,omitempty"` 173 | FileHash []byte `protobuf:"bytes,4,opt,name=fileHash,proto3" json:"fileHash,omitempty"` 174 | FileLinkTarget *string `protobuf:"bytes,5,opt,name=fileLinkTarget,proto3,oneof" json:"fileLinkTarget,omitempty"` 175 | } 176 | 177 | func (x *Index_File) Reset() { 178 | *x = Index_File{} 179 | if protoimpl.UnsafeEnabled { 180 | mi := &file_index_proto_msgTypes[2] 181 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 182 | ms.StoreMessageInfo(mi) 183 | } 184 | } 185 | 186 | func (x *Index_File) String() string { 187 | return protoimpl.X.MessageStringOf(x) 188 | } 189 | 190 | func (*Index_File) ProtoMessage() {} 191 | 192 | func (x *Index_File) ProtoReflect() protoreflect.Message { 193 | mi := &file_index_proto_msgTypes[2] 194 | if protoimpl.UnsafeEnabled && x != nil { 195 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 196 | if ms.LoadMessageInfo() == nil { 197 | ms.StoreMessageInfo(mi) 198 | } 199 | return ms 200 | } 201 | return mi.MessageOf(x) 202 | } 203 | 204 | // Deprecated: Use Index_File.ProtoReflect.Descriptor instead. 205 | func (*Index_File) Descriptor() ([]byte, []int) { 206 | return file_index_proto_rawDescGZIP(), []int{0, 0} 207 | } 208 | 209 | func (x *Index_File) GetFilePath() string { 210 | if x != nil { 211 | return x.FilePath 212 | } 213 | return "" 214 | } 215 | 216 | func (x *Index_File) GetFileMode() uint64 { 217 | if x != nil { 218 | return x.FileMode 219 | } 220 | return 0 221 | } 222 | 223 | func (x *Index_File) GetFileSize() uint64 { 224 | if x != nil { 225 | return x.FileSize 226 | } 227 | return 0 228 | } 229 | 230 | func (x *Index_File) GetFileHash() []byte { 231 | if x != nil { 232 | return x.FileHash 233 | } 234 | return nil 235 | } 236 | 237 | func (x *Index_File) GetFileLinkTarget() string { 238 | if x != nil && x.FileLinkTarget != nil { 239 | return *x.FileLinkTarget 240 | } 241 | return "" 242 | } 243 | 244 | type Index_Library struct { 245 | state protoimpl.MessageState 246 | sizeCache protoimpl.SizeCache 247 | unknownFields protoimpl.UnknownFields 248 | 249 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 250 | } 251 | 252 | func (x *Index_Library) Reset() { 253 | *x = Index_Library{} 254 | if protoimpl.UnsafeEnabled { 255 | mi := &file_index_proto_msgTypes[3] 256 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 257 | ms.StoreMessageInfo(mi) 258 | } 259 | } 260 | 261 | func (x *Index_Library) String() string { 262 | return protoimpl.X.MessageStringOf(x) 263 | } 264 | 265 | func (*Index_Library) ProtoMessage() {} 266 | 267 | func (x *Index_Library) ProtoReflect() protoreflect.Message { 268 | mi := &file_index_proto_msgTypes[3] 269 | if protoimpl.UnsafeEnabled && x != nil { 270 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 271 | if ms.LoadMessageInfo() == nil { 272 | ms.StoreMessageInfo(mi) 273 | } 274 | return ms 275 | } 276 | return mi.MessageOf(x) 277 | } 278 | 279 | // Deprecated: Use Index_Library.ProtoReflect.Descriptor instead. 280 | func (*Index_Library) Descriptor() ([]byte, []int) { 281 | return file_index_proto_rawDescGZIP(), []int{0, 1} 282 | } 283 | 284 | func (x *Index_Library) GetName() string { 285 | if x != nil { 286 | return x.Name 287 | } 288 | return "" 289 | } 290 | 291 | var File_index_proto protoreflect.FileDescriptor 292 | 293 | var file_index_proto_rawDesc = []byte{ 294 | 0x0a, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6d, 295 | 0x6f, 0x6f, 0x6e, 0x73, 0x6e, 0x61, 0x70, 0x22, 0xc2, 0x02, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 296 | 0x78, 0x12, 0x2a, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 297 | 0x32, 0x14, 0x2e, 0x6d, 0x6f, 0x6f, 0x6e, 0x73, 0x6e, 0x61, 0x70, 0x2e, 0x49, 0x6e, 0x64, 0x65, 298 | 0x78, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x35, 0x0a, 299 | 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 300 | 0x32, 0x17, 0x2e, 0x6d, 0x6f, 0x6f, 0x6e, 0x73, 0x6e, 0x61, 0x70, 0x2e, 0x49, 0x6e, 0x64, 0x65, 301 | 0x78, 0x2e, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 302 | 0x72, 0x69, 0x65, 0x73, 0x1a, 0xb6, 0x01, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 303 | 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 304 | 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 305 | 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x66, 0x69, 0x6c, 306 | 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 307 | 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 308 | 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 309 | 0x01, 0x28, 0x0c, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2b, 0x0a, 310 | 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 311 | 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x6e, 312 | 0x6b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x66, 313 | 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x1a, 0x1d, 0x0a, 314 | 0x07, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 315 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xe2, 0x01, 0x0a, 316 | 0x0c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x22, 0x0a, 317 | 0x0c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 318 | 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x6e, 0x64, 0x65, 319 | 0x78, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 320 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x66, 0x66, 321 | 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 322 | 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x66, 323 | 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 324 | 0x66, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 325 | 0x65, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x66, 326 | 0x69, 0x6c, 0x65, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x36, 0x0a, 0x16, 0x66, 0x69, 0x6c, 327 | 0x65, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x4c, 0x65, 0x6e, 328 | 0x67, 0x74, 0x68, 0x18, 0x06, 0x20, 0x03, 0x28, 0x04, 0x52, 0x16, 0x66, 0x69, 0x6c, 0x65, 0x4c, 329 | 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x4c, 0x65, 0x6e, 0x67, 0x74, 330 | 0x68, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x3b, 0x6d, 0x6f, 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 331 | 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 332 | } 333 | 334 | var ( 335 | file_index_proto_rawDescOnce sync.Once 336 | file_index_proto_rawDescData = file_index_proto_rawDesc 337 | ) 338 | 339 | func file_index_proto_rawDescGZIP() []byte { 340 | file_index_proto_rawDescOnce.Do(func() { 341 | file_index_proto_rawDescData = protoimpl.X.CompressGZIP(file_index_proto_rawDescData) 342 | }) 343 | return file_index_proto_rawDescData 344 | } 345 | 346 | var file_index_proto_msgTypes = make([]protoimpl.MessageInfo, 4) 347 | var file_index_proto_goTypes = []interface{}{ 348 | (*Index)(nil), // 0: moonsnap.Index 349 | (*LibraryChunk)(nil), // 1: moonsnap.LibraryChunk 350 | (*Index_File)(nil), // 2: moonsnap.Index.File 351 | (*Index_Library)(nil), // 3: moonsnap.Index.Library 352 | } 353 | var file_index_proto_depIdxs = []int32{ 354 | 2, // 0: moonsnap.Index.files:type_name -> moonsnap.Index.File 355 | 3, // 1: moonsnap.Index.libraries:type_name -> moonsnap.Index.Library 356 | 2, // [2:2] is the sub-list for method output_type 357 | 2, // [2:2] is the sub-list for method input_type 358 | 2, // [2:2] is the sub-list for extension type_name 359 | 2, // [2:2] is the sub-list for extension extendee 360 | 0, // [0:2] is the sub-list for field type_name 361 | } 362 | 363 | func init() { file_index_proto_init() } 364 | func file_index_proto_init() { 365 | if File_index_proto != nil { 366 | return 367 | } 368 | if !protoimpl.UnsafeEnabled { 369 | file_index_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 370 | switch v := v.(*Index); i { 371 | case 0: 372 | return &v.state 373 | case 1: 374 | return &v.sizeCache 375 | case 2: 376 | return &v.unknownFields 377 | default: 378 | return nil 379 | } 380 | } 381 | file_index_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 382 | switch v := v.(*LibraryChunk); i { 383 | case 0: 384 | return &v.state 385 | case 1: 386 | return &v.sizeCache 387 | case 2: 388 | return &v.unknownFields 389 | default: 390 | return nil 391 | } 392 | } 393 | file_index_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 394 | switch v := v.(*Index_File); i { 395 | case 0: 396 | return &v.state 397 | case 1: 398 | return &v.sizeCache 399 | case 2: 400 | return &v.unknownFields 401 | default: 402 | return nil 403 | } 404 | } 405 | file_index_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 406 | switch v := v.(*Index_Library); i { 407 | case 0: 408 | return &v.state 409 | case 1: 410 | return &v.sizeCache 411 | case 2: 412 | return &v.unknownFields 413 | default: 414 | return nil 415 | } 416 | } 417 | } 418 | file_index_proto_msgTypes[2].OneofWrappers = []interface{}{} 419 | type x struct{} 420 | out := protoimpl.TypeBuilder{ 421 | File: protoimpl.DescBuilder{ 422 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 423 | RawDescriptor: file_index_proto_rawDesc, 424 | NumEnums: 0, 425 | NumMessages: 4, 426 | NumExtensions: 0, 427 | NumServices: 0, 428 | }, 429 | GoTypes: file_index_proto_goTypes, 430 | DependencyIndexes: file_index_proto_depIdxs, 431 | MessageInfos: file_index_proto_msgTypes, 432 | }.Build() 433 | File_index_proto = out.File 434 | file_index_proto_rawDesc = nil 435 | file_index_proto_goTypes = nil 436 | file_index_proto_depIdxs = nil 437 | } 438 | -------------------------------------------------------------------------------- /fonts/block.flf: -------------------------------------------------------------------------------- 1 | flf2a$ 8 6 27 0 10 0 576 2 | Block by Glenn Chappell 4/93 -- straight version of Lean 3 | Includes ISO Latin-1 4 | figlet release 2.1 -- 12 Aug 1994 5 | Permission is hereby given to modify this font, as long as the 6 | modifier's name is placed on a comment line. 7 | 8 | Modified by Paul Burton 12/96 to include new parameter 9 | supported by FIGlet and FIGWin. May also be slightly modified for better use 10 | of new full-width/kern/smush alternatives, but default output is NOT changed. 11 | 12 | $ $@ 13 | $ $@ 14 | $ $@ 15 | $ $@ 16 | $ $@ 17 | $ $@ 18 | $ $@ 19 | $ $@@ 20 | $$@ 21 | _| $@ 22 | _| $@ 23 | _| $@ 24 | $$@ 25 | _| $@ 26 | $$@ 27 | @@ 28 | _| _| $@ 29 | _| _| $@ 30 | $$@ 31 | $$ @ 32 | $$ @ 33 | $$ @ 34 | @ 35 | @@ 36 | $$ @ 37 | _| _| $@ 38 | _|_|_|_|_| $@ 39 | _| _| $@ 40 | _|_|_|_|_| $@ 41 | _| _| $@ 42 | $$ @ 43 | @@ 44 | $$ @ 45 | _| $@ 46 | _|_|_| $@ 47 | _|_| $@ 48 | _|_| $@ 49 | _|_|_| $@ 50 | _| $@ 51 | $$ @@ 52 | $$@ 53 | _|_| _| $@ 54 | _|_| _| $@ 55 | _| $@ 56 | _| _|_| $@ 57 | _| _|_| $@ 58 | $$@ 59 | @@ 60 | $$ @ 61 | _| $ @ 62 | _| _| $@ 63 | _|_| _| $@ 64 | _| _| $@ 65 | _|_| _| $@ 66 | $$@ 67 | @@ 68 | _| $@ 69 | _| $@ 70 | $$ @ 71 | $$ @ 72 | $$ @ 73 | $$ @ 74 | @ 75 | @@ 76 | _| $@ 77 | _| $@ 78 | _| $ @ 79 | _| $ @ 80 | _| $ @ 81 | _| $@ 82 | _| $@ 83 | $$@@ 84 | _| $@ 85 | _| $@ 86 | _| $@ 87 | _| $@ 88 | _| $@ 89 | _| $@ 90 | _| $@ 91 | $$ @@ 92 | $$@ 93 | _| _| _| $@ 94 | _|_|_| $@ 95 | _|_|_|_|_| $@ 96 | _|_|_| $@ 97 | _| _| _| $@ 98 | $$@ 99 | @@ 100 | $$ @ 101 | _| $ @ 102 | _| $@ 103 | _|_|_|_|_| $@ 104 | _| $@ 105 | _| $ @ 106 | $$ @ 107 | @@ 108 | @ 109 | @ 110 | @ 111 | @ 112 | $$@ 113 | _| $@ 114 | _| $@ 115 | $$ @@ 116 | @ 117 | @ 118 | $$@ 119 | _|_|_|_|_| $@ 120 | $$@ 121 | @ 122 | @ 123 | @@ 124 | @ 125 | @ 126 | @ 127 | @ 128 | $$@ 129 | _| $@ 130 | $$@ 131 | @@ 132 | $$@ 133 | _| $@ 134 | _| $@ 135 | _| $ @ 136 | _| $ @ 137 | _| $ @ 138 | $$ @ 139 | @@ 140 | $$ @ 141 | _| $@ 142 | _| _| $@ 143 | _| _| $@ 144 | _| _| $@ 145 | _| $@ 146 | $$ @ 147 | @@ 148 | $$@ 149 | _| $@ 150 | _|_| $@ 151 | _| $@ 152 | _| $@ 153 | _| $@ 154 | $$@ 155 | @@ 156 | $$ @ 157 | _|_| $@ 158 | _| _| $@ 159 | _| $@ 160 | _| $@ 161 | _|_|_|_| $@ 162 | $$@ 163 | @@ 164 | $$ @ 165 | _|_|_| $@ 166 | _| $@ 167 | _|_| $@ 168 | _| $@ 169 | _|_|_| $@ 170 | $$ @ 171 | @@ 172 | $$ @ 173 | _| _| $ @ 174 | _| _| $@ 175 | _|_|_|_| $@ 176 | _| $@ 177 | _| $ @ 178 | $$ @ 179 | @@ 180 | $$@ 181 | _|_|_|_| $@ 182 | _| $@ 183 | _|_|_| $@ 184 | _| $@ 185 | _|_|_| $@ 186 | $$ @ 187 | @@ 188 | $$@ 189 | _|_|_| $@ 190 | _| $@ 191 | _|_|_| $@ 192 | _| _| $@ 193 | _|_| $@ 194 | $$ @ 195 | @@ 196 | $$@ 197 | _|_|_|_|_| $@ 198 | _| $@ 199 | _| $@ 200 | _| $ @ 201 | _| $ @ 202 | $$ @ 203 | @@ 204 | $$ @ 205 | _|_| $@ 206 | _| _| $@ 207 | _|_| $@ 208 | _| _| $@ 209 | _|_| $@ 210 | $$ @ 211 | @@ 212 | $$ @ 213 | _|_| $@ 214 | _| _| $@ 215 | _|_|_| $@ 216 | _| $@ 217 | _|_|_| $@ 218 | $$ @ 219 | @@ 220 | @ 221 | $$@ 222 | _| $@ 223 | $$@ 224 | $$@ 225 | _| $@ 226 | $$@ 227 | @@ 228 | @ 229 | $$@ 230 | _| $@ 231 | $$@ 232 | $$@ 233 | _| $@ 234 | _| $@ 235 | $$ @@ 236 | $$@ 237 | _| $@ 238 | _| $@ 239 | _| $ @ 240 | _| $@ 241 | _| $@ 242 | $$@ 243 | @@ 244 | @ 245 | $$@ 246 | _|_|_|_|_| $@ 247 | $$@ 248 | _|_|_|_|_| $@ 249 | $$@ 250 | @ 251 | @@ 252 | $$ @ 253 | _| $ @ 254 | _| $@ 255 | _| $@ 256 | _| $@ 257 | _| $ @ 258 | $$ @ 259 | @@ 260 | $$ @ 261 | _|_| $@ 262 | _| $@ 263 | _|_| $@ 264 | $$ @ 265 | _| $ @ 266 | $$ @ 267 | @@ 268 | $$ @ 269 | _|_|_|_|_| $@ 270 | _| _| $@ 271 | _| _|_|_| _| $@ 272 | _| _| _| _| $@ 273 | _| _|_|_|_| $@ 274 | _| $@ 275 | _|_|_|_|_|_| $@@ 276 | $$ @ 277 | _|_| $@ 278 | _| _| $@ 279 | _|_|_|_| $@ 280 | _| _| $@ 281 | _| _| $@ 282 | $$@ 283 | @@ 284 | $$ @ 285 | _|_|_| $@ 286 | _| _| $@ 287 | _|_|_| $@ 288 | _| _| $@ 289 | _|_|_| $@ 290 | $$ @ 291 | @@ 292 | $$@ 293 | _|_|_| $@ 294 | _| $@ 295 | _| $ @ 296 | _| $@ 297 | _|_|_| $@ 298 | $$@ 299 | @@ 300 | $$ @ 301 | _|_|_| $@ 302 | _| _| $@ 303 | _| _| $@ 304 | _| _| $@ 305 | _|_|_| $@ 306 | $$ @ 307 | @@ 308 | $$@ 309 | _|_|_|_| $@ 310 | _| $@ 311 | _|_|_| $ @ 312 | _| $@ 313 | _|_|_|_| $@ 314 | $$@ 315 | @@ 316 | $$@ 317 | _|_|_|_| $@ 318 | _| $@ 319 | _|_|_| $ @ 320 | _| $ @ 321 | _| $ @ 322 | $$ @ 323 | @@ 324 | $$@ 325 | _|_|_| $@ 326 | _| $@ 327 | _| _|_| $@ 328 | _| _| $@ 329 | _|_|_| $@ 330 | $$@ 331 | @@ 332 | $$@ 333 | _| _| $@ 334 | _| _| $@ 335 | _|_|_|_| $@ 336 | _| _| $@ 337 | _| _| $@ 338 | $$@ 339 | @@ 340 | $$@ 341 | _|_|_| $@ 342 | _| $@ 343 | _| $ @ 344 | _| $@ 345 | _|_|_| $@ 346 | $$@ 347 | @@ 348 | $$@ 349 | _| $@ 350 | _| $@ 351 | _| $@ 352 | _| _| $@ 353 | _|_| $@ 354 | $$ @ 355 | @@ 356 | $$@ 357 | _| _| $@ 358 | _| _| $@ 359 | _|_| $ @ 360 | _| _| $@ 361 | _| _| $@ 362 | $$@ 363 | @@ 364 | $$ @ 365 | _| $ @ 366 | _| $ @ 367 | _| $ @ 368 | _| $@ 369 | _|_|_|_| $@ 370 | $$@ 371 | @@ 372 | $$@ 373 | _| _| $@ 374 | _|_| _|_| $@ 375 | _| _| _| $@ 376 | _| _| $@ 377 | _| _| $@ 378 | $$@ 379 | @@ 380 | $$@ 381 | _| _| $@ 382 | _|_| _| $@ 383 | _| _| _| $@ 384 | _| _|_| $@ 385 | _| _| $@ 386 | $$@ 387 | @@ 388 | $$ @ 389 | _|_| $@ 390 | _| _| $@ 391 | _| _| $@ 392 | _| _| $@ 393 | _|_| $@ 394 | $$ @ 395 | @@ 396 | $$ @ 397 | _|_|_| $@ 398 | _| _| $@ 399 | _|_|_| $@ 400 | _| $ @ 401 | _| $ @ 402 | $$ @ 403 | @@ 404 | $$ @ 405 | _|_| $ @ 406 | _| _| $ @ 407 | _| _|_| $ @ 408 | _| _| $@ 409 | _|_| _| $@ 410 | $$@ 411 | @@ 412 | $$ @ 413 | _|_|_| $@ 414 | _| _| $@ 415 | _|_|_| $@ 416 | _| _| $@ 417 | _| _| $@ 418 | $$@ 419 | @@ 420 | $$@ 421 | _|_|_| $@ 422 | _| $@ 423 | _|_| $@ 424 | _| $@ 425 | _|_|_| $@ 426 | $$ @ 427 | @@ 428 | $$@ 429 | _|_|_|_|_| $@ 430 | _| $@ 431 | _| $ @ 432 | _| $ @ 433 | _| $ @ 434 | $$ @ 435 | @@ 436 | $$@ 437 | _| _| $@ 438 | _| _| $@ 439 | _| _| $@ 440 | _| _| $@ 441 | _|_| $@ 442 | $$ @ 443 | @@ 444 | $$@ 445 | _| _| $@ 446 | _| _| $@ 447 | _| _| $@ 448 | _| _| $@ 449 | _| $ @ 450 | $$ @ 451 | @@ 452 | $$@ 453 | _| _| $@ 454 | _| _| $@ 455 | _| _| _| $@ 456 | _| _| _| $@ 457 | _| _| $ @ 458 | $$ @ 459 | @@ 460 | $$@ 461 | _| _| $@ 462 | _| _| $@ 463 | _| $ @ 464 | _| _| $@ 465 | _| _| $@ 466 | $$@ 467 | @@ 468 | $$@ 469 | _| _| $@ 470 | _| _| $@ 471 | _| $ @ 472 | _| $ @ 473 | _| $ @ 474 | $$ @ 475 | @@ 476 | $$@ 477 | _|_|_|_|_| $@ 478 | _| $@ 479 | _| $ @ 480 | _| $@ 481 | _|_|_|_|_| $@ 482 | $$@ 483 | @@ 484 | _|_| $@ 485 | _| $@ 486 | _| $ @ 487 | _| $ @ 488 | _| $ @ 489 | _| $@ 490 | _|_| $@ 491 | $$@@ 492 | $$ @ 493 | _| $ @ 494 | _| $ @ 495 | _| $ @ 496 | _| $@ 497 | _| $@ 498 | $$@ 499 | @@ 500 | _|_| $@ 501 | _| $@ 502 | _| $@ 503 | _| $@ 504 | _| $@ 505 | _| $@ 506 | _|_| $@ 507 | $$@@ 508 | _| $@ 509 | _| _| $@ 510 | $$@ 511 | $$ @ 512 | $$ @ 513 | $$ @ 514 | @ 515 | @@ 516 | @ 517 | @ 518 | $$ @ 519 | $$ @ 520 | $$ @ 521 | $$ @ 522 | $$@ 523 | _|_|_|_|_| $@@ 524 | _| $@ 525 | _| $@ 526 | $$@ 527 | $$ @ 528 | $$ @ 529 | $$ @ 530 | @ 531 | @@ 532 | @ 533 | $$@ 534 | _|_|_| $@ 535 | _| _| $@ 536 | _| _| $@ 537 | _|_|_| $@ 538 | $$@ 539 | @@ 540 | $$ @ 541 | _| $ @ 542 | _|_|_| $@ 543 | _| _| $@ 544 | _| _| $@ 545 | _|_|_| $@ 546 | $$ @ 547 | @@ 548 | @ 549 | $$@ 550 | _|_|_| $@ 551 | _| $@ 552 | _| $@ 553 | _|_|_| $@ 554 | $$@ 555 | @@ 556 | $$@ 557 | _| $@ 558 | _|_|_| $@ 559 | _| _| $@ 560 | _| _| $@ 561 | _|_|_| $@ 562 | $$@ 563 | @@ 564 | @ 565 | $$ @ 566 | _|_| $@ 567 | _|_|_|_| $@ 568 | _| $@ 569 | _|_|_| $@ 570 | $$@ 571 | @@ 572 | $$@ 573 | _|_| $@ 574 | _| $@ 575 | _|_|_|_| $@ 576 | _| $@ 577 | _| $ @ 578 | $$ @ 579 | @@ 580 | @ 581 | $$@ 582 | _|_|_| $@ 583 | _| _| $@ 584 | _| _| $@ 585 | _|_|_| $@ 586 | _| $@ 587 | _|_| $@@ 588 | $$ @ 589 | _| $ @ 590 | _|_|_| $@ 591 | _| _| $@ 592 | _| _| $@ 593 | _| _| $@ 594 | $$@ 595 | @@ 596 | $$@ 597 | _| $@ 598 | $$@ 599 | _| $@ 600 | _| $@ 601 | _| $@ 602 | $$@ 603 | @@ 604 | $$@ 605 | _| $@ 606 | $$@ 607 | _| $@ 608 | _| $@ 609 | _| $@ 610 | _| $@ 611 | _| $@@ 612 | $$ @ 613 | _| $ @ 614 | _| _| $ @ 615 | _|_| $ @ 616 | _| _| $@ 617 | _| _| $@ 618 | $$@ 619 | @@ 620 | $$@ 621 | _| $@ 622 | _| $@ 623 | _| $@ 624 | _| $@ 625 | _| $@ 626 | $$@ 627 | @@ 628 | @ 629 | $$ @ 630 | _|_|_| _|_| $@ 631 | _| _| _| $@ 632 | _| _| _| $@ 633 | _| _| _| $@ 634 | $$@ 635 | @@ 636 | @ 637 | $$ @ 638 | _|_|_| $@ 639 | _| _| $@ 640 | _| _| $@ 641 | _| _| $@ 642 | $$@ 643 | @@ 644 | @ 645 | $$ @ 646 | _|_| $@ 647 | _| _| $@ 648 | _| _| $@ 649 | _|_| $@ 650 | $$ @ 651 | @@ 652 | @ 653 | $$ @ 654 | _|_|_| $@ 655 | _| _| $@ 656 | _| _| $@ 657 | _|_|_| $@ 658 | _| $ @ 659 | _| $ @@ 660 | @ 661 | $$@ 662 | _|_|_| $@ 663 | _| _| $@ 664 | _| _| $@ 665 | _|_|_| $@ 666 | _| $@ 667 | _| $@@ 668 | @ 669 | $$@ 670 | _| _|_| $@ 671 | _|_| $@ 672 | _| $ @ 673 | _| $ @ 674 | $$ @ 675 | @@ 676 | @ 677 | $$@ 678 | _|_|_| $@ 679 | _|_| $@ 680 | _|_| $@ 681 | _|_|_| $@ 682 | $$ @ 683 | @@ 684 | $$ @ 685 | _| $@ 686 | _|_|_|_| @ 687 | _| $@ 688 | _| $@ 689 | _|_| $@ 690 | $$@ 691 | @@ 692 | @ 693 | $$@ 694 | _| _| $@ 695 | _| _| $@ 696 | _| _| $@ 697 | _|_|_| $@ 698 | $$@ 699 | @@ 700 | @ 701 | $$@ 702 | _| _| $@ 703 | _| _| $@ 704 | _| _| $@ 705 | _| $ @ 706 | $$ @ 707 | @@ 708 | @ 709 | $$@ 710 | _| _| _| $@ 711 | _| _| _| $@ 712 | _| _| _| _| $@ 713 | _| _| $ @ 714 | $$ @ 715 | @@ 716 | @ 717 | $$@ 718 | _| _| $@ 719 | _|_| $@ 720 | _| _| $@ 721 | _| _| $@ 722 | $$@ 723 | @@ 724 | @ 725 | $$@ 726 | _| _| $@ 727 | _| _| $@ 728 | _| _| $@ 729 | _|_|_| $@ 730 | _| $@ 731 | _|_| $@@ 732 | @ 733 | $$@ 734 | _|_|_|_| $@ 735 | _| $@ 736 | _| $@ 737 | _|_|_|_| $@ 738 | $$@ 739 | @@ 740 | _| $@ 741 | _| $@ 742 | _| $@ 743 | _| $ @ 744 | _| $@ 745 | _| $@ 746 | _| $@ 747 | $$@@ 748 | _| $@ 749 | _| $@ 750 | _| $@ 751 | _| $@ 752 | _| $@ 753 | _| $@ 754 | _| $@ 755 | _| $@@ 756 | _| $ @ 757 | _| $@ 758 | _| $@ 759 | _| $@ 760 | _| $@ 761 | _| $@ 762 | _| $ @ 763 | $$ @@ 764 | _| _| $@ 765 | _| _| $@ 766 | $$ @ 767 | $$ @ 768 | $$ @ 769 | $$ @ 770 | @ 771 | @@ 772 | _| _| $@ 773 | $$@ 774 | _|_| $@ 775 | _| _| $@ 776 | _|_|_|_| $@ 777 | _| _| $@ 778 | $$@ 779 | @@ 780 | _| _| $@ 781 | $$@ 782 | _|_| $@ 783 | _| _| $@ 784 | _| _| $@ 785 | _|_| $@ 786 | $$ @ 787 | @@ 788 | _| _| $@ 789 | $$@ 790 | _| _| $@ 791 | _| _| $@ 792 | _| _| $@ 793 | _|_| $@ 794 | $$ @ 795 | @@ 796 | _| _| $@ 797 | $$@ 798 | _|_|_| $@ 799 | _| _| $@ 800 | _| _| $@ 801 | _|_|_| $@ 802 | $$@ 803 | @@ 804 | _| _| $@ 805 | $$@ 806 | _|_| $@ 807 | _| _| $@ 808 | _| _| $@ 809 | _|_| $@ 810 | $$ @ 811 | @@ 812 | _| _| $@ 813 | $$@ 814 | _| _| $@ 815 | _| _| $@ 816 | _| _| $@ 817 | _|_|_| $@ 818 | $$@ 819 | @@ 820 | $$ @ 821 | _|_| $@ 822 | _| _| $@ 823 | _| _| $@ 824 | _| _| $@ 825 | _| _| $@ 826 | _| $ @ 827 | $$ @@ 828 | 160 NO-BREAK SPACE 829 | $ $@ 830 | $ $@ 831 | $ $@ 832 | $ $@ 833 | $ $@ 834 | $ $@ 835 | $ $@ 836 | $ $@@ 837 | 161 INVERTED EXCLAMATION MARK 838 | $$@ 839 | _| $@ 840 | $$@ 841 | _| $@ 842 | _| $@ 843 | _| $@ 844 | $$@ 845 | @@ 846 | 162 CENT SIGN 847 | $$ @ 848 | _| $@ 849 | _|_|_| $@ 850 | _| _| $@ 851 | _| _| $@ 852 | _|_|_| $@ 853 | _| $@ 854 | $$ @@ 855 | 163 POUND SIGN 856 | $$ @ 857 | _|_| $ @ 858 | _| _| $ @ 859 | _|_|_| $ @ 860 | _| $@ 861 | _|_|_| _| $@ 862 | _|_| _|_| $@ 863 | @@ 864 | 164 CURRENCY SIGN 865 | $$@ 866 | _| _| $@ 867 | _|_|_|_| $@ 868 | _| _| $ @ 869 | _| _| $ @ 870 | _|_|_|_| $@ 871 | _| _| $@ 872 | $$@@ 873 | 165 YEN SIGN 874 | $$@ 875 | _| _| $@ 876 | _| _| $@ 877 | _|_|_|_|_| $@ 878 | _| $@ 879 | _|_|_|_|_| $@ 880 | _| $@ 881 | $$ @@ 882 | 166 BROKEN BAR 883 | _| $@ 884 | _| $@ 885 | _| $@ 886 | $$@ 887 | $$@ 888 | _| $@ 889 | _| $@ 890 | _| $@@ 891 | 167 SECTION SIGN 892 | _|_| $@ 893 | _| $@ 894 | _| $@ 895 | _| _| $@ 896 | _| $@ 897 | _| $@ 898 | _|_| $@ 899 | $$ @@ 900 | 168 DIAERESIS 901 | _| _| $@ 902 | $$@ 903 | $ $ @ 904 | $ $ @ 905 | $ $ @ 906 | $ $ @ 907 | @ 908 | @@ 909 | 169 COPYRIGHT SIGN 910 | _|_|_|_| $ @ 911 | _| _| $@ 912 | _| _|_|_| _| $@ 913 | _| _| _| $@ 914 | _| _| _| $@ 915 | _| _|_|_| _| $@ 916 | _| _| $@ 917 | _|_|_|_| $ @@ 918 | 170 FEMININE ORDINAL INDICATOR 919 | $$@ 920 | _|_|_| $@ 921 | _| _| $@ 922 | _|_|_| $@ 923 | $$@ 924 | _|_|_|_| $@ 925 | @ 926 | @@ 927 | 171 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK 928 | $$@ 929 | _| _| $@ 930 | _| _| $@ 931 | _| _| $ @ 932 | _| _| $@ 933 | _| _| $@ 934 | $$@ 935 | @@ 936 | 172 NOT SIGN 937 | @ 938 | @ 939 | $$@ 940 | _|_|_|_|_| $@ 941 | _| $@ 942 | $$@ 943 | @ 944 | @@ 945 | 173 SOFT HYPHEN 946 | @ 947 | @ 948 | $$@ 949 | _|_|_|_| $@ 950 | $$@ 951 | $$ @ 952 | @ 953 | @@ 954 | 174 REGISTERED SIGN 955 | _|_|_|_| $ @ 956 | _| _| $@ 957 | _| _|_|_| _| $@ 958 | _| _| _| _| $@ 959 | _| _|_|_| _| $@ 960 | _| _| _| _| $@ 961 | _| _| $@ 962 | _|_|_|_| $ @@ 963 | 175 MACRON 964 | _|_|_|_|_| $@ 965 | $$@ 966 | $$ @ 967 | $$ @ 968 | $$ @ 969 | $$ @ 970 | @ 971 | @@ 972 | 176 DEGREE SIGN 973 | _| $@ 974 | _| _| $@ 975 | _| $@ 976 | $$ @ 977 | $$ @ 978 | $$ @ 979 | @ 980 | @@ 981 | 177 PLUS-MINUS SIGN 982 | $$ @ 983 | _| $ @ 984 | _| $@ 985 | _|_|_|_|_| $@ 986 | _| $@ 987 | _|_|_|_|_| $@ 988 | $$@ 989 | @@ 990 | 178 SUPERSCRIPT TWO 991 | $$ @ 992 | _|_| $@ 993 | _| $@ 994 | _| $@ 995 | _|_|_| $@ 996 | $$@ 997 | @ 998 | @@ 999 | 179 SUPERSCRIPT THREE 1000 | $$@ 1001 | _|_|_| $@ 1002 | _| $@ 1003 | _| $@ 1004 | _|_| $@ 1005 | $$ @ 1006 | @ 1007 | @@ 1008 | 180 ACUTE ACCENT 1009 | _| $@ 1010 | _| $@ 1011 | $$ @ 1012 | $$ @ 1013 | $$ @ 1014 | $$ @ 1015 | @ 1016 | @@ 1017 | 181 MICRO SIGN 1018 | @ 1019 | $$@ 1020 | _| _| $@ 1021 | _| _| $@ 1022 | _| _| $@ 1023 | _|_|_|_| $@ 1024 | _| $@ 1025 | _| $ @@ 1026 | 182 PILCROW SIGN 1027 | $$@ 1028 | _|_|_|_| $@ 1029 | _|_|_| _| $@ 1030 | _|_| _| $@ 1031 | _| _| $@ 1032 | _| _| $@ 1033 | $$@ 1034 | @@ 1035 | 183 MIDDLE DOT 1036 | @ 1037 | @ 1038 | $$@ 1039 | _| $@ 1040 | $$@ 1041 | $$ @ 1042 | @ 1043 | @@ 1044 | 184 CEDILLA 1045 | @ 1046 | @ 1047 | @ 1048 | @ 1049 | @ 1050 | $$@ 1051 | _| $@ 1052 | _|_| $@@ 1053 | 185 SUPERSCRIPT ONE 1054 | $$@ 1055 | _| $@ 1056 | _|_| $@ 1057 | _| $@ 1058 | _| $@ 1059 | $$@ 1060 | @ 1061 | @@ 1062 | 186 MASCULINE ORDINAL INDICATOR 1063 | $$ @ 1064 | _|_| $@ 1065 | _| _| $@ 1066 | _|_| $@ 1067 | $$@ 1068 | _|_|_|_| $@ 1069 | @ 1070 | @@ 1071 | 187 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK 1072 | $$ @ 1073 | _| _| $ @ 1074 | _| _| $@ 1075 | _| _| $@ 1076 | _| _| $@ 1077 | _| _| $ @ 1078 | $$ @ 1079 | @@ 1080 | 188 VULGAR FRACTION ONE QUARTER 1081 | $$ @ 1082 | _| _| $ @ 1083 | _|_| _| _| _| $ @ 1084 | _| _| _| _| $@ 1085 | _| _| _|_|_|_| $@ 1086 | _| _| $@ 1087 | $$ @ 1088 | @@ 1089 | 189 VULGAR FRACTION ONE HALF 1090 | $$ @ 1091 | _| _| $ @ 1092 | _|_| _| _|_| $@ 1093 | _| _| _| $@ 1094 | _| _| _| $@ 1095 | _| _|_|_| $@ 1096 | $$@ 1097 | @@ 1098 | 190 VULGAR FRACTION THREE QUARTERS 1099 | $$ @ 1100 | _|_|_| _| $ @ 1101 | _| _| _| _| $ @ 1102 | _| _| _| _| $@ 1103 | _|_| _| _|_|_|_| $@ 1104 | _| _| $@ 1105 | $$ @ 1106 | @@ 1107 | 191 INVERTED QUESTION MARK 1108 | $$@ 1109 | _| $@ 1110 | $$@ 1111 | _|_| $@ 1112 | _| $@ 1113 | _|_| $@ 1114 | $$@ 1115 | @@ 1116 | 192 LATIN CAPITAL LETTER A WITH GRAVE 1117 | _| $ @ 1118 | _| $ @ 1119 | _|_| $@ 1120 | _| _| $@ 1121 | _|_|_|_| $@ 1122 | _| _| $@ 1123 | $$@ 1124 | @@ 1125 | 193 LATIN CAPITAL LETTER A WITH ACUTE 1126 | _| $ @ 1127 | _| $ @ 1128 | _|_| $@ 1129 | _| _| $@ 1130 | _|_|_|_| $@ 1131 | _| _| $@ 1132 | $$@ 1133 | @@ 1134 | 194 LATIN CAPITAL LETTER A WITH CIRCUMFLEX 1135 | _|_| $@ 1136 | _| _| $@ 1137 | $$@ 1138 | _|_| $@ 1139 | _|_|_|_| $@ 1140 | _| _| $@ 1141 | $$@ 1142 | @@ 1143 | 195 LATIN CAPITAL LETTER A WITH TILDE 1144 | _| _| $@ 1145 | _| _| $@ 1146 | $$ @ 1147 | _|_| $@ 1148 | _|_|_|_| $@ 1149 | _| _| $@ 1150 | $$@ 1151 | @@ 1152 | 196 LATIN CAPITAL LETTER A WITH DIAERESIS 1153 | _| _| $@ 1154 | $$@ 1155 | _|_| $@ 1156 | _| _| $@ 1157 | _|_|_|_| $@ 1158 | _| _| $@ 1159 | $$@ 1160 | @@ 1161 | 197 LATIN CAPITAL LETTER A WITH RING ABOVE 1162 | _|_| $@ 1163 | _| _| $@ 1164 | _|_| $@ 1165 | _| _| $@ 1166 | _|_|_|_| $@ 1167 | _| _| $@ 1168 | $$@ 1169 | @@ 1170 | 198 LATIN CAPITAL LETTER AE 1171 | $$@ 1172 | _|_|_|_|_|_| $@ 1173 | _| _| $@ 1174 | _|_|_|_|_|_| $ @ 1175 | _| _| $@ 1176 | _| _|_|_|_| $@ 1177 | $$@ 1178 | @@ 1179 | 199 LATIN CAPITAL LETTER C WITH CEDILLA 1180 | $$@ 1181 | _|_|_| $@ 1182 | _| $@ 1183 | _| $ @ 1184 | _| $@ 1185 | _|_|_| $@ 1186 | _| $@ 1187 | _|_| $ @@ 1188 | 200 LATIN CAPITAL LETTER E WITH GRAVE 1189 | _| $ @ 1190 | _| $@ 1191 | _|_|_|_| $@ 1192 | _|_|_| $ @ 1193 | _| $@ 1194 | _|_|_|_| $@ 1195 | $$@ 1196 | @@ 1197 | 201 LATIN CAPITAL LETTER E WITH ACUTE 1198 | _| $ @ 1199 | _| $@ 1200 | _|_|_|_| $@ 1201 | _|_|_| $ @ 1202 | _| $@ 1203 | _|_|_|_| $@ 1204 | $$@ 1205 | @@ 1206 | 202 LATIN CAPITAL LETTER E WITH CIRCUMFLEX 1207 | _|_| $@ 1208 | _| _| $@ 1209 | _|_|_|_| $@ 1210 | _|_|_| $ @ 1211 | _| $@ 1212 | _|_|_|_| $@ 1213 | $$@ 1214 | @@ 1215 | 203 LATIN CAPITAL LETTER E WITH DIAERESIS 1216 | _| _| $@ 1217 | $$@ 1218 | _|_|_|_| $@ 1219 | _|_|_| $ @ 1220 | _| $@ 1221 | _|_|_|_| $@ 1222 | $$@ 1223 | @@ 1224 | 204 LATIN CAPITAL LETTER I WITH GRAVE 1225 | _| $ @ 1226 | _| $@ 1227 | _|_|_| $@ 1228 | _| $@ 1229 | _| $@ 1230 | _|_|_| $@ 1231 | $$@ 1232 | @@ 1233 | 205 LATIN CAPITAL LETTER I WITH ACUTE 1234 | _| $@ 1235 | _| $@ 1236 | _|_|_| $@ 1237 | _| $@ 1238 | _| $@ 1239 | _|_|_| $@ 1240 | $$@ 1241 | @@ 1242 | 206 LATIN CAPITAL LETTER I WITH CIRCUMFLEX 1243 | _| $@ 1244 | _| _| $@ 1245 | _|_|_| $@ 1246 | _| $@ 1247 | _| $@ 1248 | _|_|_| $@ 1249 | $$@ 1250 | @@ 1251 | 207 LATIN CAPITAL LETTER I WITH DIAERESIS 1252 | _| _| $@ 1253 | $$@ 1254 | _|_|_| $@ 1255 | _| $@ 1256 | _| $@ 1257 | _|_|_| $@ 1258 | $$@ 1259 | @@ 1260 | 208 LATIN CAPITAL LETTER ETH 1261 | $$ @ 1262 | _|_|_| $@ 1263 | _| _| $@ 1264 | _|_|_| _| $@ 1265 | _| _| $@ 1266 | _|_|_| $@ 1267 | $$ @ 1268 | @@ 1269 | 209 LATIN CAPITAL LETTER N WITH TILDE 1270 | _| _| $@ 1271 | _| _| $@ 1272 | _| _| $@ 1273 | _|_| _| $@ 1274 | _| _|_| $@ 1275 | _| _| $@ 1276 | $$@ 1277 | @@ 1278 | 210 LATIN CAPITAL LETTER O WITH GRAVE 1279 | _| $ @ 1280 | _| $ @ 1281 | _|_| $@ 1282 | _| _| $@ 1283 | _| _| $@ 1284 | _|_| $@ 1285 | $$ @ 1286 | @@ 1287 | 211 LATIN CAPITAL LETTER O WITH ACUTE 1288 | _| $ @ 1289 | _| $ @ 1290 | _|_| $@ 1291 | _| _| $@ 1292 | _| _| $@ 1293 | _|_| $@ 1294 | $$ @ 1295 | @@ 1296 | 212 LATIN CAPITAL LETTER O WITH CIRCUMFLEX 1297 | _|_| $@ 1298 | _| _| $@ 1299 | _|_| $@ 1300 | _| _| $@ 1301 | _| _| $@ 1302 | _|_| $@ 1303 | $$ @ 1304 | @@ 1305 | 213 LATIN CAPITAL LETTER O WITH TILDE 1306 | _| _| $@ 1307 | _| _| $@ 1308 | _|_| $@ 1309 | _| _| $@ 1310 | _| _| $@ 1311 | _|_| $@ 1312 | $$ @ 1313 | @@ 1314 | 214 LATIN CAPITAL LETTER O WITH DIAERESIS 1315 | _| _| $@ 1316 | $$@ 1317 | _|_| $@ 1318 | _| _| $@ 1319 | _| _| $@ 1320 | _|_| $@ 1321 | $$ @ 1322 | @@ 1323 | 215 MULTIPLICATION SIGN 1324 | @ 1325 | $$@ 1326 | _| _| $@ 1327 | _| $@ 1328 | _| _| $@ 1329 | $$@ 1330 | @ 1331 | @@ 1332 | 216 LATIN CAPITAL LETTER O WITH STROKE 1333 | $$@ 1334 | _|_|_|_| $@ 1335 | _| _|_| $@ 1336 | _| _| _| $@ 1337 | _|_| _| $@ 1338 | _|_|_|_| $@ 1339 | $$ @ 1340 | @@ 1341 | 217 LATIN CAPITAL LETTER U WITH GRAVE 1342 | _| $ @ 1343 | _| $ @ 1344 | $$@ 1345 | _| _| $@ 1346 | _| _| $@ 1347 | _|_| $@ 1348 | $$ @ 1349 | @@ 1350 | 218 LATIN CAPITAL LETTER U WITH ACUTE 1351 | _| $ @ 1352 | _| $ @ 1353 | $$@ 1354 | _| _| $@ 1355 | _| _| $@ 1356 | _|_| $@ 1357 | $$ @ 1358 | @@ 1359 | 219 LATIN CAPITAL LETTER U WITH CIRCUMFLEX 1360 | _|_| $@ 1361 | _| _| $@ 1362 | $$@ 1363 | _| _| $@ 1364 | _| _| $@ 1365 | _|_| $@ 1366 | $$ @ 1367 | @@ 1368 | 220 LATIN CAPITAL LETTER U WITH DIAERESIS 1369 | _| _| $@ 1370 | $$@ 1371 | _| _| $@ 1372 | _| _| $@ 1373 | _| _| $@ 1374 | _|_| $@ 1375 | $$ @ 1376 | @@ 1377 | 221 LATIN CAPITAL LETTER Y WITH ACUTE 1378 | _| $ @ 1379 | _| $@ 1380 | _| _| $@ 1381 | _| _| $@ 1382 | _| $ @ 1383 | _| $ @ 1384 | $$ @ 1385 | @@ 1386 | 222 LATIN CAPITAL LETTER THORN 1387 | $$ @ 1388 | _| $ @ 1389 | _|_|_| $@ 1390 | _| _| $@ 1391 | _|_|_| $@ 1392 | _| $ @ 1393 | $$ @ 1394 | @@ 1395 | 223 LATIN SMALL LETTER SHARP S 1396 | $$ @ 1397 | _|_| $@ 1398 | _| _| $@ 1399 | _| _| $@ 1400 | _| _| $@ 1401 | _| _| $@ 1402 | _| $ @ 1403 | $$ @@ 1404 | 224 LATIN SMALL LETTER A WITH GRAVE 1405 | _| $ @ 1406 | _| $ @ 1407 | $$@ 1408 | _|_|_| $@ 1409 | _| _| $@ 1410 | _|_|_| $@ 1411 | $$@ 1412 | @@ 1413 | 225 LATIN SMALL LETTER A WITH ACUTE 1414 | _| $@ 1415 | _| $@ 1416 | $$@ 1417 | _|_|_| $@ 1418 | _| _| $@ 1419 | _|_|_| $@ 1420 | $$@ 1421 | @@ 1422 | 226 LATIN SMALL LETTER A WITH CIRCUMFLEX 1423 | _| $@ 1424 | _| _| $@ 1425 | $$@ 1426 | _|_|_| $@ 1427 | _| _| $@ 1428 | _|_|_| $@ 1429 | $$@ 1430 | @@ 1431 | 227 LATIN SMALL LETTER A WITH TILDE 1432 | _| _| $@ 1433 | _| _| $@ 1434 | $$@ 1435 | _|_|_| $@ 1436 | _| _| $@ 1437 | _|_|_| $@ 1438 | $$@ 1439 | @@ 1440 | 228 LATIN SMALL LETTER A WITH DIAERESIS 1441 | _| _| $@ 1442 | $$@ 1443 | _|_|_| $@ 1444 | _| _| $@ 1445 | _| _| $@ 1446 | _|_|_| $@ 1447 | $$@ 1448 | @@ 1449 | 229 LATIN SMALL LETTER A WITH RING ABOVE 1450 | _| $@ 1451 | _| _| $@ 1452 | _| $@ 1453 | _|_|_| $@ 1454 | _| _| $@ 1455 | _|_|_| $@ 1456 | $$@ 1457 | @@ 1458 | 230 LATIN SMALL LETTER AE 1459 | @ 1460 | $$ @ 1461 | _|_|_| _|_| $@ 1462 | _| _|_|_|_|_| $@ 1463 | _| _|_| $@ 1464 | _|_|_| _|_|_| $@ 1465 | $$@ 1466 | @@ 1467 | 231 LATIN SMALL LETTER C WITH CEDILLA 1468 | @ 1469 | $$@ 1470 | _|_|_| $@ 1471 | _| $@ 1472 | _| $@ 1473 | _|_|_| $@ 1474 | _| $@ 1475 | _|_| $ @@ 1476 | 232 LATIN SMALL LETTER E WITH GRAVE 1477 | _| $@ 1478 | _| $ @ 1479 | _|_| $@ 1480 | _|_|_|_| $@ 1481 | _| $@ 1482 | _|_|_| $@ 1483 | $$@ 1484 | @@ 1485 | 233 LATIN SMALL LETTER E WITH ACUTE 1486 | _| $@ 1487 | _| $@ 1488 | _|_| $@ 1489 | _|_|_|_| $@ 1490 | _| $@ 1491 | _|_|_| $@ 1492 | $$@ 1493 | @@ 1494 | 234 LATIN SMALL LETTER E WITH CIRCUMFLEX 1495 | _|_| $@ 1496 | _| _| $@ 1497 | _|_| $@ 1498 | _|_|_|_| $@ 1499 | _| $@ 1500 | _|_|_| $@ 1501 | $$@ 1502 | @@ 1503 | 235 LATIN SMALL LETTER E WITH DIAERESIS 1504 | _| _| $@ 1505 | $$@ 1506 | _|_| $@ 1507 | _|_|_|_| $@ 1508 | _| $@ 1509 | _|_|_| $@ 1510 | $$@ 1511 | @@ 1512 | 236 LATIN SMALL LETTER I WITH GRAVE 1513 | _| $@ 1514 | _| $@ 1515 | $$@ 1516 | _| $@ 1517 | _| $@ 1518 | _| $@ 1519 | $$@ 1520 | @@ 1521 | 237 LATIN SMALL LETTER I WITH ACUTE 1522 | _| $@ 1523 | _| $@ 1524 | $$ @ 1525 | _| $ @ 1526 | _| $ @ 1527 | _| $ @ 1528 | $$ @ 1529 | @@ 1530 | 238 LATIN SMALL LETTER I WITH CIRCUMFLEX 1531 | _| $@ 1532 | _| _| $@ 1533 | $$@ 1534 | _| $ @ 1535 | _| $ @ 1536 | _| $ @ 1537 | $$ @ 1538 | @@ 1539 | 239 LATIN SMALL LETTER I WITH DIAERESIS 1540 | _| _| $@ 1541 | $$@ 1542 | _| $ @ 1543 | _| $ @ 1544 | _| $ @ 1545 | _| $ @ 1546 | $$ @ 1547 | @@ 1548 | 240 LATIN SMALL LETTER ETH 1549 | _| _| $ @ 1550 | _| $ @ 1551 | _| _| $@ 1552 | _|_|_| $@ 1553 | _| _| $@ 1554 | _|_| $@ 1555 | $$ @ 1556 | @@ 1557 | 241 LATIN SMALL LETTER N WITH TILDE 1558 | _| _| $@ 1559 | _| _| $@ 1560 | $$ @ 1561 | _|_|_| $@ 1562 | _| _| $@ 1563 | _| _| $@ 1564 | $$@ 1565 | @@ 1566 | 242 LATIN SMALL LETTER O WITH GRAVE 1567 | _| $ @ 1568 | _| $ @ 1569 | $$ @ 1570 | _|_| $@ 1571 | _| _| $@ 1572 | _|_| $@ 1573 | $$ @ 1574 | @@ 1575 | 243 LATIN SMALL LETTER O WITH ACUTE 1576 | _| $ @ 1577 | _| $ @ 1578 | $$ @ 1579 | _|_| $@ 1580 | _| _| $@ 1581 | _|_| $@ 1582 | $$ @ 1583 | @@ 1584 | 244 LATIN SMALL LETTER O WITH CIRCUMFLEX 1585 | _|_| $@ 1586 | _| _| $@ 1587 | $$@ 1588 | _|_| $@ 1589 | _| _| $@ 1590 | _|_| $@ 1591 | $$ @ 1592 | @@ 1593 | 245 LATIN SMALL LETTER O WITH TILDE 1594 | _|_|_| $@ 1595 | _| _| $@ 1596 | $$ @ 1597 | _|_| $@ 1598 | _| _| $@ 1599 | _|_| $@ 1600 | $$ @ 1601 | @@ 1602 | 246 LATIN SMALL LETTER O WITH DIAERESIS 1603 | _| _| $@ 1604 | $$@ 1605 | _|_| $@ 1606 | _| _| $@ 1607 | _| _| $@ 1608 | _|_| $@ 1609 | $$ @ 1610 | @@ 1611 | 247 DIVISION SIGN 1612 | $$ @ 1613 | _| $ @ 1614 | $$@ 1615 | _|_|_|_|_| $@ 1616 | $$@ 1617 | _| $ @ 1618 | $$ @ 1619 | @@ 1620 | 248 LATIN SMALL LETTER O WITH STROKE 1621 | @ 1622 | $$@ 1623 | _|_|_| $@ 1624 | _| _|_| $@ 1625 | _|_| _| $@ 1626 | _|_|_| $@ 1627 | $$ @ 1628 | @@ 1629 | 249 LATIN SMALL LETTER U WITH GRAVE 1630 | _| $ @ 1631 | _| $ @ 1632 | $$@ 1633 | _| _| $@ 1634 | _| _| $@ 1635 | _|_|_| $@ 1636 | $$@ 1637 | @@ 1638 | 250 LATIN SMALL LETTER U WITH ACUTE 1639 | _| $@ 1640 | _| $@ 1641 | $$@ 1642 | _| _| $@ 1643 | _| _| $@ 1644 | _|_|_| $@ 1645 | $$@ 1646 | @@ 1647 | 251 LATIN SMALL LETTER U WITH CIRCUMFLEX 1648 | _|_| $@ 1649 | _| _| $@ 1650 | $$@ 1651 | _| _| $@ 1652 | _| _| $@ 1653 | _|_|_| $@ 1654 | $$@ 1655 | @@ 1656 | 252 LATIN SMALL LETTER U WITH DIAERESIS 1657 | _| _| $@ 1658 | $$@ 1659 | _| _| $@ 1660 | _| _| $@ 1661 | _| _| $@ 1662 | _|_|_| $@ 1663 | $$@ 1664 | @@ 1665 | 253 LATIN SMALL LETTER Y WITH ACUTE 1666 | _| $ @ 1667 | _| $ @ 1668 | $$@ 1669 | _| _| $@ 1670 | _| _| $@ 1671 | _|_|_| $@ 1672 | _| $@ 1673 | _|_| $@@ 1674 | 254 LATIN SMALL LETTER THORN 1675 | $$ @ 1676 | _| $ @ 1677 | _|_|_| $@ 1678 | _| _| $@ 1679 | _| _| $@ 1680 | _|_|_| $@ 1681 | _| $ @ 1682 | _| $ @@ 1683 | 255 LATIN SMALL LETTER Y WITH DIAERESIS 1684 | _| _| $@ 1685 | $$@ 1686 | _| _| $@ 1687 | _| _| $@ 1688 | _| _| $@ 1689 | _|_|_| $@ 1690 | _| $@ 1691 | _|_| $@@ 1692 | --------------------------------------------------------------------------------