├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── .gitignore ├── .sqlx ├── query-184a09d8c84061938663c58cba11993f134586635e136a627c2f183fa7212203.json ├── query-2a56981ba30ff7508903576200148505dd36bc348e0293ea9b5635f9fb48160d.json ├── query-2b93c9ad90c732121cb8fa5cf1b52e3a0f8e42d3db141253729e6fa8e9761a3b.json ├── query-502b941f1aa2f3e871ce188d1eec8d0b94b9424fa85fecec5cb64172db4bc92e.json ├── query-55129a07e1f3e43abb480ea64639ad671c851ca2ebe42e49673d5d94130c0812.json ├── query-6150428ed0a993ba15ce92bb6e7769e6f706105955b9c55f026cfa4cedab3ff8.json ├── query-746b389d34b32c02c7074221fcd84aa43c4dc9684d1ef6c18e3c7539a5050f5a.json ├── query-89b7b5b3520e10720b85089109e953396385ba50de797e80877bd85c466c6dee.json ├── query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json ├── query-b159232dfd15ae6eb696c02357affbfdf3e0bc9876db3f4b7f377fed8564ede0.json └── query-fb5bc4b83c4960f14bb2e6186d79f027477075ad7dc50f6c485da92f85234919.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── crates ├── cli │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── core │ ├── Cargo.toml │ └── src │ │ ├── arch.rs │ │ ├── cluster.rs │ │ ├── ingest.rs │ │ └── lib.rs └── db │ ├── Cargo.toml │ ├── migrations │ └── 20250508130138_init.sql │ └── src │ └── lib.rs └── test ├── notes.txt ├── simple.c ├── simple.ld ├── simple_mips.map ├── simple_mips.o ├── simple_mips_linked.o └── simple_mips_raw.bin /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | jobs: 5 | release: 6 | name: release ${{ matrix.target }} 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | include: 12 | - target: x86_64-pc-windows-gnu 13 | archive: zip 14 | - target: x86_64-unknown-linux-musl 15 | archive: tar.gz 16 | - target: x86_64-apple-darwin 17 | archive: zip 18 | steps: 19 | - uses: actions/checkout@master 20 | - name: Compile and release 21 | uses: rust-build/rust-build.action@v1.4.5 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | RUSTTARGET: ${{ matrix.target }} 26 | ARCHIVE_TYPES: ${{ matrix.archive }} 27 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | rustfmt-check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Run cargo fmt 14 | run: cargo fmt --all -- --check 15 | - name: Run cargo clippy 16 | run: cargo clippy --all -- -D warnings 17 | macos-check: 18 | runs-on: macos-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Test 22 | run: cargo test --all-features 23 | ubuntu-check: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Test 28 | run: cargo test --all-features 29 | windows-check: 30 | runs-on: windows-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Test 34 | run: cargo test --all-features 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coddog/ 2 | .idea/ 3 | 4 | .env 5 | .vscode/launch.json 6 | config.toml 7 | 8 | /target 9 | -------------------------------------------------------------------------------- /.sqlx/query-184a09d8c84061938663c58cba11993f134586635e136a627c2f183fa7212203.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO sources (project_id, hash, name, filepath) VALUES ($1, $2, $3, $4) RETURNING id", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8", 15 | "Text", 16 | "Text", 17 | "Text" 18 | ] 19 | }, 20 | "nullable": [ 21 | false 22 | ] 23 | }, 24 | "hash": "184a09d8c84061938663c58cba11993f134586635e136a627c2f183fa7212203" 25 | } 26 | -------------------------------------------------------------------------------- /.sqlx/query-2a56981ba30ff7508903576200148505dd36bc348e0293ea9b5635f9fb48160d.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO windows (pos, hash, symbol_id)\n SELECT * FROM UNNEST($1::int[], $2::bigint[], $3::bigint[])\n ", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int4Array", 9 | "Int8Array", 10 | "Int8Array" 11 | ] 12 | }, 13 | "nullable": [] 14 | }, 15 | "hash": "2a56981ba30ff7508903576200148505dd36bc348e0293ea9b5635f9fb48160d" 16 | } 17 | -------------------------------------------------------------------------------- /.sqlx/query-2b93c9ad90c732121cb8fa5cf1b52e3a0f8e42d3db141253729e6fa8e9761a3b.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\nWITH\npotential_matches AS (\n SELECT\n b.symbol_id,\n a.pos AS query_pos,\n b.pos AS match_pos,\n a.hash,\n (a.pos - b.pos) AS pos_diff\n FROM windows a\n JOIN windows b ON a.hash = b.hash\n WHERE a.symbol_id = $1 AND a.symbol_id != b.symbol_id\n),\nsequence_groups AS (\n SELECT\n hash,\n symbol_id,\n query_pos,\n match_pos,\n pos_diff,\n query_pos - ROW_NUMBER() OVER (PARTITION BY symbol_id, pos_diff ORDER BY query_pos) AS sequence_id\n FROM potential_matches\n),\nfinal_sequences AS (\n SELECT\n symbol_id,\n MIN(query_pos) AS start_query_pos,\n MIN(match_pos) AS start_match_pos,\n COUNT(*) AS length\n FROM sequence_groups\n GROUP BY symbol_id, pos_diff, sequence_id\n)\nSELECT project_id, projects.name AS project_name, source_id, sources.name AS source_name, symbol_id,\n symbols.name as symbol_name, start_query_pos, start_match_pos, length\nFROM final_sequences\nJOIN symbols ON symbol_id = symbols.id\nJOIN sources ON symbols.source_id = sources.id\nJOIN projects ON sources.project_id = projects.id\nWHERE length >= $2\nORDER BY project_id, source_id, symbol_id, start_query_pos, start_match_pos\n", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "project_id", 9 | "type_info": "Int8" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "project_name", 14 | "type_info": "Text" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "source_id", 19 | "type_info": "Int8" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "source_name", 24 | "type_info": "Text" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "symbol_id", 29 | "type_info": "Int8" 30 | }, 31 | { 32 | "ordinal": 5, 33 | "name": "symbol_name", 34 | "type_info": "Text" 35 | }, 36 | { 37 | "ordinal": 6, 38 | "name": "start_query_pos", 39 | "type_info": "Int4" 40 | }, 41 | { 42 | "ordinal": 7, 43 | "name": "start_match_pos", 44 | "type_info": "Int4" 45 | }, 46 | { 47 | "ordinal": 8, 48 | "name": "length", 49 | "type_info": "Int8" 50 | } 51 | ], 52 | "parameters": { 53 | "Left": [ 54 | "Int8", 55 | "Int8" 56 | ] 57 | }, 58 | "nullable": [ 59 | false, 60 | false, 61 | false, 62 | false, 63 | false, 64 | false, 65 | null, 66 | null, 67 | null 68 | ] 69 | }, 70 | "hash": "2b93c9ad90c732121cb8fa5cf1b52e3a0f8e42d3db141253729e6fa8e9761a3b" 71 | } 72 | -------------------------------------------------------------------------------- /.sqlx/query-502b941f1aa2f3e871ce188d1eec8d0b94b9424fa85fecec5cb64172db4bc92e.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "SELECT projects.name, projects.id FROM projects WHERE projects.name LIKE $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "name", 9 | "type_info": "Text" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "id", 14 | "type_info": "Int8" 15 | } 16 | ], 17 | "parameters": { 18 | "Left": [ 19 | "Text" 20 | ] 21 | }, 22 | "nullable": [ 23 | false, 24 | false 25 | ] 26 | }, 27 | "hash": "502b941f1aa2f3e871ce188d1eec8d0b94b9424fa85fecec5cb64172db4bc92e" 28 | } 29 | -------------------------------------------------------------------------------- /.sqlx/query-55129a07e1f3e43abb480ea64639ad671c851ca2ebe42e49673d5d94130c0812.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n SELECT symbols.id, symbols.pos, symbols.len, symbols.name, \n symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash,\n symbols.source_id,\n sources.name AS source_name, projects.name AS project_name, projects.id as project_id\n FROM symbols\n INNER JOIN sources ON sources.id = symbols.source_id\n INNER JOIN projects on sources.project_id = projects.id\n WHERE symbols.opcode_hash = $1 AND NOT symbols.id = $2", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int8" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "pos", 14 | "type_info": "Int8" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "len", 19 | "type_info": "Int4" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "name", 24 | "type_info": "Text" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "opcode_hash", 29 | "type_info": "Int8" 30 | }, 31 | { 32 | "ordinal": 5, 33 | "name": "equiv_hash", 34 | "type_info": "Int8" 35 | }, 36 | { 37 | "ordinal": 6, 38 | "name": "exact_hash", 39 | "type_info": "Int8" 40 | }, 41 | { 42 | "ordinal": 7, 43 | "name": "source_id", 44 | "type_info": "Int8" 45 | }, 46 | { 47 | "ordinal": 8, 48 | "name": "source_name", 49 | "type_info": "Text" 50 | }, 51 | { 52 | "ordinal": 9, 53 | "name": "project_name", 54 | "type_info": "Text" 55 | }, 56 | { 57 | "ordinal": 10, 58 | "name": "project_id", 59 | "type_info": "Int8" 60 | } 61 | ], 62 | "parameters": { 63 | "Left": [ 64 | "Int8", 65 | "Int8" 66 | ] 67 | }, 68 | "nullable": [ 69 | false, 70 | false, 71 | false, 72 | false, 73 | false, 74 | false, 75 | false, 76 | false, 77 | false, 78 | false, 79 | false 80 | ] 81 | }, 82 | "hash": "55129a07e1f3e43abb480ea64639ad671c851ca2ebe42e49673d5d94130c0812" 83 | } 84 | -------------------------------------------------------------------------------- /.sqlx/query-6150428ed0a993ba15ce92bb6e7769e6f706105955b9c55f026cfa4cedab3ff8.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "INSERT INTO projects (name, platform) VALUES ($1, $2) RETURNING id", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Text", 15 | "Int4" 16 | ] 17 | }, 18 | "nullable": [ 19 | false 20 | ] 21 | }, 22 | "hash": "6150428ed0a993ba15ce92bb6e7769e6f706105955b9c55f026cfa4cedab3ff8" 23 | } 24 | -------------------------------------------------------------------------------- /.sqlx/query-746b389d34b32c02c7074221fcd84aa43c4dc9684d1ef6c18e3c7539a5050f5a.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n INSERT INTO symbols (source_id, pos, len, name, opcode_hash, equiv_hash, exact_hash)\n SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::bigint[], $4::text[], $5::bigint[], $6::bigint[], $7::bigint[])\n RETURNING id\n ", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int8" 10 | } 11 | ], 12 | "parameters": { 13 | "Left": [ 14 | "Int8Array", 15 | "Int8Array", 16 | "Int8Array", 17 | "TextArray", 18 | "Int8Array", 19 | "Int8Array", 20 | "Int8Array" 21 | ] 22 | }, 23 | "nullable": [ 24 | false 25 | ] 26 | }, 27 | "hash": "746b389d34b32c02c7074221fcd84aa43c4dc9684d1ef6c18e3c7539a5050f5a" 28 | } 29 | -------------------------------------------------------------------------------- /.sqlx/query-89b7b5b3520e10720b85089109e953396385ba50de797e80877bd85c466c6dee.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n SELECT symbols.id, symbols.pos, symbols.len, symbols.name, \n symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash,\n symbols.source_id,\n sources.name AS source_name, projects.name AS project_name, projects.id as project_id\n FROM symbols\n INNER JOIN sources ON sources.id = symbols.source_id\n INNER JOIN projects on sources.project_id = projects.id\n WHERE symbols.equiv_hash = $1 AND NOT symbols.id = $2", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int8" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "pos", 14 | "type_info": "Int8" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "len", 19 | "type_info": "Int4" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "name", 24 | "type_info": "Text" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "opcode_hash", 29 | "type_info": "Int8" 30 | }, 31 | { 32 | "ordinal": 5, 33 | "name": "equiv_hash", 34 | "type_info": "Int8" 35 | }, 36 | { 37 | "ordinal": 6, 38 | "name": "exact_hash", 39 | "type_info": "Int8" 40 | }, 41 | { 42 | "ordinal": 7, 43 | "name": "source_id", 44 | "type_info": "Int8" 45 | }, 46 | { 47 | "ordinal": 8, 48 | "name": "source_name", 49 | "type_info": "Text" 50 | }, 51 | { 52 | "ordinal": 9, 53 | "name": "project_name", 54 | "type_info": "Text" 55 | }, 56 | { 57 | "ordinal": 10, 58 | "name": "project_id", 59 | "type_info": "Int8" 60 | } 61 | ], 62 | "parameters": { 63 | "Left": [ 64 | "Int8", 65 | "Int8" 66 | ] 67 | }, 68 | "nullable": [ 69 | false, 70 | false, 71 | false, 72 | false, 73 | false, 74 | false, 75 | false, 76 | false, 77 | false, 78 | false, 79 | false 80 | ] 81 | }, 82 | "hash": "89b7b5b3520e10720b85089109e953396385ba50de797e80877bd85c466c6dee" 83 | } 84 | -------------------------------------------------------------------------------- /.sqlx/query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "DELETE FROM projects WHERE id = $1", 4 | "describe": { 5 | "columns": [], 6 | "parameters": { 7 | "Left": [ 8 | "Int8" 9 | ] 10 | }, 11 | "nullable": [] 12 | }, 13 | "hash": "a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314" 14 | } 15 | -------------------------------------------------------------------------------- /.sqlx/query-b159232dfd15ae6eb696c02357affbfdf3e0bc9876db3f4b7f377fed8564ede0.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n SELECT symbols.id, symbols.pos, symbols.len, symbols.name,\n symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash, symbols.source_id,\n sources.name AS source_name, projects.name AS project_name, projects.id as project_id\n FROM symbols\n INNER JOIN sources ON sources.id = symbols.source_id\n INNER JOIN projects on sources.project_id = projects.id\n WHERE symbols.exact_hash = $1 AND NOT symbols.id = $2", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int8" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "pos", 14 | "type_info": "Int8" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "len", 19 | "type_info": "Int4" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "name", 24 | "type_info": "Text" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "opcode_hash", 29 | "type_info": "Int8" 30 | }, 31 | { 32 | "ordinal": 5, 33 | "name": "equiv_hash", 34 | "type_info": "Int8" 35 | }, 36 | { 37 | "ordinal": 6, 38 | "name": "exact_hash", 39 | "type_info": "Int8" 40 | }, 41 | { 42 | "ordinal": 7, 43 | "name": "source_id", 44 | "type_info": "Int8" 45 | }, 46 | { 47 | "ordinal": 8, 48 | "name": "source_name", 49 | "type_info": "Text" 50 | }, 51 | { 52 | "ordinal": 9, 53 | "name": "project_name", 54 | "type_info": "Text" 55 | }, 56 | { 57 | "ordinal": 10, 58 | "name": "project_id", 59 | "type_info": "Int8" 60 | } 61 | ], 62 | "parameters": { 63 | "Left": [ 64 | "Int8", 65 | "Int8" 66 | ] 67 | }, 68 | "nullable": [ 69 | false, 70 | false, 71 | false, 72 | false, 73 | false, 74 | false, 75 | false, 76 | false, 77 | false, 78 | false, 79 | false 80 | ] 81 | }, 82 | "hash": "b159232dfd15ae6eb696c02357affbfdf3e0bc9876db3f4b7f377fed8564ede0" 83 | } 84 | -------------------------------------------------------------------------------- /.sqlx/query-fb5bc4b83c4960f14bb2e6186d79f027477075ad7dc50f6c485da92f85234919.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "PostgreSQL", 3 | "query": "\n SELECT symbols.id, symbols.pos, symbols.len,\n symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash, symbols.source_id,\n sources.name AS source_name, projects.name AS project_name, projects.id as project_id\n FROM symbols\n INNER JOIN sources ON sources.id = symbols.source_id\n INNER JOIN projects on sources.project_id = projects.id\n WHERE symbols.name = $1", 4 | "describe": { 5 | "columns": [ 6 | { 7 | "ordinal": 0, 8 | "name": "id", 9 | "type_info": "Int8" 10 | }, 11 | { 12 | "ordinal": 1, 13 | "name": "pos", 14 | "type_info": "Int8" 15 | }, 16 | { 17 | "ordinal": 2, 18 | "name": "len", 19 | "type_info": "Int4" 20 | }, 21 | { 22 | "ordinal": 3, 23 | "name": "opcode_hash", 24 | "type_info": "Int8" 25 | }, 26 | { 27 | "ordinal": 4, 28 | "name": "equiv_hash", 29 | "type_info": "Int8" 30 | }, 31 | { 32 | "ordinal": 5, 33 | "name": "exact_hash", 34 | "type_info": "Int8" 35 | }, 36 | { 37 | "ordinal": 6, 38 | "name": "source_id", 39 | "type_info": "Int8" 40 | }, 41 | { 42 | "ordinal": 7, 43 | "name": "source_name", 44 | "type_info": "Text" 45 | }, 46 | { 47 | "ordinal": 8, 48 | "name": "project_name", 49 | "type_info": "Text" 50 | }, 51 | { 52 | "ordinal": 9, 53 | "name": "project_id", 54 | "type_info": "Int8" 55 | } 56 | ], 57 | "parameters": { 58 | "Left": [ 59 | "Text" 60 | ] 61 | }, 62 | "nullable": [ 63 | false, 64 | false, 65 | false, 66 | false, 67 | false, 68 | false, 69 | false, 70 | false, 71 | false, 72 | false 73 | ] 74 | }, 75 | "hash": "fb5bc4b83c4960f14bb2e6186d79f027477075ad7dc50f6c485da92f85234919" 76 | } 77 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "allocator-api2" 31 | version = "0.2.21" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.18" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "is_terminal_polyfill", 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle" 52 | version = "1.0.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 55 | 56 | [[package]] 57 | name = "anstyle-parse" 58 | version = "0.2.6" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 61 | dependencies = [ 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-query" 67 | version = "1.1.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 70 | dependencies = [ 71 | "windows-sys 0.59.0", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-wincon" 76 | version = "3.0.8" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" 79 | dependencies = [ 80 | "anstyle", 81 | "once_cell_polyfill", 82 | "windows-sys 0.59.0", 83 | ] 84 | 85 | [[package]] 86 | name = "anyhow" 87 | version = "1.0.98" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 90 | 91 | [[package]] 92 | name = "arrayref" 93 | version = "0.3.9" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" 96 | 97 | [[package]] 98 | name = "arrayvec" 99 | version = "0.7.6" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 102 | 103 | [[package]] 104 | name = "atoi" 105 | version = "2.0.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 108 | dependencies = [ 109 | "num-traits", 110 | ] 111 | 112 | [[package]] 113 | name = "autocfg" 114 | version = "1.4.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 117 | 118 | [[package]] 119 | name = "backtrace" 120 | version = "0.3.75" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 123 | dependencies = [ 124 | "addr2line", 125 | "cfg-if", 126 | "libc", 127 | "miniz_oxide", 128 | "object", 129 | "rustc-demangle", 130 | "windows-targets 0.52.6", 131 | ] 132 | 133 | [[package]] 134 | name = "base64" 135 | version = "0.22.1" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 138 | 139 | [[package]] 140 | name = "base64ct" 141 | version = "1.7.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" 144 | 145 | [[package]] 146 | name = "bitflags" 147 | version = "1.3.2" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 150 | 151 | [[package]] 152 | name = "bitflags" 153 | version = "2.9.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 156 | dependencies = [ 157 | "serde", 158 | ] 159 | 160 | [[package]] 161 | name = "blake3" 162 | version = "1.8.2" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" 165 | dependencies = [ 166 | "arrayref", 167 | "arrayvec", 168 | "cc", 169 | "cfg-if", 170 | "constant_time_eq", 171 | ] 172 | 173 | [[package]] 174 | name = "block-buffer" 175 | version = "0.10.4" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 178 | dependencies = [ 179 | "generic-array", 180 | ] 181 | 182 | [[package]] 183 | name = "byteorder" 184 | version = "1.5.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 187 | 188 | [[package]] 189 | name = "bytes" 190 | version = "1.10.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 193 | 194 | [[package]] 195 | name = "cc" 196 | version = "1.2.25" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" 199 | dependencies = [ 200 | "shlex", 201 | ] 202 | 203 | [[package]] 204 | name = "cfg-if" 205 | version = "1.0.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 208 | 209 | [[package]] 210 | name = "clap" 211 | version = "4.5.39" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" 214 | dependencies = [ 215 | "clap_builder", 216 | "clap_derive", 217 | ] 218 | 219 | [[package]] 220 | name = "clap_builder" 221 | version = "4.5.39" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" 224 | dependencies = [ 225 | "anstream", 226 | "anstyle", 227 | "clap_lex", 228 | "strsim", 229 | ] 230 | 231 | [[package]] 232 | name = "clap_derive" 233 | version = "4.5.32" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 236 | dependencies = [ 237 | "heck", 238 | "proc-macro2", 239 | "quote", 240 | "syn", 241 | ] 242 | 243 | [[package]] 244 | name = "clap_lex" 245 | version = "0.7.4" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 248 | 249 | [[package]] 250 | name = "coddog-cli" 251 | version = "0.4.0" 252 | dependencies = [ 253 | "anyhow", 254 | "clap", 255 | "coddog-core", 256 | "coddog-db", 257 | "colored", 258 | "decomp_settings", 259 | "dotenvy", 260 | "glob", 261 | "inquire", 262 | "itertools", 263 | "pbr", 264 | "sqlx", 265 | "tokio", 266 | ] 267 | 268 | [[package]] 269 | name = "coddog-core" 270 | version = "0.4.0" 271 | dependencies = [ 272 | "anyhow", 273 | "editdistancek", 274 | "mapfile_parser", 275 | "object", 276 | "ppc750cl", 277 | "rabbitizer", 278 | ] 279 | 280 | [[package]] 281 | name = "coddog-db" 282 | version = "0.4.0" 283 | dependencies = [ 284 | "anyhow", 285 | "blake3", 286 | "coddog-core", 287 | "sqlx", 288 | ] 289 | 290 | [[package]] 291 | name = "colorchoice" 292 | version = "1.0.3" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 295 | 296 | [[package]] 297 | name = "colored" 298 | version = "3.0.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" 301 | dependencies = [ 302 | "windows-sys 0.59.0", 303 | ] 304 | 305 | [[package]] 306 | name = "concurrent-queue" 307 | version = "2.5.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 310 | dependencies = [ 311 | "crossbeam-utils", 312 | ] 313 | 314 | [[package]] 315 | name = "const-oid" 316 | version = "0.9.6" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 319 | 320 | [[package]] 321 | name = "constant_time_eq" 322 | version = "0.3.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" 325 | 326 | [[package]] 327 | name = "cpufeatures" 328 | version = "0.2.17" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 331 | dependencies = [ 332 | "libc", 333 | ] 334 | 335 | [[package]] 336 | name = "crc" 337 | version = "3.3.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" 340 | dependencies = [ 341 | "crc-catalog", 342 | ] 343 | 344 | [[package]] 345 | name = "crc-catalog" 346 | version = "2.4.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 349 | 350 | [[package]] 351 | name = "crc32fast" 352 | version = "1.4.2" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 355 | dependencies = [ 356 | "cfg-if", 357 | ] 358 | 359 | [[package]] 360 | name = "crossbeam-channel" 361 | version = "0.5.15" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 364 | dependencies = [ 365 | "crossbeam-utils", 366 | ] 367 | 368 | [[package]] 369 | name = "crossbeam-queue" 370 | version = "0.3.12" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 373 | dependencies = [ 374 | "crossbeam-utils", 375 | ] 376 | 377 | [[package]] 378 | name = "crossbeam-utils" 379 | version = "0.8.21" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 382 | 383 | [[package]] 384 | name = "crossterm" 385 | version = "0.25.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" 388 | dependencies = [ 389 | "bitflags 1.3.2", 390 | "crossterm_winapi", 391 | "libc", 392 | "mio 0.8.11", 393 | "parking_lot", 394 | "signal-hook", 395 | "signal-hook-mio", 396 | "winapi", 397 | ] 398 | 399 | [[package]] 400 | name = "crossterm_winapi" 401 | version = "0.9.1" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 404 | dependencies = [ 405 | "winapi", 406 | ] 407 | 408 | [[package]] 409 | name = "crypto-common" 410 | version = "0.1.6" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 413 | dependencies = [ 414 | "generic-array", 415 | "typenum", 416 | ] 417 | 418 | [[package]] 419 | name = "decomp_settings" 420 | version = "0.0.9" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "5cdbfd341ed8115f229b7f7a56c3f77c6260797c761c5805d2530fc860a9e012" 423 | dependencies = [ 424 | "serde", 425 | "serde_yaml", 426 | "thiserror", 427 | ] 428 | 429 | [[package]] 430 | name = "der" 431 | version = "0.7.10" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 434 | dependencies = [ 435 | "const-oid", 436 | "pem-rfc7468", 437 | "zeroize", 438 | ] 439 | 440 | [[package]] 441 | name = "digest" 442 | version = "0.10.7" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 445 | dependencies = [ 446 | "block-buffer", 447 | "const-oid", 448 | "crypto-common", 449 | "subtle", 450 | ] 451 | 452 | [[package]] 453 | name = "displaydoc" 454 | version = "0.2.5" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 457 | dependencies = [ 458 | "proc-macro2", 459 | "quote", 460 | "syn", 461 | ] 462 | 463 | [[package]] 464 | name = "dotenvy" 465 | version = "0.15.7" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 468 | 469 | [[package]] 470 | name = "dyn-clone" 471 | version = "1.0.19" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" 474 | 475 | [[package]] 476 | name = "editdistancek" 477 | version = "1.0.2" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "3e02df23d5b1c6f9e69fa603b890378123b93073df998a21e6e33b9db0a32613" 480 | 481 | [[package]] 482 | name = "either" 483 | version = "1.15.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 486 | dependencies = [ 487 | "serde", 488 | ] 489 | 490 | [[package]] 491 | name = "equivalent" 492 | version = "1.0.2" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 495 | 496 | [[package]] 497 | name = "etcetera" 498 | version = "0.8.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" 501 | dependencies = [ 502 | "cfg-if", 503 | "home", 504 | "windows-sys 0.48.0", 505 | ] 506 | 507 | [[package]] 508 | name = "event-listener" 509 | version = "5.4.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 512 | dependencies = [ 513 | "concurrent-queue", 514 | "parking", 515 | "pin-project-lite", 516 | ] 517 | 518 | [[package]] 519 | name = "flate2" 520 | version = "1.1.1" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" 523 | dependencies = [ 524 | "crc32fast", 525 | "miniz_oxide", 526 | ] 527 | 528 | [[package]] 529 | name = "flume" 530 | version = "0.11.1" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" 533 | dependencies = [ 534 | "futures-core", 535 | "futures-sink", 536 | "spin", 537 | ] 538 | 539 | [[package]] 540 | name = "foldhash" 541 | version = "0.1.5" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 544 | 545 | [[package]] 546 | name = "form_urlencoded" 547 | version = "1.2.1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 550 | dependencies = [ 551 | "percent-encoding", 552 | ] 553 | 554 | [[package]] 555 | name = "futures-channel" 556 | version = "0.3.31" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 559 | dependencies = [ 560 | "futures-core", 561 | "futures-sink", 562 | ] 563 | 564 | [[package]] 565 | name = "futures-core" 566 | version = "0.3.31" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 569 | 570 | [[package]] 571 | name = "futures-executor" 572 | version = "0.3.31" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 575 | dependencies = [ 576 | "futures-core", 577 | "futures-task", 578 | "futures-util", 579 | ] 580 | 581 | [[package]] 582 | name = "futures-intrusive" 583 | version = "0.5.0" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" 586 | dependencies = [ 587 | "futures-core", 588 | "lock_api", 589 | "parking_lot", 590 | ] 591 | 592 | [[package]] 593 | name = "futures-io" 594 | version = "0.3.31" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 597 | 598 | [[package]] 599 | name = "futures-sink" 600 | version = "0.3.31" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 603 | 604 | [[package]] 605 | name = "futures-task" 606 | version = "0.3.31" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 609 | 610 | [[package]] 611 | name = "futures-util" 612 | version = "0.3.31" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 615 | dependencies = [ 616 | "futures-core", 617 | "futures-io", 618 | "futures-sink", 619 | "futures-task", 620 | "memchr", 621 | "pin-project-lite", 622 | "pin-utils", 623 | "slab", 624 | ] 625 | 626 | [[package]] 627 | name = "fuzzy-matcher" 628 | version = "0.3.7" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 631 | dependencies = [ 632 | "thread_local", 633 | ] 634 | 635 | [[package]] 636 | name = "fxhash" 637 | version = "0.2.1" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 640 | dependencies = [ 641 | "byteorder", 642 | ] 643 | 644 | [[package]] 645 | name = "generic-array" 646 | version = "0.14.7" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 649 | dependencies = [ 650 | "typenum", 651 | "version_check", 652 | ] 653 | 654 | [[package]] 655 | name = "getrandom" 656 | version = "0.2.16" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 659 | dependencies = [ 660 | "cfg-if", 661 | "libc", 662 | "wasi", 663 | ] 664 | 665 | [[package]] 666 | name = "gimli" 667 | version = "0.31.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 670 | 671 | [[package]] 672 | name = "glob" 673 | version = "0.3.2" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 676 | 677 | [[package]] 678 | name = "hashbrown" 679 | version = "0.15.3" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 682 | dependencies = [ 683 | "allocator-api2", 684 | "equivalent", 685 | "foldhash", 686 | ] 687 | 688 | [[package]] 689 | name = "hashlink" 690 | version = "0.10.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 693 | dependencies = [ 694 | "hashbrown", 695 | ] 696 | 697 | [[package]] 698 | name = "heck" 699 | version = "0.5.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 702 | 703 | [[package]] 704 | name = "hex" 705 | version = "0.4.3" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 708 | 709 | [[package]] 710 | name = "hkdf" 711 | version = "0.12.4" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 714 | dependencies = [ 715 | "hmac", 716 | ] 717 | 718 | [[package]] 719 | name = "hmac" 720 | version = "0.12.1" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 723 | dependencies = [ 724 | "digest", 725 | ] 726 | 727 | [[package]] 728 | name = "home" 729 | version = "0.5.11" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 732 | dependencies = [ 733 | "windows-sys 0.59.0", 734 | ] 735 | 736 | [[package]] 737 | name = "icu_collections" 738 | version = "2.0.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 741 | dependencies = [ 742 | "displaydoc", 743 | "potential_utf", 744 | "yoke", 745 | "zerofrom", 746 | "zerovec", 747 | ] 748 | 749 | [[package]] 750 | name = "icu_locale_core" 751 | version = "2.0.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 754 | dependencies = [ 755 | "displaydoc", 756 | "litemap", 757 | "tinystr", 758 | "writeable", 759 | "zerovec", 760 | ] 761 | 762 | [[package]] 763 | name = "icu_normalizer" 764 | version = "2.0.0" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 767 | dependencies = [ 768 | "displaydoc", 769 | "icu_collections", 770 | "icu_normalizer_data", 771 | "icu_properties", 772 | "icu_provider", 773 | "smallvec", 774 | "zerovec", 775 | ] 776 | 777 | [[package]] 778 | name = "icu_normalizer_data" 779 | version = "2.0.0" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 782 | 783 | [[package]] 784 | name = "icu_properties" 785 | version = "2.0.1" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 788 | dependencies = [ 789 | "displaydoc", 790 | "icu_collections", 791 | "icu_locale_core", 792 | "icu_properties_data", 793 | "icu_provider", 794 | "potential_utf", 795 | "zerotrie", 796 | "zerovec", 797 | ] 798 | 799 | [[package]] 800 | name = "icu_properties_data" 801 | version = "2.0.1" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 804 | 805 | [[package]] 806 | name = "icu_provider" 807 | version = "2.0.0" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 810 | dependencies = [ 811 | "displaydoc", 812 | "icu_locale_core", 813 | "stable_deref_trait", 814 | "tinystr", 815 | "writeable", 816 | "yoke", 817 | "zerofrom", 818 | "zerotrie", 819 | "zerovec", 820 | ] 821 | 822 | [[package]] 823 | name = "idna" 824 | version = "1.0.3" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 827 | dependencies = [ 828 | "idna_adapter", 829 | "smallvec", 830 | "utf8_iter", 831 | ] 832 | 833 | [[package]] 834 | name = "idna_adapter" 835 | version = "1.2.1" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 838 | dependencies = [ 839 | "icu_normalizer", 840 | "icu_properties", 841 | ] 842 | 843 | [[package]] 844 | name = "indexmap" 845 | version = "2.9.0" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 848 | dependencies = [ 849 | "equivalent", 850 | "hashbrown", 851 | ] 852 | 853 | [[package]] 854 | name = "inquire" 855 | version = "0.7.5" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" 858 | dependencies = [ 859 | "bitflags 2.9.1", 860 | "crossterm", 861 | "dyn-clone", 862 | "fuzzy-matcher", 863 | "fxhash", 864 | "newline-converter", 865 | "once_cell", 866 | "unicode-segmentation", 867 | "unicode-width", 868 | ] 869 | 870 | [[package]] 871 | name = "is_terminal_polyfill" 872 | version = "1.70.1" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 875 | 876 | [[package]] 877 | name = "itertools" 878 | version = "0.14.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 881 | dependencies = [ 882 | "either", 883 | ] 884 | 885 | [[package]] 886 | name = "itoa" 887 | version = "1.0.15" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 890 | 891 | [[package]] 892 | name = "lazy_static" 893 | version = "1.5.0" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 896 | dependencies = [ 897 | "spin", 898 | ] 899 | 900 | [[package]] 901 | name = "libc" 902 | version = "0.2.172" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 905 | 906 | [[package]] 907 | name = "libm" 908 | version = "0.2.15" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 911 | 912 | [[package]] 913 | name = "libsqlite3-sys" 914 | version = "0.30.1" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" 917 | dependencies = [ 918 | "pkg-config", 919 | "vcpkg", 920 | ] 921 | 922 | [[package]] 923 | name = "litemap" 924 | version = "0.8.0" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 927 | 928 | [[package]] 929 | name = "lock_api" 930 | version = "0.4.13" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 933 | dependencies = [ 934 | "autocfg", 935 | "scopeguard", 936 | ] 937 | 938 | [[package]] 939 | name = "log" 940 | version = "0.4.27" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 943 | 944 | [[package]] 945 | name = "mapfile_parser" 946 | version = "2.9.3" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "e61fddbb64f353668400ef0850c5eef6ae0a7df9d8fbabe7cf132deea61a283a" 949 | dependencies = [ 950 | "lazy_static", 951 | "regex", 952 | ] 953 | 954 | [[package]] 955 | name = "md-5" 956 | version = "0.10.6" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 959 | dependencies = [ 960 | "cfg-if", 961 | "digest", 962 | ] 963 | 964 | [[package]] 965 | name = "memchr" 966 | version = "2.7.4" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 969 | 970 | [[package]] 971 | name = "miniz_oxide" 972 | version = "0.8.8" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 975 | dependencies = [ 976 | "adler2", 977 | ] 978 | 979 | [[package]] 980 | name = "mio" 981 | version = "0.8.11" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 984 | dependencies = [ 985 | "libc", 986 | "log", 987 | "wasi", 988 | "windows-sys 0.48.0", 989 | ] 990 | 991 | [[package]] 992 | name = "mio" 993 | version = "1.0.4" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 996 | dependencies = [ 997 | "libc", 998 | "wasi", 999 | "windows-sys 0.59.0", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "newline-converter" 1004 | version = "0.3.0" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" 1007 | dependencies = [ 1008 | "unicode-segmentation", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "num-bigint-dig" 1013 | version = "0.8.4" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 1016 | dependencies = [ 1017 | "byteorder", 1018 | "lazy_static", 1019 | "libm", 1020 | "num-integer", 1021 | "num-iter", 1022 | "num-traits", 1023 | "rand", 1024 | "smallvec", 1025 | "zeroize", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "num-integer" 1030 | version = "0.1.46" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1033 | dependencies = [ 1034 | "num-traits", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "num-iter" 1039 | version = "0.1.45" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1042 | dependencies = [ 1043 | "autocfg", 1044 | "num-integer", 1045 | "num-traits", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "num-traits" 1050 | version = "0.2.19" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1053 | dependencies = [ 1054 | "autocfg", 1055 | "libm", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "object" 1060 | version = "0.36.7" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1063 | dependencies = [ 1064 | "flate2", 1065 | "memchr", 1066 | "ruzstd", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "once_cell" 1071 | version = "1.21.3" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1074 | 1075 | [[package]] 1076 | name = "once_cell_polyfill" 1077 | version = "1.70.1" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 1080 | 1081 | [[package]] 1082 | name = "ordered-float" 1083 | version = "5.0.0" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" 1086 | dependencies = [ 1087 | "num-traits", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "parking" 1092 | version = "2.2.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1095 | 1096 | [[package]] 1097 | name = "parking_lot" 1098 | version = "0.12.4" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 1101 | dependencies = [ 1102 | "lock_api", 1103 | "parking_lot_core", 1104 | ] 1105 | 1106 | [[package]] 1107 | name = "parking_lot_core" 1108 | version = "0.9.11" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 1111 | dependencies = [ 1112 | "cfg-if", 1113 | "libc", 1114 | "redox_syscall", 1115 | "smallvec", 1116 | "windows-targets 0.52.6", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "pbr" 1121 | version = "1.1.1" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "ed5827dfa0d69b6c92493d6c38e633bbaa5937c153d0d7c28bf12313f8c6d514" 1124 | dependencies = [ 1125 | "crossbeam-channel", 1126 | "libc", 1127 | "winapi", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "pem-rfc7468" 1132 | version = "0.7.0" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1135 | dependencies = [ 1136 | "base64ct", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "percent-encoding" 1141 | version = "2.3.1" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1144 | 1145 | [[package]] 1146 | name = "pin-project-lite" 1147 | version = "0.2.16" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1150 | 1151 | [[package]] 1152 | name = "pin-utils" 1153 | version = "0.1.0" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1156 | 1157 | [[package]] 1158 | name = "pkcs1" 1159 | version = "0.7.5" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 1162 | dependencies = [ 1163 | "der", 1164 | "pkcs8", 1165 | "spki", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "pkcs8" 1170 | version = "0.10.2" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1173 | dependencies = [ 1174 | "der", 1175 | "spki", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "pkg-config" 1180 | version = "0.3.32" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1183 | 1184 | [[package]] 1185 | name = "potential_utf" 1186 | version = "0.1.2" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 1189 | dependencies = [ 1190 | "zerovec", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "ppc750cl" 1195 | version = "0.3.2" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "be8e5b9d48ab30323e8ece6d655d20fc16a570e4f1af0de6890d3e9ebd284ba0" 1198 | 1199 | [[package]] 1200 | name = "ppv-lite86" 1201 | version = "0.2.21" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1204 | dependencies = [ 1205 | "zerocopy", 1206 | ] 1207 | 1208 | [[package]] 1209 | name = "proc-macro2" 1210 | version = "1.0.95" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1213 | dependencies = [ 1214 | "unicode-ident", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "quote" 1219 | version = "1.0.40" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1222 | dependencies = [ 1223 | "proc-macro2", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "rabbitizer" 1228 | version = "2.0.0-alpha.3" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "5b1a0d79724d4721a143687e2b0d9f2bf1d6823f46d8bdf2174f0866eec8636f" 1231 | dependencies = [ 1232 | "bitflags 2.9.1", 1233 | "ordered-float", 1234 | ] 1235 | 1236 | [[package]] 1237 | name = "rand" 1238 | version = "0.8.5" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1241 | dependencies = [ 1242 | "libc", 1243 | "rand_chacha", 1244 | "rand_core", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "rand_chacha" 1249 | version = "0.3.1" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1252 | dependencies = [ 1253 | "ppv-lite86", 1254 | "rand_core", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "rand_core" 1259 | version = "0.6.4" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1262 | dependencies = [ 1263 | "getrandom", 1264 | ] 1265 | 1266 | [[package]] 1267 | name = "redox_syscall" 1268 | version = "0.5.12" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 1271 | dependencies = [ 1272 | "bitflags 2.9.1", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "regex" 1277 | version = "1.11.1" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1280 | dependencies = [ 1281 | "aho-corasick", 1282 | "memchr", 1283 | "regex-automata", 1284 | "regex-syntax", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "regex-automata" 1289 | version = "0.4.9" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1292 | dependencies = [ 1293 | "aho-corasick", 1294 | "memchr", 1295 | "regex-syntax", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "regex-syntax" 1300 | version = "0.8.5" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1303 | 1304 | [[package]] 1305 | name = "rsa" 1306 | version = "0.9.8" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" 1309 | dependencies = [ 1310 | "const-oid", 1311 | "digest", 1312 | "num-bigint-dig", 1313 | "num-integer", 1314 | "num-traits", 1315 | "pkcs1", 1316 | "pkcs8", 1317 | "rand_core", 1318 | "signature", 1319 | "spki", 1320 | "subtle", 1321 | "zeroize", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "rustc-demangle" 1326 | version = "0.1.24" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1329 | 1330 | [[package]] 1331 | name = "ruzstd" 1332 | version = "0.7.3" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" 1335 | dependencies = [ 1336 | "twox-hash", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "ryu" 1341 | version = "1.0.20" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1344 | 1345 | [[package]] 1346 | name = "scopeguard" 1347 | version = "1.2.0" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1350 | 1351 | [[package]] 1352 | name = "serde" 1353 | version = "1.0.219" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1356 | dependencies = [ 1357 | "serde_derive", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "serde_derive" 1362 | version = "1.0.219" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1365 | dependencies = [ 1366 | "proc-macro2", 1367 | "quote", 1368 | "syn", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "serde_json" 1373 | version = "1.0.140" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1376 | dependencies = [ 1377 | "itoa", 1378 | "memchr", 1379 | "ryu", 1380 | "serde", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "serde_urlencoded" 1385 | version = "0.7.1" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1388 | dependencies = [ 1389 | "form_urlencoded", 1390 | "itoa", 1391 | "ryu", 1392 | "serde", 1393 | ] 1394 | 1395 | [[package]] 1396 | name = "serde_yaml" 1397 | version = "0.9.34+deprecated" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1400 | dependencies = [ 1401 | "indexmap", 1402 | "itoa", 1403 | "ryu", 1404 | "serde", 1405 | "unsafe-libyaml", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "sha1" 1410 | version = "0.10.6" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1413 | dependencies = [ 1414 | "cfg-if", 1415 | "cpufeatures", 1416 | "digest", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "sha2" 1421 | version = "0.10.9" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1424 | dependencies = [ 1425 | "cfg-if", 1426 | "cpufeatures", 1427 | "digest", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "shlex" 1432 | version = "1.3.0" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1435 | 1436 | [[package]] 1437 | name = "signal-hook" 1438 | version = "0.3.18" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 1441 | dependencies = [ 1442 | "libc", 1443 | "signal-hook-registry", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "signal-hook-mio" 1448 | version = "0.2.4" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1451 | dependencies = [ 1452 | "libc", 1453 | "mio 0.8.11", 1454 | "signal-hook", 1455 | ] 1456 | 1457 | [[package]] 1458 | name = "signal-hook-registry" 1459 | version = "1.4.5" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 1462 | dependencies = [ 1463 | "libc", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "signature" 1468 | version = "2.2.0" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 1471 | dependencies = [ 1472 | "digest", 1473 | "rand_core", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "slab" 1478 | version = "0.4.9" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1481 | dependencies = [ 1482 | "autocfg", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "smallvec" 1487 | version = "1.15.0" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1490 | dependencies = [ 1491 | "serde", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "socket2" 1496 | version = "0.5.10" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 1499 | dependencies = [ 1500 | "libc", 1501 | "windows-sys 0.52.0", 1502 | ] 1503 | 1504 | [[package]] 1505 | name = "spin" 1506 | version = "0.9.8" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1509 | dependencies = [ 1510 | "lock_api", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "spki" 1515 | version = "0.7.3" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 1518 | dependencies = [ 1519 | "base64ct", 1520 | "der", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "sqlx" 1525 | version = "0.8.6" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" 1528 | dependencies = [ 1529 | "sqlx-core", 1530 | "sqlx-macros", 1531 | "sqlx-mysql", 1532 | "sqlx-postgres", 1533 | "sqlx-sqlite", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "sqlx-core" 1538 | version = "0.8.6" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" 1541 | dependencies = [ 1542 | "base64", 1543 | "bytes", 1544 | "crc", 1545 | "crossbeam-queue", 1546 | "either", 1547 | "event-listener", 1548 | "futures-core", 1549 | "futures-intrusive", 1550 | "futures-io", 1551 | "futures-util", 1552 | "hashbrown", 1553 | "hashlink", 1554 | "indexmap", 1555 | "log", 1556 | "memchr", 1557 | "once_cell", 1558 | "percent-encoding", 1559 | "serde", 1560 | "serde_json", 1561 | "sha2", 1562 | "smallvec", 1563 | "thiserror", 1564 | "tokio", 1565 | "tokio-stream", 1566 | "tracing", 1567 | "url", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "sqlx-macros" 1572 | version = "0.8.6" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" 1575 | dependencies = [ 1576 | "proc-macro2", 1577 | "quote", 1578 | "sqlx-core", 1579 | "sqlx-macros-core", 1580 | "syn", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "sqlx-macros-core" 1585 | version = "0.8.6" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" 1588 | dependencies = [ 1589 | "dotenvy", 1590 | "either", 1591 | "heck", 1592 | "hex", 1593 | "once_cell", 1594 | "proc-macro2", 1595 | "quote", 1596 | "serde", 1597 | "serde_json", 1598 | "sha2", 1599 | "sqlx-core", 1600 | "sqlx-mysql", 1601 | "sqlx-postgres", 1602 | "sqlx-sqlite", 1603 | "syn", 1604 | "tokio", 1605 | "url", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "sqlx-mysql" 1610 | version = "0.8.6" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" 1613 | dependencies = [ 1614 | "atoi", 1615 | "base64", 1616 | "bitflags 2.9.1", 1617 | "byteorder", 1618 | "bytes", 1619 | "crc", 1620 | "digest", 1621 | "dotenvy", 1622 | "either", 1623 | "futures-channel", 1624 | "futures-core", 1625 | "futures-io", 1626 | "futures-util", 1627 | "generic-array", 1628 | "hex", 1629 | "hkdf", 1630 | "hmac", 1631 | "itoa", 1632 | "log", 1633 | "md-5", 1634 | "memchr", 1635 | "once_cell", 1636 | "percent-encoding", 1637 | "rand", 1638 | "rsa", 1639 | "serde", 1640 | "sha1", 1641 | "sha2", 1642 | "smallvec", 1643 | "sqlx-core", 1644 | "stringprep", 1645 | "thiserror", 1646 | "tracing", 1647 | "whoami", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "sqlx-postgres" 1652 | version = "0.8.6" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" 1655 | dependencies = [ 1656 | "atoi", 1657 | "base64", 1658 | "bitflags 2.9.1", 1659 | "byteorder", 1660 | "crc", 1661 | "dotenvy", 1662 | "etcetera", 1663 | "futures-channel", 1664 | "futures-core", 1665 | "futures-util", 1666 | "hex", 1667 | "hkdf", 1668 | "hmac", 1669 | "home", 1670 | "itoa", 1671 | "log", 1672 | "md-5", 1673 | "memchr", 1674 | "once_cell", 1675 | "rand", 1676 | "serde", 1677 | "serde_json", 1678 | "sha2", 1679 | "smallvec", 1680 | "sqlx-core", 1681 | "stringprep", 1682 | "thiserror", 1683 | "tracing", 1684 | "whoami", 1685 | ] 1686 | 1687 | [[package]] 1688 | name = "sqlx-sqlite" 1689 | version = "0.8.6" 1690 | source = "registry+https://github.com/rust-lang/crates.io-index" 1691 | checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" 1692 | dependencies = [ 1693 | "atoi", 1694 | "flume", 1695 | "futures-channel", 1696 | "futures-core", 1697 | "futures-executor", 1698 | "futures-intrusive", 1699 | "futures-util", 1700 | "libsqlite3-sys", 1701 | "log", 1702 | "percent-encoding", 1703 | "serde", 1704 | "serde_urlencoded", 1705 | "sqlx-core", 1706 | "thiserror", 1707 | "tracing", 1708 | "url", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "stable_deref_trait" 1713 | version = "1.2.0" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1716 | 1717 | [[package]] 1718 | name = "static_assertions" 1719 | version = "1.1.0" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1722 | 1723 | [[package]] 1724 | name = "stringprep" 1725 | version = "0.1.5" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 1728 | dependencies = [ 1729 | "unicode-bidi", 1730 | "unicode-normalization", 1731 | "unicode-properties", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "strsim" 1736 | version = "0.11.1" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1739 | 1740 | [[package]] 1741 | name = "subtle" 1742 | version = "2.6.1" 1743 | source = "registry+https://github.com/rust-lang/crates.io-index" 1744 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1745 | 1746 | [[package]] 1747 | name = "syn" 1748 | version = "2.0.101" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 1751 | dependencies = [ 1752 | "proc-macro2", 1753 | "quote", 1754 | "unicode-ident", 1755 | ] 1756 | 1757 | [[package]] 1758 | name = "synstructure" 1759 | version = "0.13.2" 1760 | source = "registry+https://github.com/rust-lang/crates.io-index" 1761 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1762 | dependencies = [ 1763 | "proc-macro2", 1764 | "quote", 1765 | "syn", 1766 | ] 1767 | 1768 | [[package]] 1769 | name = "thiserror" 1770 | version = "2.0.12" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1773 | dependencies = [ 1774 | "thiserror-impl", 1775 | ] 1776 | 1777 | [[package]] 1778 | name = "thiserror-impl" 1779 | version = "2.0.12" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1782 | dependencies = [ 1783 | "proc-macro2", 1784 | "quote", 1785 | "syn", 1786 | ] 1787 | 1788 | [[package]] 1789 | name = "thread_local" 1790 | version = "1.1.8" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1793 | dependencies = [ 1794 | "cfg-if", 1795 | "once_cell", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "tinystr" 1800 | version = "0.8.1" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 1803 | dependencies = [ 1804 | "displaydoc", 1805 | "zerovec", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "tinyvec" 1810 | version = "1.9.0" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 1813 | dependencies = [ 1814 | "tinyvec_macros", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "tinyvec_macros" 1819 | version = "0.1.1" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1822 | 1823 | [[package]] 1824 | name = "tokio" 1825 | version = "1.45.1" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" 1828 | dependencies = [ 1829 | "backtrace", 1830 | "bytes", 1831 | "libc", 1832 | "mio 1.0.4", 1833 | "pin-project-lite", 1834 | "socket2", 1835 | "tokio-macros", 1836 | "windows-sys 0.52.0", 1837 | ] 1838 | 1839 | [[package]] 1840 | name = "tokio-macros" 1841 | version = "2.5.0" 1842 | source = "registry+https://github.com/rust-lang/crates.io-index" 1843 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1844 | dependencies = [ 1845 | "proc-macro2", 1846 | "quote", 1847 | "syn", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "tokio-stream" 1852 | version = "0.1.17" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 1855 | dependencies = [ 1856 | "futures-core", 1857 | "pin-project-lite", 1858 | "tokio", 1859 | ] 1860 | 1861 | [[package]] 1862 | name = "tracing" 1863 | version = "0.1.41" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1866 | dependencies = [ 1867 | "log", 1868 | "pin-project-lite", 1869 | "tracing-attributes", 1870 | "tracing-core", 1871 | ] 1872 | 1873 | [[package]] 1874 | name = "tracing-attributes" 1875 | version = "0.1.28" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 1878 | dependencies = [ 1879 | "proc-macro2", 1880 | "quote", 1881 | "syn", 1882 | ] 1883 | 1884 | [[package]] 1885 | name = "tracing-core" 1886 | version = "0.1.33" 1887 | source = "registry+https://github.com/rust-lang/crates.io-index" 1888 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1889 | dependencies = [ 1890 | "once_cell", 1891 | ] 1892 | 1893 | [[package]] 1894 | name = "twox-hash" 1895 | version = "1.6.3" 1896 | source = "registry+https://github.com/rust-lang/crates.io-index" 1897 | checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" 1898 | dependencies = [ 1899 | "cfg-if", 1900 | "static_assertions", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "typenum" 1905 | version = "1.18.0" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 1908 | 1909 | [[package]] 1910 | name = "unicode-bidi" 1911 | version = "0.3.18" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 1914 | 1915 | [[package]] 1916 | name = "unicode-ident" 1917 | version = "1.0.18" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1920 | 1921 | [[package]] 1922 | name = "unicode-normalization" 1923 | version = "0.1.24" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1926 | dependencies = [ 1927 | "tinyvec", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "unicode-properties" 1932 | version = "0.1.3" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 1935 | 1936 | [[package]] 1937 | name = "unicode-segmentation" 1938 | version = "1.12.0" 1939 | source = "registry+https://github.com/rust-lang/crates.io-index" 1940 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1941 | 1942 | [[package]] 1943 | name = "unicode-width" 1944 | version = "0.1.14" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1947 | 1948 | [[package]] 1949 | name = "unsafe-libyaml" 1950 | version = "0.2.11" 1951 | source = "registry+https://github.com/rust-lang/crates.io-index" 1952 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1953 | 1954 | [[package]] 1955 | name = "url" 1956 | version = "2.5.4" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1959 | dependencies = [ 1960 | "form_urlencoded", 1961 | "idna", 1962 | "percent-encoding", 1963 | ] 1964 | 1965 | [[package]] 1966 | name = "utf8_iter" 1967 | version = "1.0.4" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1970 | 1971 | [[package]] 1972 | name = "utf8parse" 1973 | version = "0.2.2" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1976 | 1977 | [[package]] 1978 | name = "vcpkg" 1979 | version = "0.2.15" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1982 | 1983 | [[package]] 1984 | name = "version_check" 1985 | version = "0.9.5" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1988 | 1989 | [[package]] 1990 | name = "wasi" 1991 | version = "0.11.0+wasi-snapshot-preview1" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1994 | 1995 | [[package]] 1996 | name = "wasite" 1997 | version = "0.1.0" 1998 | source = "registry+https://github.com/rust-lang/crates.io-index" 1999 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 2000 | 2001 | [[package]] 2002 | name = "whoami" 2003 | version = "1.6.0" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" 2006 | dependencies = [ 2007 | "redox_syscall", 2008 | "wasite", 2009 | ] 2010 | 2011 | [[package]] 2012 | name = "winapi" 2013 | version = "0.3.9" 2014 | source = "registry+https://github.com/rust-lang/crates.io-index" 2015 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2016 | dependencies = [ 2017 | "winapi-i686-pc-windows-gnu", 2018 | "winapi-x86_64-pc-windows-gnu", 2019 | ] 2020 | 2021 | [[package]] 2022 | name = "winapi-i686-pc-windows-gnu" 2023 | version = "0.4.0" 2024 | source = "registry+https://github.com/rust-lang/crates.io-index" 2025 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2026 | 2027 | [[package]] 2028 | name = "winapi-x86_64-pc-windows-gnu" 2029 | version = "0.4.0" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2032 | 2033 | [[package]] 2034 | name = "windows-sys" 2035 | version = "0.48.0" 2036 | source = "registry+https://github.com/rust-lang/crates.io-index" 2037 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2038 | dependencies = [ 2039 | "windows-targets 0.48.5", 2040 | ] 2041 | 2042 | [[package]] 2043 | name = "windows-sys" 2044 | version = "0.52.0" 2045 | source = "registry+https://github.com/rust-lang/crates.io-index" 2046 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2047 | dependencies = [ 2048 | "windows-targets 0.52.6", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "windows-sys" 2053 | version = "0.59.0" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2056 | dependencies = [ 2057 | "windows-targets 0.52.6", 2058 | ] 2059 | 2060 | [[package]] 2061 | name = "windows-targets" 2062 | version = "0.48.5" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2065 | dependencies = [ 2066 | "windows_aarch64_gnullvm 0.48.5", 2067 | "windows_aarch64_msvc 0.48.5", 2068 | "windows_i686_gnu 0.48.5", 2069 | "windows_i686_msvc 0.48.5", 2070 | "windows_x86_64_gnu 0.48.5", 2071 | "windows_x86_64_gnullvm 0.48.5", 2072 | "windows_x86_64_msvc 0.48.5", 2073 | ] 2074 | 2075 | [[package]] 2076 | name = "windows-targets" 2077 | version = "0.52.6" 2078 | source = "registry+https://github.com/rust-lang/crates.io-index" 2079 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2080 | dependencies = [ 2081 | "windows_aarch64_gnullvm 0.52.6", 2082 | "windows_aarch64_msvc 0.52.6", 2083 | "windows_i686_gnu 0.52.6", 2084 | "windows_i686_gnullvm", 2085 | "windows_i686_msvc 0.52.6", 2086 | "windows_x86_64_gnu 0.52.6", 2087 | "windows_x86_64_gnullvm 0.52.6", 2088 | "windows_x86_64_msvc 0.52.6", 2089 | ] 2090 | 2091 | [[package]] 2092 | name = "windows_aarch64_gnullvm" 2093 | version = "0.48.5" 2094 | source = "registry+https://github.com/rust-lang/crates.io-index" 2095 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2096 | 2097 | [[package]] 2098 | name = "windows_aarch64_gnullvm" 2099 | version = "0.52.6" 2100 | source = "registry+https://github.com/rust-lang/crates.io-index" 2101 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2102 | 2103 | [[package]] 2104 | name = "windows_aarch64_msvc" 2105 | version = "0.48.5" 2106 | source = "registry+https://github.com/rust-lang/crates.io-index" 2107 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2108 | 2109 | [[package]] 2110 | name = "windows_aarch64_msvc" 2111 | version = "0.52.6" 2112 | source = "registry+https://github.com/rust-lang/crates.io-index" 2113 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2114 | 2115 | [[package]] 2116 | name = "windows_i686_gnu" 2117 | version = "0.48.5" 2118 | source = "registry+https://github.com/rust-lang/crates.io-index" 2119 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2120 | 2121 | [[package]] 2122 | name = "windows_i686_gnu" 2123 | version = "0.52.6" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2126 | 2127 | [[package]] 2128 | name = "windows_i686_gnullvm" 2129 | version = "0.52.6" 2130 | source = "registry+https://github.com/rust-lang/crates.io-index" 2131 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2132 | 2133 | [[package]] 2134 | name = "windows_i686_msvc" 2135 | version = "0.48.5" 2136 | source = "registry+https://github.com/rust-lang/crates.io-index" 2137 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2138 | 2139 | [[package]] 2140 | name = "windows_i686_msvc" 2141 | version = "0.52.6" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2144 | 2145 | [[package]] 2146 | name = "windows_x86_64_gnu" 2147 | version = "0.48.5" 2148 | source = "registry+https://github.com/rust-lang/crates.io-index" 2149 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2150 | 2151 | [[package]] 2152 | name = "windows_x86_64_gnu" 2153 | version = "0.52.6" 2154 | source = "registry+https://github.com/rust-lang/crates.io-index" 2155 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2156 | 2157 | [[package]] 2158 | name = "windows_x86_64_gnullvm" 2159 | version = "0.48.5" 2160 | source = "registry+https://github.com/rust-lang/crates.io-index" 2161 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2162 | 2163 | [[package]] 2164 | name = "windows_x86_64_gnullvm" 2165 | version = "0.52.6" 2166 | source = "registry+https://github.com/rust-lang/crates.io-index" 2167 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2168 | 2169 | [[package]] 2170 | name = "windows_x86_64_msvc" 2171 | version = "0.48.5" 2172 | source = "registry+https://github.com/rust-lang/crates.io-index" 2173 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2174 | 2175 | [[package]] 2176 | name = "windows_x86_64_msvc" 2177 | version = "0.52.6" 2178 | source = "registry+https://github.com/rust-lang/crates.io-index" 2179 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2180 | 2181 | [[package]] 2182 | name = "writeable" 2183 | version = "0.6.1" 2184 | source = "registry+https://github.com/rust-lang/crates.io-index" 2185 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 2186 | 2187 | [[package]] 2188 | name = "yoke" 2189 | version = "0.8.0" 2190 | source = "registry+https://github.com/rust-lang/crates.io-index" 2191 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 2192 | dependencies = [ 2193 | "serde", 2194 | "stable_deref_trait", 2195 | "yoke-derive", 2196 | "zerofrom", 2197 | ] 2198 | 2199 | [[package]] 2200 | name = "yoke-derive" 2201 | version = "0.8.0" 2202 | source = "registry+https://github.com/rust-lang/crates.io-index" 2203 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 2204 | dependencies = [ 2205 | "proc-macro2", 2206 | "quote", 2207 | "syn", 2208 | "synstructure", 2209 | ] 2210 | 2211 | [[package]] 2212 | name = "zerocopy" 2213 | version = "0.8.25" 2214 | source = "registry+https://github.com/rust-lang/crates.io-index" 2215 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 2216 | dependencies = [ 2217 | "zerocopy-derive", 2218 | ] 2219 | 2220 | [[package]] 2221 | name = "zerocopy-derive" 2222 | version = "0.8.25" 2223 | source = "registry+https://github.com/rust-lang/crates.io-index" 2224 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 2225 | dependencies = [ 2226 | "proc-macro2", 2227 | "quote", 2228 | "syn", 2229 | ] 2230 | 2231 | [[package]] 2232 | name = "zerofrom" 2233 | version = "0.1.6" 2234 | source = "registry+https://github.com/rust-lang/crates.io-index" 2235 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2236 | dependencies = [ 2237 | "zerofrom-derive", 2238 | ] 2239 | 2240 | [[package]] 2241 | name = "zerofrom-derive" 2242 | version = "0.1.6" 2243 | source = "registry+https://github.com/rust-lang/crates.io-index" 2244 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2245 | dependencies = [ 2246 | "proc-macro2", 2247 | "quote", 2248 | "syn", 2249 | "synstructure", 2250 | ] 2251 | 2252 | [[package]] 2253 | name = "zeroize" 2254 | version = "1.8.1" 2255 | source = "registry+https://github.com/rust-lang/crates.io-index" 2256 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2257 | 2258 | [[package]] 2259 | name = "zerotrie" 2260 | version = "0.2.2" 2261 | source = "registry+https://github.com/rust-lang/crates.io-index" 2262 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 2263 | dependencies = [ 2264 | "displaydoc", 2265 | "yoke", 2266 | "zerofrom", 2267 | ] 2268 | 2269 | [[package]] 2270 | name = "zerovec" 2271 | version = "0.11.2" 2272 | source = "registry+https://github.com/rust-lang/crates.io-index" 2273 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 2274 | dependencies = [ 2275 | "yoke", 2276 | "zerofrom", 2277 | "zerovec-derive", 2278 | ] 2279 | 2280 | [[package]] 2281 | name = "zerovec-derive" 2282 | version = "0.11.1" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 2285 | dependencies = [ 2286 | "proc-macro2", 2287 | "quote", 2288 | "syn", 2289 | ] 2290 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/db", "crates/core", "crates/cli"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.4.0" 7 | authors = ["Ethan Roseman "] 8 | edition = "2024" 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/etheteck/coddog" -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 Ethan Roseman. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ethan Roseman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coddog - the dog that sniffs for cod 2 | 3 | coddog allows you to more efficiently decompile binaries by reducing redundant work. Whether it's identifying library functions, de-duplicating code, or looking for ways to get the codegen you want for that one portion of a function, coddog will find your cod! 4 | 5 | ## Features 6 | 7 | ### **match**: Function matching 8 | 9 | Find functions that are similar to a query function 10 | 11 | ``` 12 | ~/repos/pokemonsnap$ coddog match func_80348C08_828378 -t 0.7 13 | 100.00% - func_802ECC44_5E9D14 (decompiled) 14 | 100.00% - func_802E01F4_6C7FD4 (decompiled) 15 | 100.00% - func_802C57A4_647C54 (decompiled) 16 | 73.33% - finishLevel (decompiled) 17 | 73.10% - func_802D9DD8_6C1BB8 18 | 71.88% - osAiSetFrequency (decompiled) 19 | 70.89% - func_800E1930_A08EC0 (decompiled) 20 | 70.19% - func_802D0D0C_7AA29C 21 | ``` 22 | 23 | ### **cluster**: Function clustering 24 | 25 | Find clusters of functions that are identical or near-identical in one binary. This can be useful for de-duplicating redundant code and turning common functions into #includes. 26 | 27 | ``` 28 | ~/repos/pokemonsnap$ coddog cluster -m 10 29 | Cluster func_802C8998_7A1F28 has 23 symbols 30 | Cluster func_802E1110_6C8EF0 has 12 symbols 31 | Cluster func_802C2D00_6451B0 has 12 symbols 32 | Cluster func_802CA858_7A3DE8 has 10 symbols 33 | Cluster func_beach_802C68D8 has 8 symbols 34 | ``` 35 | 36 | ### **submatch**: Partial function matching 37 | 38 | Find n-length segments of code that are common between the ones found in the given query function and all other functions in a binary (and soon, beyond) 39 | 40 | ``` 41 | ~/repos/pokemonsnap$ coddog submatch finishLevel 30 42 | func_credits_801DE060 (decompiled): 43 | query [41-77] matches func_credits_801DE060 [insn 101-137] (36 total) 44 | func_800A081C: 45 | query [42-76] matches func_800A081C [insn 165-199] (34 total) 46 | func_8035464C_4F4A5C (decompiled): 47 | query [43-77] matches func_8035464C_4F4A5C [insn 81-115] (34 total) 48 | updateIdle (decompiled): 49 | query [23-89] matches updateIdle [insn 107-173] (66 total) 50 | ``` 51 | 52 | ## Experimental features 53 | 54 | ### **compare2**: Find common functions between one binary and another 55 | 56 | ``` 57 | ~/repos/pokemonsnap$ coddog compare2 decomp.yaml us ~/repos/stadium/decomp.yaml us 58 | alMainBusPull (decompiled) - alMainBusPull (decompiled) (98.61%) 59 | __ll_div (decompiled) - __ll_div (decompiled) (100.00%) 60 | __osIdCheckSum (decompiled) - __osIdCheckSum (decompiled) (100.00%) 61 | __ull_to_d (decompiled) - __ull_to_d (decompiled) (100.00%) 62 | __osSumcalc (decompiled) - __osSumcalc (decompiled) (100.00%) 63 | __ll_lshift (decompiled) - __ll_lshift (decompiled) (100.00%) 64 | Vec3fDiff (decompiled) - func_8000E958 (100.00%) 65 | ``` 66 | 67 | ### **compare-n**: Find common functions between one binary and multiple others 68 | ``` 69 | ~/repos/pokemonsnap$ coddog compare-n decomp.yaml us /home/ethteck/repos/papermario/decomp.yaml 70 | Comparing Pokemon Snap US to Paper Mario US: 71 | func_80369F80_83D730 (decompiled) - npc_set_palswap_2 (decompiled) (90.91%) 72 | alFxParam (decompiled) - au_SEFCmd_06_FineTune (decompiled) (91.67%) 73 | alLink (decompiled) - alLink (decompiled) (93.33%) 74 | alUnlink (decompiled) - alUnlink (decompiled) (90.91%) 75 | func_800C1E04_5ECA4 - osFlashWriteBuffer (decompiled) (92.00%) 76 | 77 | Comparing Pokemon Snap US to Paper Mario PAL: 78 | No matches found 79 | 80 | Comparing Pokemon Snap US to Paper Mario iQue: 81 | func_80369F80_83D730 (decompiled) - npc_set_palswap_2 (decompiled) (90.91%) 82 | alFxParam (decompiled) - au_SEFCmd_06_FineTune (decompiled) (91.67%) 83 | alLink (decompiled) - alLink (decompiled) (93.33%) 84 | alUnlink (decompiled) - alUnlink (decompiled) (90.91%) 85 | 86 | Comparing Pokemon Snap US to Paper Mario JP: 87 | func_80369F80_83D730 (decompiled) - npc_set_palswap_2 (decompiled) (90.91%) 88 | alFxParam (decompiled) - au_SEFCmd_06_FineTune (decompiled) (91.67%) 89 | alLink (decompiled) - alLink (decompiled) (93.33%) 90 | alUnlink (decompiled) - alUnlink (decompiled) (90.91%) 91 | func_800C1E04_5ECA4 - osFlashWriteBuffer (decompiled) (92.00%) 92 | ``` 93 | 94 | ### Configuration 95 | coddog reads [decomp.yaml](https://github.com/ethteck/decomp_settings) files to understand the attributes of a project. -------------------------------------------------------------------------------- /crates/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coddog-cli" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | readme = "../../README.md" 9 | description = """ 10 | A tool for finding matching sequences of code among binaries 11 | """ 12 | 13 | [dependencies] 14 | anyhow = "1.0" 15 | clap = { version = "4.5.39", features = ["derive"] } 16 | coddog-db = { path = "../db" } 17 | coddog-core = { path = "../core" } 18 | colored = "3.0.0" 19 | decomp_settings = "0.0.9" 20 | dotenvy = "0.15.7" 21 | glob = "0.3.2" 22 | inquire = "0.7.5" 23 | itertools = "0.14.0" 24 | pbr = "1.1.1" 25 | sqlx = { version = "0.8", features = ["macros", "migrate", "runtime-tokio", "postgres"] } 26 | tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] } 27 | -------------------------------------------------------------------------------- /crates/cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, anyhow}; 2 | use clap::{Parser, Subcommand, ValueEnum}; 3 | use coddog_core::cluster::get_clusters; 4 | use coddog_core::{ 5 | self as core, Binary, Platform, Symbol, get_submatches, 6 | ingest::{read_elf, read_map}, 7 | }; 8 | use coddog_db::{DBSymbol, DBWindow, db_delete_project}; 9 | use colored::*; 10 | use decomp_settings::{config::Version, read_config, scan_for_config}; 11 | use dotenvy::dotenv; 12 | use glob::glob; 13 | use inquire::Select; 14 | use itertools::Itertools; 15 | use pbr::ProgressBar; 16 | use sqlx::{Pool, Postgres}; 17 | use std::collections::HashMap; 18 | use std::time::SystemTime; 19 | use std::{ 20 | fs, 21 | path::{Path, PathBuf}, 22 | }; 23 | 24 | const BINARY_COLORS: [Color; 6] = [ 25 | Color::BrightGreen, 26 | Color::BrightYellow, 27 | Color::BrightBlue, 28 | Color::BrightMagenta, 29 | Color::BrightCyan, 30 | Color::BrightRed, 31 | ]; 32 | 33 | /// Find cod 34 | #[derive(Parser)] 35 | #[command(author, version, about, long_about = None)] 36 | struct Cli { 37 | #[command(subcommand)] 38 | command: Commands, 39 | } 40 | #[derive(Subcommand)] 41 | 42 | enum Commands { 43 | /// Find functions similar to the query function 44 | Match { 45 | /// Name of the query function 46 | query: String, 47 | 48 | /// Similarity threshold 49 | #[arg(short, long, default_value = "0.985")] 50 | threshold: f32, 51 | }, 52 | 53 | /// Cluster functions by similarity, showing possible duplicates 54 | Cluster { 55 | /// Similarity threshold 56 | #[arg(short, long, default_value = "0.985")] 57 | threshold: f32, 58 | 59 | /// Minimum length of functions (in number of instructions) to consider 60 | #[arg(short, long, default_value = "5")] 61 | min_len: usize, 62 | }, 63 | 64 | /// Find chunks of code similar to those in the query function 65 | Submatch { 66 | /// Name of the query function 67 | query: String, 68 | 69 | /// Window size (smaller values will find more matches but take longer) 70 | window_size: usize, 71 | }, 72 | 73 | /// Compare two binaries, showing the functions in common between them 74 | Compare2 { 75 | /// Path to the first decomp.yaml 76 | yaml1: PathBuf, 77 | 78 | /// Version to compare from the first yaml 79 | version1: String, 80 | 81 | /// Path to the second decomp.yaml 82 | yaml2: PathBuf, 83 | 84 | /// Version to compare from the second yaml 85 | version2: String, 86 | 87 | /// Similarity threshold 88 | #[arg(short, long, default_value = "0.985")] 89 | threshold: f32, 90 | 91 | /// Minimum length of functions (in number of instructions) to consider 92 | #[arg(short, long, default_value = "5")] 93 | min_len: usize, 94 | }, 95 | 96 | /// Compare a binary in one project to one or more others, showing the functions in common between them 97 | CompareN { 98 | /// Path to the main decomp.yaml 99 | main_yaml: PathBuf, 100 | 101 | /// Version to compare from the main yaml 102 | main_version: String, 103 | 104 | /// Path to other projects' decomp.yaml files 105 | other_yamls: Vec, 106 | }, 107 | 108 | /// Compare one raw binary to one or more projects' binaries, showing the functions in common between them 109 | CompareRaw { 110 | /// Path to the main binary 111 | query_bin: PathBuf, 112 | 113 | /// Path to other projects' decomp.yaml files 114 | yamls: Vec, 115 | }, 116 | /// Database management commands 117 | #[command(subcommand)] 118 | Db(DbCommands), 119 | } 120 | 121 | #[derive(Subcommand)] 122 | enum DbCommands { 123 | /// Add a new project to the database, given a decomp.yaml file 124 | AddProject { 125 | /// Path to the project's decomp.yaml file 126 | yaml: PathBuf, 127 | }, 128 | /// Delete a project from the database, removing its sources, symbols, and hashes 129 | DeleteProject { 130 | /// Name of the project to delete 131 | name: String, 132 | }, 133 | /// Search the database for matches of a given symbol 134 | Match { 135 | /// Name of the query function 136 | query: String, 137 | /// Specificity of match 138 | match_type: MatchType, 139 | }, 140 | /// Search the database for submatches of a given symbol 141 | Submatch { 142 | /// Name of the query function 143 | query: String, 144 | /// Window size (smaller values will find more matches but take longer) 145 | window_size: usize, 146 | }, 147 | } 148 | 149 | #[derive(ValueEnum, Clone, PartialEq)] 150 | enum MatchType { 151 | /// Only opcodes are compared 152 | Opcode, 153 | /// Opcodes and some operands are compared 154 | Equivalent, 155 | /// Exact bytes are compared 156 | Exact, 157 | } 158 | 159 | fn cli_fullname(sym: &Symbol) -> String { 160 | format!( 161 | "{}{}", 162 | sym.name.clone(), 163 | if sym.is_decompiled { 164 | " (decompiled)".green() 165 | } else { 166 | "".normal() 167 | } 168 | ) 169 | } 170 | 171 | fn cli_name_colored(sym: &Symbol, color: Color) -> String { 172 | format!("{}", sym.name.clone().color(color)) 173 | } 174 | 175 | fn do_match(query: &str, symbols: &[Symbol], threshold: f32) { 176 | struct FunctionMatch<'a> { 177 | symbol: &'a Symbol, 178 | score: f32, 179 | } 180 | 181 | let Some(query_sym) = symbols.iter().find(|s| s.name == query) else { 182 | println!("Symbol {query:} not found"); 183 | return; 184 | }; 185 | 186 | let mut matches: Vec = symbols 187 | .iter() 188 | .filter(|s| s.name != query_sym.name) 189 | .map(|s| FunctionMatch { 190 | symbol: s, 191 | score: core::diff_symbols(query_sym, s, threshold), 192 | }) 193 | .filter(|m| m.score > threshold) 194 | .collect(); 195 | 196 | // sort by score descending 197 | matches.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap()); 198 | 199 | for m in matches { 200 | println!("{:.2}% - {}", m.score * 100.0, cli_fullname(m.symbol)); 201 | } 202 | } 203 | 204 | fn do_submatch(query: &str, symbols: &[Symbol], window_size: usize) { 205 | let Some(query_sym) = symbols.iter().find(|s| s.name == query) else { 206 | println!("Symbol {query:} not found"); 207 | return; 208 | }; 209 | 210 | let query_hashes = query_sym.get_opcode_hashes(window_size); 211 | 212 | for s in symbols { 213 | if s == query_sym { 214 | continue; 215 | } 216 | 217 | if query_sym.opcodes == s.opcodes { 218 | let match_pct = if query_sym.bytes == s.bytes { 219 | "100%" 220 | } else { 221 | "99%" 222 | }; 223 | println!("{} matches {}", cli_fullname(s), match_pct); 224 | continue; 225 | } 226 | 227 | let hashes = s.get_opcode_hashes(window_size); 228 | 229 | let pair_matches = get_submatches(&query_hashes, &hashes, window_size); 230 | 231 | if pair_matches.is_empty() { 232 | continue; 233 | } 234 | 235 | println!("{}:", cli_fullname(s)); 236 | 237 | for m in pair_matches { 238 | let query_str = format!("query [{}-{}]", m.offset1, m.offset1 + m.length); 239 | let target_str = format!( 240 | "{} [insn {}-{}] ({} total)", 241 | s.name, 242 | m.offset2, 243 | m.offset2 + m.length, 244 | m.length 245 | ); 246 | println!("\t{query_str} matches {target_str}"); 247 | } 248 | } 249 | } 250 | 251 | pub fn do_cluster(symbols: &[Symbol], threshold: f32, min_len: usize) { 252 | let clusters = get_clusters(symbols, threshold, min_len); 253 | 254 | // Print clusters 255 | for cluster in clusters.iter().filter(|c| c.size() > 1) { 256 | println!( 257 | "Cluster {} has {} symbols", 258 | cluster.syms[0].name, 259 | cluster.size() 260 | ); 261 | } 262 | } 263 | 264 | fn get_full_path(base_dir: &Path, config_path: Option) -> Option { 265 | config_path.map(|path| { 266 | if path.is_relative() { 267 | base_dir.join(path) 268 | } else { 269 | path.clone() 270 | } 271 | }) 272 | } 273 | 274 | fn get_unmatched_funcs(base_dir: &Path, config: &Version) -> Option> { 275 | get_full_path(base_dir, config.paths.asm.clone()).map(|asm_dir| { 276 | let mut unmatched_funcs = Vec::new(); 277 | 278 | for s_file in glob(asm_dir.join("**/*.s").to_str().unwrap()).unwrap() { 279 | // add filename minus extension to vec 280 | let s_file = s_file.unwrap(); 281 | let s_file_stem = s_file.file_stem().unwrap().to_str().unwrap(); 282 | unmatched_funcs.push(s_file_stem.to_string()); 283 | } 284 | unmatched_funcs 285 | }) 286 | } 287 | 288 | fn collect_symbols(config: &Version, base_dir: &Path, platform: &str) -> Result> { 289 | let unmatched_funcs = get_unmatched_funcs(base_dir, config); 290 | let platform = 291 | Platform::of(platform).unwrap_or_else(|| panic!("Invalid platform: {}", platform)); 292 | 293 | if let Some(elf_path) = get_full_path(base_dir, config.paths.elf.clone()) { 294 | let elf_data = fs::read(elf_path)?; 295 | return read_elf(platform, &unmatched_funcs, elf_data); 296 | } 297 | 298 | if let (Some(target), Some(map_path)) = ( 299 | get_full_path(base_dir, Some(config.paths.target.clone())), 300 | get_full_path(base_dir, Some(config.paths.map.clone())), 301 | ) { 302 | let target_bytes = fs::read(target)?; 303 | let map_str = fs::read_to_string(map_path)?; 304 | return read_map(platform, unmatched_funcs, target_bytes, &map_str); 305 | } 306 | 307 | Err(anyhow!("No elf or mapfile found")) 308 | } 309 | 310 | fn do_compare_binaries(bin1: &Binary, bin2: &Binary, threshold: f32, min_len: usize) { 311 | let mut matched_syms: Vec<(&Symbol, &Symbol, f32)> = Vec::new(); 312 | 313 | bin1.symbols 314 | .iter() 315 | .filter(|s| s.opcodes.len() >= min_len) 316 | .for_each(|sym| { 317 | let mut best_match: Option<(&Symbol, f32)> = None; 318 | 319 | for sym2 in bin2.symbols.iter().filter(|s| s.opcodes.len() >= min_len) { 320 | let score = core::diff_symbols(sym, sym2, threshold); 321 | if score > threshold { 322 | if let Some((_, best_score)) = best_match { 323 | if score > best_score { 324 | best_match = Some((sym2, score)); 325 | } 326 | } else { 327 | best_match = Some((sym2, score)); 328 | } 329 | } 330 | } 331 | 332 | if let Some((best_sym, score)) = best_match { 333 | matched_syms.push((sym, best_sym, score)); 334 | } 335 | }); 336 | 337 | match matched_syms.len() { 338 | 0 => { 339 | println!("No matches found"); 340 | } 341 | _ => { 342 | let mut both_decompiled: Vec<(&Symbol, &Symbol, f32)> = vec![]; 343 | let mut only1_decompiled: Vec<(&Symbol, &Symbol, f32)> = vec![]; 344 | let mut only2_decompiled: Vec<(&Symbol, &Symbol, f32)> = vec![]; 345 | let mut both_undecompiled: Vec<(&Symbol, &Symbol, f32)> = vec![]; 346 | 347 | for (sym1, sym2, score) in matched_syms { 348 | if sym1.is_decompiled && sym2.is_decompiled { 349 | both_decompiled.push((sym1, sym2, score)); 350 | } else if sym1.is_decompiled { 351 | only1_decompiled.push((sym1, sym2, score)); 352 | } else if sym2.is_decompiled { 353 | only2_decompiled.push((sym1, sym2, score)); 354 | } else { 355 | both_undecompiled.push((sym1, sym2, score)); 356 | } 357 | } 358 | 359 | if !both_decompiled.is_empty() { 360 | println!( 361 | "\nDecompiled in {} and {}:", 362 | bin1.name.color(BINARY_COLORS[0]), 363 | bin2.name.color(BINARY_COLORS[1]) 364 | ); 365 | for (sym1, sym2, score) in both_decompiled { 366 | println!( 367 | "{} - {} ({:.2}%)", 368 | cli_name_colored(sym1, BINARY_COLORS[0]), 369 | cli_name_colored(sym2, BINARY_COLORS[1]), 370 | score * 100.0 371 | ); 372 | } 373 | } 374 | 375 | if !only1_decompiled.is_empty() { 376 | println!( 377 | "\nOnly decompiled in {}:", 378 | bin1.name.color(BINARY_COLORS[0]) 379 | ); 380 | for (sym1, sym2, score) in only1_decompiled { 381 | println!( 382 | "{} - {} ({:.2}%)", 383 | cli_name_colored(sym1, BINARY_COLORS[0]), 384 | cli_name_colored(sym2, BINARY_COLORS[1]), 385 | score * 100.0 386 | ); 387 | } 388 | } 389 | 390 | if !only2_decompiled.is_empty() { 391 | println!( 392 | "\nOnly decompiled in {}:", 393 | bin2.name.color(BINARY_COLORS[1]) 394 | ); 395 | for (sym1, sym2, score) in only2_decompiled { 396 | println!( 397 | "{} - {} ({:.2}%)", 398 | cli_name_colored(sym1, BINARY_COLORS[0]), 399 | cli_name_colored(sym2, BINARY_COLORS[1]), 400 | score * 100.0 401 | ); 402 | } 403 | } 404 | 405 | if !both_undecompiled.is_empty() { 406 | println!("\nDecompiled in neither:"); 407 | for (sym1, sym2, score) in both_undecompiled { 408 | println!( 409 | "{} - {} ({:.2}%)", 410 | cli_name_colored(sym1, BINARY_COLORS[0]), 411 | cli_name_colored(sym2, BINARY_COLORS[1]), 412 | score * 100.0 413 | ); 414 | } 415 | } 416 | } 417 | } 418 | } 419 | 420 | fn get_cwd_symbols() -> Result> { 421 | let config = scan_for_config()?; 422 | 423 | let version = if config.versions.len() > 1 { 424 | let res = Select::new("Which version do you want to use?", config.versions).prompt(); 425 | res? 426 | } else { 427 | config.versions.first().unwrap().clone() 428 | }; 429 | 430 | collect_symbols(&version, &std::env::current_dir()?, &config.platform) 431 | } 432 | 433 | async fn db_search_symbol_by_name(conn: Pool, name: &str) -> Result { 434 | let symbols = coddog_db::db_query_symbols_by_name(conn, name).await?; 435 | 436 | if symbols.is_empty() { 437 | return Err(anyhow!("No symbols found with the name '{}'", name)); 438 | } 439 | 440 | if symbols.len() > 1 { 441 | let res = Select::new("Which symbol do you want to check?", symbols).prompt(); 442 | Ok(res?) 443 | } else { 444 | Ok(symbols.first().unwrap().clone()) 445 | } 446 | } 447 | 448 | async fn db_search_project_by_name(conn: Pool, name: &str) -> Result { 449 | let projects = coddog_db::db_query_projects_by_name(conn, name).await?; 450 | 451 | if projects.is_empty() { 452 | return Err(anyhow!("No projects found with the name '{}'", name)); 453 | } 454 | 455 | if projects.len() > 1 { 456 | let res = Select::new("Which project do you want to check?", projects).prompt(); 457 | Ok(res?.id) 458 | } else { 459 | Ok(projects.first().unwrap().id) 460 | } 461 | } 462 | 463 | #[tokio::main] 464 | async fn main() -> Result<()> { 465 | dotenv().ok(); 466 | let cli: Cli = Cli::parse(); 467 | 468 | match &cli.command { 469 | Commands::Match { query, threshold } => { 470 | let symbols = get_cwd_symbols()?; 471 | do_match(query, &symbols, *threshold); 472 | } 473 | Commands::Submatch { query, window_size } => { 474 | let symbols = get_cwd_symbols()?; 475 | do_submatch(query, &symbols, *window_size); 476 | } 477 | Commands::Cluster { threshold, min_len } => { 478 | let symbols = get_cwd_symbols()?; 479 | do_cluster(&symbols, *threshold, *min_len); 480 | } 481 | Commands::Compare2 { 482 | yaml1, 483 | version1, 484 | yaml2, 485 | version2, 486 | threshold, 487 | min_len, 488 | } => { 489 | let config1 = read_config(yaml1.clone())?; 490 | let config2 = read_config(yaml2.clone())?; 491 | 492 | let version1 = config1.get_version_by_name(version1).unwrap(); 493 | let version2 = config2.get_version_by_name(version2).unwrap(); 494 | 495 | let symbols1 = collect_symbols(&version1, yaml1.parent().unwrap(), &config1.platform)?; 496 | let symbols2 = collect_symbols(&version2, yaml2.parent().unwrap(), &config2.platform)?; 497 | 498 | let bin1 = Binary { 499 | name: config1.name, 500 | symbols: symbols1, 501 | }; 502 | 503 | let bin2 = Binary { 504 | name: config2.name, 505 | symbols: symbols2, 506 | }; 507 | 508 | do_compare_binaries(&bin1, &bin2, *threshold, *min_len); 509 | } 510 | Commands::CompareN { 511 | main_yaml, 512 | main_version, 513 | other_yamls, 514 | } => { 515 | let main_config = read_config(main_yaml.clone())?; 516 | let main_version = main_config.get_version_by_name(main_version).unwrap(); 517 | let main_symbols = collect_symbols( 518 | &main_version, 519 | main_yaml.parent().unwrap(), 520 | &main_config.platform, 521 | )?; 522 | 523 | let main_bin: Binary = Binary { 524 | name: main_config.name.clone(), 525 | symbols: main_symbols, 526 | }; 527 | 528 | for other_yaml in other_yamls { 529 | let other_config = read_config(other_yaml.clone())?; 530 | 531 | for other_version in &other_config.versions { 532 | let other_symbols = collect_symbols( 533 | other_version, 534 | other_yaml.parent().unwrap(), 535 | &other_config.platform.clone(), 536 | )?; 537 | 538 | let other_bin = Binary { 539 | name: other_config.name.clone(), 540 | symbols: other_symbols, 541 | }; 542 | 543 | println!( 544 | "Comparing {} {} to {} {}:", 545 | main_config.name.color(BINARY_COLORS[0]), 546 | main_version.fullname.color(BINARY_COLORS[0]), 547 | other_config.name.color(BINARY_COLORS[1]), 548 | other_version.fullname.color(BINARY_COLORS[1]) 549 | ); 550 | 551 | do_compare_binaries(&main_bin, &other_bin, 0.99, 5); 552 | println!(); 553 | } 554 | } 555 | } 556 | Commands::CompareRaw { query_bin, yamls } => { 557 | let query_bin_data = fs::read(query_bin)?; 558 | let mut symbol_hashes = HashMap::new(); 559 | let mut platform = None; 560 | let window_size = 20; 561 | 562 | for yaml in yamls { 563 | let config = read_config(yaml.clone())?; 564 | let cur_platform = Platform::of(&config.platform).unwrap(); 565 | 566 | if platform.is_none() { 567 | platform = Some(cur_platform); 568 | } else if platform.unwrap() != cur_platform { 569 | return Err(anyhow!("All projects must be for the same platform")); 570 | } 571 | 572 | for version in &config.versions { 573 | let symbols = 574 | collect_symbols(version, yaml.parent().unwrap(), &config.platform)?; 575 | for sym in symbols { 576 | if sym.opcodes.len() < window_size { 577 | continue; 578 | } 579 | let hashes = sym.get_opcode_hashes(window_size); 580 | let first_hash = *hashes.first().unwrap(); 581 | symbol_hashes.insert( 582 | first_hash, 583 | (config.name.clone(), version.fullname.clone(), sym.clone()), 584 | ); 585 | } 586 | } 587 | } 588 | 589 | let platform = 590 | platform.ok_or_else(|| anyhow!("No platform found in provided configs"))?; 591 | 592 | let opcodes = core::arch::get_opcodes(&query_bin_data, platform); 593 | for (i, hash) in core::get_hashes(&opcodes, window_size).iter().enumerate() { 594 | if let Some((project_name, version_name, symbol)) = symbol_hashes.get(hash) { 595 | if opcodes[i..i + symbol.opcodes.len()] == symbol.opcodes { 596 | println!( 597 | "0x{:X} - {} {}: {}", 598 | i * 4, 599 | project_name.color(BINARY_COLORS[0]), 600 | version_name.color(BINARY_COLORS[0]), 601 | cli_fullname(symbol) 602 | ); 603 | } 604 | } 605 | } 606 | } 607 | Commands::Db(DbCommands::AddProject { yaml }) => { 608 | let config = read_config(yaml.clone())?; 609 | let platform = Platform::of(&config.platform).unwrap(); 610 | let window_size = std::env::var("DB_WINDOW_SIZE") 611 | .expect("DB_WINDOW_SIZE must be set") 612 | .parse::()?; 613 | 614 | let pool = coddog_db::db_init().await?; 615 | let mut tx = pool.begin().await?; 616 | 617 | let project_id = coddog_db::add_project(&mut tx, &config.name, platform).await?; 618 | 619 | for version in &config.versions { 620 | let baserom_path = 621 | get_full_path(yaml.parent().unwrap(), Some(version.paths.target.clone())) 622 | .unwrap(); 623 | let source_id = 624 | coddog_db::add_source(&mut tx, project_id, &version.fullname, &baserom_path) 625 | .await?; 626 | 627 | let symbols = collect_symbols(version, yaml.parent().unwrap(), &config.platform)?; 628 | 629 | let symbol_ids = coddog_db::add_symbols(&mut tx, source_id, &symbols).await; 630 | 631 | let mut pb = ProgressBar::new(symbols.len() as u64); 632 | 633 | pb.format("[=>-]"); 634 | 635 | if config.versions.len() == 1 { 636 | pb.message("Importing hashes "); 637 | } else { 638 | pb.message(format!("Importing hashes ({}) ", version.fullname).as_str()); 639 | } 640 | 641 | for (symbol, id) in symbols.iter().zip(symbol_ids) { 642 | pb.inc(); 643 | 644 | let opcode_hashes = symbol.get_opcode_hashes(window_size); 645 | 646 | coddog_db::add_symbol_window_hashes(&mut tx, &opcode_hashes, id).await?; 647 | } 648 | println!(); 649 | } 650 | tx.commit().await?; 651 | println!("Imported project {} ", config.name); 652 | } 653 | Commands::Db(DbCommands::DeleteProject { name }) => { 654 | let pool = coddog_db::db_init().await?; 655 | 656 | let project = db_search_project_by_name(pool.clone(), name).await?; 657 | 658 | db_delete_project(pool.clone(), project).await?; 659 | println!("Deleted project {name}"); 660 | } 661 | Commands::Db(DbCommands::Match { query, match_type }) => { 662 | let pool = coddog_db::db_init().await?; 663 | 664 | let symbol = db_search_symbol_by_name(pool.clone(), query).await?; 665 | 666 | let matches = match match_type { 667 | MatchType::Opcode => { 668 | coddog_db::db_query_symbols_by_opcode_hash(pool.clone(), &symbol).await? 669 | } 670 | MatchType::Equivalent => { 671 | coddog_db::db_query_symbols_by_equiv_hash(pool.clone(), &symbol).await? 672 | } 673 | MatchType::Exact => { 674 | coddog_db::db_query_symbols_by_exact_hash(pool.clone(), &symbol).await? 675 | } 676 | }; 677 | 678 | if matches.is_empty() { 679 | println!("No matches found"); 680 | } else { 681 | for sym in matches { 682 | println!("{} - {} {}", sym.name, sym.project_name, sym.source_name); 683 | } 684 | } 685 | } 686 | Commands::Db(DbCommands::Submatch { query, window_size }) => { 687 | let db_window_size = std::env::var("DB_WINDOW_SIZE") 688 | .expect("DB_WINDOW_SIZE must be set") 689 | .parse::()?; 690 | 691 | if *window_size < db_window_size { 692 | return Err(anyhow!("Window size must be at least {}", db_window_size)); 693 | } 694 | 695 | let pool = coddog_db::db_init().await?; 696 | 697 | let symbol = db_search_symbol_by_name(pool.clone(), query).await?; 698 | 699 | let before_time = SystemTime::now(); 700 | let matching_hashes = coddog_db::db_query_windows_by_symbol_id( 701 | pool.clone(), 702 | symbol.id, 703 | (window_size - db_window_size) as i64, 704 | ) 705 | .await?; 706 | 707 | match before_time.elapsed() { 708 | Ok(elapsed) => { 709 | println!("Big query took {}ms", elapsed.as_millis()); 710 | } 711 | Err(e) => { 712 | println!("Error: {e:?}"); 713 | } 714 | } 715 | 716 | if matching_hashes.is_empty() { 717 | println!("No matches found"); 718 | return Ok(()); 719 | } 720 | 721 | let mut project_map: HashMap = HashMap::new(); 722 | let mut source_map: HashMap = HashMap::new(); 723 | let mut symbol_map: HashMap = HashMap::new(); 724 | 725 | let results = SubmatchResults::from_db_hashes( 726 | &matching_hashes, 727 | &mut project_map, 728 | &mut source_map, 729 | &mut symbol_map, 730 | ); 731 | 732 | println!( 733 | "{}", 734 | results.to_string(*window_size, &project_map, &source_map, &symbol_map) 735 | ); 736 | } 737 | } 738 | 739 | Ok(()) 740 | } 741 | 742 | struct SubmatchResults { 743 | projects: Vec, 744 | } 745 | 746 | impl SubmatchResults { 747 | fn from_db_hashes( 748 | hashes: &[DBWindow], 749 | project_map: &mut HashMap, 750 | source_map: &mut HashMap, 751 | symbol_map: &mut HashMap, 752 | ) -> Self { 753 | let mut results = SubmatchResults { projects: vec![] }; 754 | 755 | for (project_id, project_rows) in &hashes.iter().chunk_by(|h| h.project_id) { 756 | let project_rows = project_rows.collect_vec(); 757 | let project_name = &project_rows.first().unwrap().project_name; 758 | project_map.insert(project_id, project_name.to_string()); 759 | 760 | let mut project_results = SubmatchProjectResults { 761 | id: project_id, 762 | sources: vec![], 763 | }; 764 | 765 | for (source_id, source_rows) in &project_rows.iter().chunk_by(|h| h.source_id) { 766 | let source_rows = source_rows.collect_vec(); 767 | let source_name = &source_rows.first().unwrap().source_name; 768 | source_map.insert(source_id, source_name.to_string()); 769 | 770 | let mut source_results = SubmatchSourceResults { 771 | id: source_id, 772 | symbols: vec![], 773 | }; 774 | 775 | for (symbol_id, symbol_rows) in &source_rows.into_iter().chunk_by(|h| h.symbol_id) { 776 | let symbol_rows = symbol_rows.collect_vec(); 777 | let sym_name = &symbol_rows.first().unwrap().symbol_name; 778 | symbol_map.insert(symbol_id, sym_name.clone()); 779 | 780 | let sym_results = SubmatchSymbolResults { 781 | id: symbol_id, 782 | slices: symbol_rows 783 | .into_iter() 784 | .map(|h| SubmatchSliceResults { 785 | query_start: h.query_start, 786 | match_start: h.match_start, 787 | length: h.length, 788 | }) 789 | .collect(), 790 | }; 791 | source_results.symbols.push(sym_results); 792 | } 793 | project_results.sources.push(source_results); 794 | } 795 | results.projects.push(project_results); 796 | } 797 | 798 | results 799 | } 800 | 801 | fn to_string( 802 | &self, 803 | window_size: usize, 804 | project_map: &HashMap, 805 | source_map: &HashMap, 806 | symbol_map: &HashMap, 807 | ) -> String { 808 | let mut result = String::new(); 809 | for project in &self.projects { 810 | result.push_str(&format!("{}:\n", project_map.get(&project.id).unwrap())); 811 | for source in &project.sources { 812 | result.push_str(&format!( 813 | "\tVersion {}:\n", 814 | source_map.get(&source.id).unwrap() 815 | )); 816 | for symbol in &source.symbols { 817 | result.push_str(&format!("\t\t{}:\n", symbol_map.get(&symbol.id).unwrap())); 818 | for slice in &symbol.slices { 819 | result.push_str(&format!( 820 | "\t\t\t[{}/{}] ({} insns)\n", 821 | slice.query_start, 822 | slice.match_start, 823 | slice.length as usize + window_size - 1 824 | )); 825 | } 826 | } 827 | } 828 | } 829 | result 830 | } 831 | } 832 | 833 | struct SubmatchProjectResults { 834 | id: i64, 835 | sources: Vec, 836 | } 837 | 838 | struct SubmatchSourceResults { 839 | id: i64, 840 | symbols: Vec, 841 | } 842 | 843 | struct SubmatchSymbolResults { 844 | id: i64, 845 | slices: Vec, 846 | } 847 | 848 | struct SubmatchSliceResults { 849 | query_start: i32, 850 | match_start: i32, 851 | length: i64, 852 | } 853 | -------------------------------------------------------------------------------- /crates/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coddog-core" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | readme = "../../README.md" 9 | description = """ 10 | A library for finding matching sequences of code among binaries 11 | """ 12 | 13 | [dependencies] 14 | anyhow = "1.0" 15 | editdistancek = "1.0.2" 16 | object = "0.36.7" 17 | mapfile_parser = "2.9.3" 18 | ppc750cl = "0.3.2" 19 | rabbitizer = { version = "2.0.0-alpha.3", features = ["all_extensions"] } -------------------------------------------------------------------------------- /crates/core/src/arch.rs: -------------------------------------------------------------------------------- 1 | use crate::Platform; 2 | use crate::ingest::CoddogRel; 3 | use object::Endian; 4 | use rabbitizer::IsaExtension::{R3000GTE, R5900EE}; 5 | use rabbitizer::IsaVersion::MIPS_III; 6 | use rabbitizer::Vram; 7 | use rabbitizer::operands::ValuedOperand; 8 | use std::collections::{BTreeMap, HashMap}; 9 | use std::hash::{DefaultHasher, Hash, Hasher}; 10 | 11 | fn get_rabbitizer_instruction(word: u32, platform: Platform) -> rabbitizer::Instruction { 12 | rabbitizer::Instruction::new( 13 | word, 14 | Vram::new(0), 15 | match platform { 16 | Platform::N64 => rabbitizer::InstructionFlags::new(MIPS_III), 17 | Platform::Psx => rabbitizer::InstructionFlags::new_extension(R3000GTE), 18 | Platform::Ps2 => rabbitizer::InstructionFlags::new_extension(R5900EE), 19 | _ => unreachable!(), 20 | }, 21 | ) 22 | } 23 | 24 | pub fn get_opcodes(bytes: &[u8], platform: Platform) -> Vec { 25 | match platform { 26 | Platform::N64 | Platform::Psx | Platform::Ps2 => bytes 27 | .chunks_exact(4) 28 | .map(|chunk| { 29 | let code = platform 30 | .endianness() 31 | .read_u32_bytes(chunk.try_into().unwrap()); 32 | let instruction = get_rabbitizer_instruction(code, platform); 33 | instruction.opcode() as u16 34 | }) 35 | .collect(), 36 | Platform::Gc | Platform::Wii => bytes 37 | .chunks_exact(4) 38 | .map(|c| { 39 | ppc750cl::Opcode::_detect( 40 | platform.endianness().read_u32_bytes(c.try_into().unwrap()), 41 | ) as u16 42 | }) 43 | .collect(), 44 | } 45 | } 46 | 47 | pub(crate) fn get_equivalence_hash( 48 | bytes: &[u8], 49 | vram: usize, 50 | platform: Platform, 51 | relocations: &BTreeMap, 52 | ) -> u64 { 53 | let mut hasher = DefaultHasher::new(); 54 | 55 | let mut reloc_ids = HashMap::new(); 56 | 57 | match platform { 58 | Platform::N64 | Platform::Psx | Platform::Ps2 => { 59 | let mut hashed_reloc; 60 | 61 | for (i, chunk) in bytes.chunks_exact(4).enumerate() { 62 | let code = platform 63 | .endianness() 64 | .read_u32_bytes(chunk.try_into().unwrap()); 65 | let cur_vram = vram + i * 4; 66 | 67 | // Hash the unique id for the relocation entry rather than the specifics 68 | if let Some(reloc) = relocations.get(&(cur_vram as u64)) { 69 | let next_id = reloc_ids.len(); 70 | let hash_id = reloc_ids.entry(reloc).or_insert(next_id); 71 | hash_id.hash(&mut hasher); 72 | hashed_reloc = true; 73 | } else { 74 | hashed_reloc = false; 75 | } 76 | 77 | let instruction = get_rabbitizer_instruction(code, platform); 78 | 79 | // hash opcode 80 | instruction.opcode().hash(&mut hasher); 81 | 82 | // hash operands 83 | for vo in instruction.valued_operands_iter() { 84 | match vo { 85 | ValuedOperand::ALL_EMPTY() => vo.hash(&mut hasher), 86 | ValuedOperand::core_rs(_) => vo.hash(&mut hasher), 87 | ValuedOperand::core_rt(_) => vo.hash(&mut hasher), 88 | ValuedOperand::core_rd(_) => vo.hash(&mut hasher), 89 | ValuedOperand::core_sa(_) => vo.hash(&mut hasher), 90 | ValuedOperand::core_zero() => vo.hash(&mut hasher), 91 | ValuedOperand::core_cop0d(_) => vo.hash(&mut hasher), 92 | ValuedOperand::core_cop0cd(_) => vo.hash(&mut hasher), 93 | ValuedOperand::core_fs(_) => vo.hash(&mut hasher), 94 | ValuedOperand::core_ft(_) => vo.hash(&mut hasher), 95 | ValuedOperand::core_fd(_) => vo.hash(&mut hasher), 96 | // ValuedOperand::core_cop1cs(_) => {} 97 | // ValuedOperand::core_cop2t(_) => {} 98 | // ValuedOperand::core_cop2d(_) => {} 99 | // ValuedOperand::core_cop2cd(_) => {} 100 | // ValuedOperand::core_op(_) => {} 101 | // ValuedOperand::core_hint(_) => {} 102 | // ValuedOperand::core_code(_, _) => {} 103 | // ValuedOperand::core_code_lower(_) => {} 104 | // ValuedOperand::core_copraw(_) => {} 105 | ValuedOperand::core_label(_) => { 106 | if !hashed_reloc { 107 | vo.hash(&mut hasher); 108 | } 109 | } 110 | ValuedOperand::core_immediate(_) => { 111 | if !hashed_reloc { 112 | vo.hash(&mut hasher); 113 | } 114 | } 115 | ValuedOperand::core_branch_target_label(_) => { 116 | assert!( 117 | !hashed_reloc, 118 | "Relocation and branch target label at the same time" 119 | ); 120 | vo.hash(&mut hasher); 121 | } 122 | ValuedOperand::core_immediate_base(_, gpr) => { 123 | if !hashed_reloc { 124 | vo.hash(&mut hasher); 125 | } else { 126 | gpr.hash(&mut hasher); 127 | } 128 | } 129 | // ValuedOperand::core_maybe_rd_rs(_, _) => {} 130 | // ValuedOperand::core_maybe_zero_rs(_, _) => {} 131 | // ValuedOperand::rsp_cop0d(_) => {} 132 | // ValuedOperand::rsp_cop2cd(_) => {} 133 | // ValuedOperand::rsp_vs(_) => {} 134 | // ValuedOperand::rsp_vd(_) => {} 135 | // ValuedOperand::rsp_vt_elementhigh(_, _) => {} 136 | // ValuedOperand::rsp_vt_elementlow(_, _) => {} 137 | // ValuedOperand::rsp_vd_de(_, _) => {} 138 | // ValuedOperand::rsp_vs_index(_, _) => {} 139 | // ValuedOperand::rsp_offset_rs(_, _) => {} 140 | // ValuedOperand::r3000gte_sf(_) => {} 141 | // ValuedOperand::r3000gte_mx(_) => {} 142 | // ValuedOperand::r3000gte_v(_) => {} 143 | // ValuedOperand::r3000gte_cv(_) => {} 144 | // ValuedOperand::r3000gte_lm(_) => {} 145 | // ValuedOperand::r4000allegrex_s_vs(_) => {} 146 | // ValuedOperand::r4000allegrex_s_vt(_) => {} 147 | // ValuedOperand::r4000allegrex_s_vd(_) => {} 148 | // ValuedOperand::r4000allegrex_s_vt_imm(_) => {} 149 | // ValuedOperand::r4000allegrex_s_vd_imm(_) => {} 150 | // ValuedOperand::r4000allegrex_p_vs(_) => {} 151 | // ValuedOperand::r4000allegrex_p_vt(_) => {} 152 | // ValuedOperand::r4000allegrex_p_vd(_) => {} 153 | // ValuedOperand::r4000allegrex_t_vs(_) => {} 154 | // ValuedOperand::r4000allegrex_t_vt(_) => {} 155 | // ValuedOperand::r4000allegrex_t_vd(_) => {} 156 | // ValuedOperand::r4000allegrex_q_vs(_) => {} 157 | // ValuedOperand::r4000allegrex_q_vt(_) => {} 158 | // ValuedOperand::r4000allegrex_q_vd(_) => {} 159 | // ValuedOperand::r4000allegrex_q_vt_imm(_) => {} 160 | // ValuedOperand::r4000allegrex_mp_vs(_) => {} 161 | // ValuedOperand::r4000allegrex_mp_vt(_) => {} 162 | // ValuedOperand::r4000allegrex_mp_vd(_) => {} 163 | // ValuedOperand::r4000allegrex_mp_vs_transpose(_) => {} 164 | // ValuedOperand::r4000allegrex_mt_vs(_) => {} 165 | // ValuedOperand::r4000allegrex_mt_vt(_) => {} 166 | // ValuedOperand::r4000allegrex_mt_vd(_) => {} 167 | // ValuedOperand::r4000allegrex_mt_vs_transpose(_) => {} 168 | // ValuedOperand::r4000allegrex_mq_vs(_) => {} 169 | // ValuedOperand::r4000allegrex_mq_vt(_) => {} 170 | // ValuedOperand::r4000allegrex_mq_vd(_) => {} 171 | // ValuedOperand::r4000allegrex_mq_vs_transpose(_) => {} 172 | // ValuedOperand::r4000allegrex_cop2cs(_) => {} 173 | // ValuedOperand::r4000allegrex_cop2cd(_) => {} 174 | // ValuedOperand::r4000allegrex_pos(_) => {} 175 | // ValuedOperand::r4000allegrex_size(_) => {} 176 | // ValuedOperand::r4000allegrex_size_plus_pos(_) => {} 177 | // ValuedOperand::r4000allegrex_imm3(_) => {} 178 | // ValuedOperand::r4000allegrex_offset14_base(_, _) => {} 179 | // ValuedOperand::r4000allegrex_offset14_base_maybe_wb(_, _, _) => {} 180 | // ValuedOperand::r4000allegrex_vcmp_cond_s_maybe_vs_maybe_vt(_, _, _) => {} 181 | // ValuedOperand::r4000allegrex_vcmp_cond_p_maybe_vs_maybe_vt(_, _, _) => {} 182 | // ValuedOperand::r4000allegrex_vcmp_cond_t_maybe_vs_maybe_vt(_, _, _) => {} 183 | // ValuedOperand::r4000allegrex_vcmp_cond_q_maybe_vs_maybe_vt(_, _, _) => {} 184 | // ValuedOperand::r4000allegrex_vconstant(_) => {} 185 | // ValuedOperand::r4000allegrex_power_of_two(_) => {} 186 | // ValuedOperand::r4000allegrex_vfpu_cc_bit(_) => {} 187 | // ValuedOperand::r4000allegrex_bn(_) => {} 188 | // ValuedOperand::r4000allegrex_int16(_) => {} 189 | // ValuedOperand::r4000allegrex_float16(_) => {} 190 | // ValuedOperand::r4000allegrex_p_vrot_code(_) => {} 191 | // ValuedOperand::r4000allegrex_t_vrot_code(_) => {} 192 | // ValuedOperand::r4000allegrex_q_vrot_code(_) => {} 193 | // ValuedOperand::r4000allegrex_wpx(_) => {} 194 | // ValuedOperand::r4000allegrex_wpy(_) => {} 195 | // ValuedOperand::r4000allegrex_wpz(_) => {} 196 | // ValuedOperand::r4000allegrex_wpw(_) => {} 197 | // ValuedOperand::r4000allegrex_rpx(_) => {} 198 | // ValuedOperand::r4000allegrex_rpy(_) => {} 199 | // ValuedOperand::r4000allegrex_rpz(_) => {} 200 | // ValuedOperand::r4000allegrex_rpw(_) => {} 201 | // ValuedOperand::r5900ee_I() => {} 202 | // ValuedOperand::r5900ee_Q() => {} 203 | // ValuedOperand::r5900ee_R() => {} 204 | // ValuedOperand::r5900ee_ACC() => {} 205 | // ValuedOperand::r5900ee_immediate5(_) => {} 206 | // ValuedOperand::r5900ee_immediate15(_) => {} 207 | // ValuedOperand::r5900ee_vfs(_) => {} 208 | // ValuedOperand::r5900ee_vft(_) => {} 209 | // ValuedOperand::r5900ee_vfd(_) => {} 210 | // ValuedOperand::r5900ee_vis(_) => {} 211 | // ValuedOperand::r5900ee_vit(_) => {} 212 | // ValuedOperand::r5900ee_vid(_) => {} 213 | // ValuedOperand::r5900ee_ACCxyzw(_, _, _, _) => {} 214 | // ValuedOperand::r5900ee_vfsxyzw(_, _, _, _, _) => {} 215 | // ValuedOperand::r5900ee_vftxyzw(_, _, _, _, _) => {} 216 | // ValuedOperand::r5900ee_vfdxyzw(_, _, _, _, _) => {} 217 | // ValuedOperand::r5900ee_vftn(_, _) => {} 218 | // ValuedOperand::r5900ee_vfsl(_, _) => {} 219 | // ValuedOperand::r5900ee_vftm(_, _) => {} 220 | // ValuedOperand::r5900ee_vis_predecr(_, _) => {} 221 | // ValuedOperand::r5900ee_vit_predecr(_, _) => {} 222 | // ValuedOperand::r5900ee_vis_postincr(_, _) => {} 223 | // ValuedOperand::r5900ee_vit_postincr(_, _) => {} 224 | // ValuedOperand::r5900ee_vis_parenthesis(_) => {} 225 | _ => vo.hash(&mut hasher), 226 | } 227 | } 228 | } 229 | } 230 | Platform::Gc | Platform::Wii => { 231 | for (i, chunk) in bytes.chunks_exact(4).enumerate() { 232 | let code = platform 233 | .endianness() 234 | .read_u32_bytes(chunk.try_into().unwrap()); 235 | let instruction = ppc750cl::Ins::new(code); 236 | let opcode = instruction.op as u16; 237 | let _cur_vram = vram + i * 4; 238 | 239 | // hash opcode 240 | opcode.hash(&mut hasher); 241 | 242 | // hash operands 243 | // for a in instruction.basic().args { 244 | // // TODO only want to do the right ones 245 | // //a.hash(&mut hasher); 246 | // } 247 | } 248 | } 249 | } 250 | 251 | hasher.finish() 252 | } 253 | -------------------------------------------------------------------------------- /crates/core/src/cluster.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug)] 4 | pub struct Cluster<'a> { 5 | pub syms: Vec<&'a Symbol>, 6 | } 7 | 8 | impl Cluster<'_> { 9 | pub fn size(&self) -> usize { 10 | self.syms.len() 11 | } 12 | } 13 | 14 | pub fn get_clusters(symbols: &[Symbol], threshold: f32, min_len: usize) -> Vec { 15 | let mut clusters: Vec = Vec::new(); 16 | 17 | symbols 18 | .iter() 19 | .filter(|s: &&Symbol| s.opcodes.len() >= min_len) 20 | .for_each(|symbol| { 21 | let mut cluster_match = false; 22 | 23 | for cluster in &mut clusters { 24 | let cluster_score = diff_symbols(symbol, cluster.syms[0], threshold); 25 | if cluster_score > threshold { 26 | cluster_match = true; 27 | cluster.syms.push(symbol); 28 | break; 29 | } 30 | } 31 | 32 | // Add this symbol to a new cluster if it didn't match any existing clusters 33 | if !cluster_match { 34 | clusters.push(Cluster { syms: vec![symbol] }); 35 | } 36 | }); 37 | 38 | // Sort clusters by size 39 | clusters.sort_by_key(|c| std::cmp::Reverse(c.size())); 40 | 41 | clusters 42 | } 43 | -------------------------------------------------------------------------------- /crates/core/src/ingest.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use anyhow::Result; 4 | use mapfile_parser::MapFile; 5 | use object::{ 6 | Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, 7 | RelocationTarget, elf, 8 | }; 9 | 10 | use crate::ingest::CoddogRel::SymbolTarget; 11 | use crate::{Platform, Symbol}; 12 | 13 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 14 | pub enum CoddogRel { 15 | SymbolTarget(String, i64), 16 | } 17 | 18 | fn get_reloc_target(elf: &File, addr: u64, reloc: &Relocation, addend: i64) -> CoddogRel { 19 | match reloc.target() { 20 | RelocationTarget::Symbol(s) => { 21 | let symbol = elf 22 | .symbol_by_index(s) 23 | .ok() 24 | .and_then(|s| s.name().ok()) 25 | .unwrap_or_default(); 26 | SymbolTarget(symbol.to_string(), addend) 27 | } 28 | RelocationTarget::Absolute => { 29 | panic!("Absolute reloc: {:#x} => ({})?", addr, addend); 30 | } 31 | _ => { 32 | panic!("Unsupported reloc: {:#x} => ({})?", addr, addend); 33 | } 34 | } 35 | } 36 | 37 | pub fn read_elf( 38 | platform: Platform, 39 | unmatched_funcs: &Option>, 40 | elf_data: Vec, 41 | ) -> Result> { 42 | let file = File::parse(&*elf_data)?; 43 | 44 | let relocation_data = get_reloc_data(platform, &file)?; 45 | 46 | let ret: Vec = file 47 | .symbols() 48 | .filter(|s| s.kind() == object::SymbolKind::Text) 49 | .filter_map(|symbol| { 50 | symbol.section_index().and_then(|i| { 51 | file.section_by_index(i) 52 | .ok() 53 | .map(|section| (symbol, section, &relocation_data[i.0])) 54 | }) 55 | }) 56 | .filter_map(|(symbol, section, relocation_data)| { 57 | section 58 | .data_range(symbol.address(), symbol.size()) 59 | .ok() 60 | .flatten() 61 | .map(|data| { 62 | ( 63 | symbol, 64 | relocation_data, 65 | data, 66 | section.address(), 67 | section.file_range().unwrap().0, 68 | ) 69 | }) 70 | }) 71 | .map( 72 | |(symbol, section_relocations, data, section_address, section_offset)| { 73 | let offset = symbol.address() - section_address + section_offset; 74 | 75 | Symbol::new( 76 | symbol.name().unwrap().to_string(), 77 | data.to_vec(), 78 | symbol.address() as usize, 79 | offset as usize, 80 | unmatched_funcs 81 | .as_ref() 82 | .is_some_and(|fs| !fs.contains(&symbol.name().unwrap().to_string())), 83 | platform, 84 | section_relocations, 85 | ) 86 | }, 87 | ) 88 | .collect(); 89 | Ok(ret) 90 | } 91 | 92 | fn get_reloc_data(platform: Platform, file: &File) -> Result>> { 93 | // Copied & modified from objdiff - thx 94 | // Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only 95 | // have 16-bit immediate fields, the 32-bit addend is split across the two relocations. 96 | // R_MIPS_LO16 relocations without an immediately preceding R_MIPS_HI16 use the last seen 97 | // R_MIPS_HI16 addend. 98 | // See https://refspecs.linuxfoundation.org/elf/mipsabi.pdf pages 4-17 and 4-18 99 | 100 | let mut relocation_data = Vec::with_capacity(file.sections().count() + 1); 101 | for obj_section in file.sections() { 102 | let data = obj_section.data().unwrap_or_default(); 103 | let mut last_hi = None; 104 | let mut last_hi_addend = 0; 105 | let mut section_relocs = BTreeMap::new(); 106 | 107 | for (addr, reloc) in obj_section.relocations() { 108 | if !reloc.has_implicit_addend() { 109 | continue; 110 | } 111 | match reloc.flags() { 112 | RelocationFlags::Elf { 113 | r_type: elf::R_MIPS_HI16, 114 | } => { 115 | let code = data[addr as usize..addr as usize + 4].try_into()?; 116 | let addend = 117 | ((platform.endianness().read_u32_bytes(code) & 0x0000FFFF) << 16) as i32; 118 | last_hi = Some(addr); 119 | last_hi_addend = addend; 120 | } 121 | RelocationFlags::Elf { 122 | r_type: elf::R_MIPS_LO16, 123 | } => { 124 | let code = data[addr as usize..addr as usize + 4].try_into()?; 125 | let addend = 126 | (platform.endianness().read_u32_bytes(code) & 0x0000FFFF) as i16 as i32; 127 | let full_addend = (last_hi_addend + addend) as i64; 128 | 129 | let reloc_target = get_reloc_target(file, addr, &reloc, full_addend); 130 | 131 | if let Some(hi_addr) = last_hi.take() { 132 | section_relocs.insert(hi_addr, reloc_target.clone()); 133 | } 134 | section_relocs.insert(addr, reloc_target); 135 | } 136 | RelocationFlags::Elf { 137 | r_type: elf::R_MIPS_26, 138 | } => { 139 | section_relocs 140 | .insert(addr, get_reloc_target(file, addr, &reloc, reloc.addend())); 141 | } 142 | _ => { 143 | last_hi = None; 144 | } 145 | } 146 | } 147 | let section_index = obj_section.index().0; 148 | if section_index >= relocation_data.len() { 149 | relocation_data.resize_with(section_index + 1, Default::default); 150 | } 151 | relocation_data[section_index] = section_relocs; 152 | } 153 | Ok(relocation_data) 154 | } 155 | 156 | pub fn read_map( 157 | platform: Platform, 158 | unmatched_funcs: Option>, 159 | rom_bytes: Vec, 160 | map_str: &str, 161 | ) -> Result> { 162 | let mapfile = MapFile::new_from_gnu_map_str(map_str); 163 | let ret: Vec = mapfile 164 | .segments_list 165 | .iter() 166 | .flat_map(|x| x.sections_list.iter()) 167 | .filter(|x| x.section_type == ".text") 168 | .flat_map(|x| x.symbols.iter()) 169 | .filter(|x| x.vrom.is_some()) 170 | .map(|x| { 171 | let start = x.vrom.unwrap() as usize; 172 | let end = start + x.size as usize; 173 | let raw = &rom_bytes[start..end]; 174 | 175 | Symbol::new( 176 | x.name.clone(), 177 | raw.to_vec(), 178 | x.vram as usize, 179 | start, 180 | unmatched_funcs 181 | .as_ref() 182 | .is_some_and(|fs| !fs.contains(&x.name)), 183 | platform, 184 | &BTreeMap::default(), 185 | ) 186 | }) 187 | .collect(); 188 | Ok(ret) 189 | } 190 | 191 | #[cfg(test)] 192 | mod tests { 193 | use super::*; 194 | 195 | #[test] 196 | fn test_simple_mips() { 197 | let elf_data = include_bytes!("../../../test/simple_mips.o").to_vec(); 198 | let symbols = read_elf(Platform::N64, &None, elf_data).unwrap(); 199 | assert!(!symbols.is_empty()); 200 | 201 | let tf1 = symbols.iter().find(|s| s.name == "test_1").unwrap(); 202 | let tf2 = symbols.iter().find(|s| s.name == "test_2").unwrap(); 203 | 204 | assert_eq!(tf1.opcode_hash, tf2.opcode_hash); 205 | assert_eq!(tf1.equiv_hash, tf2.equiv_hash); 206 | assert_ne!(tf1.exact_hash, tf2.exact_hash); 207 | 208 | let math_op_1 = symbols.iter().find(|s| s.name == "math_op_1").unwrap(); 209 | let math_op_1_dup = symbols.iter().find(|s| s.name == "math_op_1_dup").unwrap(); 210 | assert_eq!(math_op_1.opcode_hash, math_op_1_dup.opcode_hash); 211 | assert_eq!(math_op_1.equiv_hash, math_op_1_dup.equiv_hash); 212 | assert_eq!(math_op_1.exact_hash, math_op_1_dup.exact_hash); 213 | } 214 | 215 | #[test] 216 | fn test_simple_mips_linked() { 217 | let elf_data = include_bytes!("../../../test/simple_mips_linked.o").to_vec(); 218 | let symbols = read_elf(Platform::N64, &None, elf_data).unwrap(); 219 | assert!(!symbols.is_empty()); 220 | 221 | let tf1 = symbols.iter().find(|s| s.name == "test_1").unwrap(); 222 | let tf2 = symbols.iter().find(|s| s.name == "test_2").unwrap(); 223 | 224 | assert_eq!(tf1.opcode_hash, tf2.opcode_hash); 225 | // TODO need to figure out what to do when we have no relocations 226 | //assert_eq!(tf1.equiv_hash, tf2.equiv_hash); 227 | assert_ne!(tf1.exact_hash, tf2.exact_hash); 228 | 229 | let math_op_1 = symbols.iter().find(|s| s.name == "math_op_1").unwrap(); 230 | let math_op_1_dup = symbols.iter().find(|s| s.name == "math_op_1_dup").unwrap(); 231 | assert_eq!(math_op_1.opcode_hash, math_op_1_dup.opcode_hash); 232 | assert_eq!(math_op_1.equiv_hash, math_op_1_dup.equiv_hash); 233 | assert_eq!(math_op_1.exact_hash, math_op_1_dup.exact_hash); 234 | } 235 | 236 | #[test] 237 | fn test_simple_mips_raw() { 238 | let rom_bytes = include_bytes!("../../../test/simple_mips_raw.bin").to_vec(); 239 | let map_str = include_str!("../../../test/simple_mips.map"); 240 | let symbols = read_map(Platform::N64, None, rom_bytes, &map_str).unwrap(); 241 | assert!(!symbols.is_empty()); 242 | 243 | let tf1 = symbols.iter().find(|s| s.name == "test_1").unwrap(); 244 | let tf2 = symbols.iter().find(|s| s.name == "test_2").unwrap(); 245 | 246 | assert_eq!(tf1.opcode_hash, tf2.opcode_hash); 247 | // TODO need to figure out what to do when we have no relocations 248 | //assert_eq!(tf1.equiv_hash, tf2.equiv_hash); 249 | assert_ne!(tf1.exact_hash, tf2.exact_hash); 250 | 251 | let math_op_1 = symbols.iter().find(|s| s.name == "math_op_1").unwrap(); 252 | let math_op_1_dup = symbols.iter().find(|s| s.name == "math_op_1_dup").unwrap(); 253 | assert_eq!(math_op_1.opcode_hash, math_op_1_dup.opcode_hash); 254 | assert_eq!(math_op_1.equiv_hash, math_op_1_dup.equiv_hash); 255 | assert_eq!(math_op_1.exact_hash, math_op_1_dup.exact_hash); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /crates/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod arch; 2 | pub mod cluster; 3 | pub mod ingest; 4 | 5 | use std::collections::BTreeMap; 6 | use std::hash::{DefaultHasher, Hash, Hasher}; 7 | 8 | use crate::arch::get_opcodes; 9 | use crate::ingest::CoddogRel; 10 | use editdistancek::edit_distance_bounded; 11 | use object::Endianness; 12 | 13 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 14 | pub enum Arch { 15 | Mips, 16 | Ppc, 17 | } 18 | 19 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 20 | pub enum Platform { 21 | N64, 22 | Psx, 23 | Ps2, 24 | Gc, 25 | Wii, 26 | } 27 | 28 | impl Platform { 29 | pub fn of(name: &str) -> Option { 30 | match name.to_lowercase().as_str() { 31 | "n64" => Some(Platform::N64), 32 | "psx" => Some(Platform::Psx), 33 | "ps2" => Some(Platform::Ps2), 34 | "gc" => Some(Platform::Gc), 35 | "wii" => Some(Platform::Wii), 36 | _ => None, 37 | } 38 | } 39 | 40 | pub fn endianness(&self) -> Endianness { 41 | match self { 42 | Platform::N64 => Endianness::Big, 43 | Platform::Psx => Endianness::Little, 44 | Platform::Ps2 => Endianness::Little, 45 | Platform::Gc => Endianness::Big, 46 | Platform::Wii => Endianness::Big, 47 | } 48 | } 49 | 50 | pub fn arch(&self) -> Arch { 51 | match self { 52 | Platform::N64 => Arch::Mips, 53 | Platform::Psx => Arch::Mips, 54 | Platform::Ps2 => Arch::Mips, 55 | Platform::Gc => Arch::Ppc, 56 | Platform::Wii => Arch::Ppc, 57 | } 58 | } 59 | } 60 | 61 | #[derive(Clone, Debug, PartialEq, Eq)] 62 | pub struct Symbol { 63 | /// the name of the symbol 64 | pub name: String, 65 | /// the raw bytes of the symbol 66 | pub bytes: Vec, 67 | /// the symbol's opcodes 68 | pub opcodes: Vec, 69 | /// the symbol's memory address 70 | pub vram: usize, 71 | /// the file offset of the symbol 72 | pub offset: usize, 73 | /// the length of the symbol in bytes 74 | pub length: usize, 75 | /// whether the symbol is decompiled 76 | pub is_decompiled: bool, 77 | /// the opcode hash for the symbol 78 | pub opcode_hash: u64, 79 | /// the equivalent hash for the symbol 80 | pub equiv_hash: u64, 81 | /// the exact hash for the symbol 82 | pub exact_hash: u64, 83 | } 84 | 85 | #[derive(Debug)] 86 | pub struct Binary { 87 | pub name: String, 88 | pub symbols: Vec, 89 | } 90 | 91 | #[derive(Debug, Clone, Copy)] 92 | pub struct InsnSeqMatch { 93 | pub offset1: usize, 94 | pub offset2: usize, 95 | pub length: usize, 96 | } 97 | 98 | impl Symbol { 99 | pub fn new( 100 | name: String, 101 | bytes: Vec, 102 | vram: usize, 103 | offset: usize, 104 | is_decompiled: bool, 105 | platform: Platform, 106 | relocations: &BTreeMap, 107 | ) -> Symbol { 108 | let mut bytes = bytes; 109 | match platform.arch() { 110 | Arch::Mips | Arch::Ppc => { 111 | while bytes.len() >= 4 && bytes[bytes.len() - 4..] == [0, 0, 0, 0] { 112 | bytes.truncate(bytes.len() - 4); 113 | } 114 | } 115 | } 116 | 117 | // TODO maybe remove field 118 | let length = bytes.len(); 119 | 120 | let mut hasher = DefaultHasher::new(); 121 | bytes.hash(&mut hasher); 122 | let exact_hash = hasher.finish(); 123 | 124 | let equiv_hash = arch::get_equivalence_hash(&bytes, vram, platform, relocations); 125 | 126 | let opcodes = get_opcodes(&bytes, platform); 127 | let mut hasher = DefaultHasher::new(); 128 | opcodes.hash(&mut hasher); 129 | let opcode_hash = hasher.finish(); 130 | 131 | Symbol { 132 | name, 133 | bytes, 134 | length, 135 | opcodes, 136 | vram, 137 | offset, 138 | is_decompiled, 139 | exact_hash, 140 | equiv_hash, 141 | opcode_hash, 142 | } 143 | } 144 | 145 | pub fn get_exact_hashes(&self, window_size: usize) -> Vec { 146 | get_hashes(&self.bytes, window_size) 147 | } 148 | 149 | pub fn get_opcode_hashes(&self, window_size: usize) -> Vec { 150 | get_hashes(&self.opcodes, window_size) 151 | } 152 | } 153 | pub fn get_hashes(data: &[T], window_size: usize) -> Vec { 154 | let mut data = data.to_vec(); 155 | 156 | if data.len() < window_size { 157 | data.resize(window_size, Default::default()); 158 | } 159 | 160 | data.windows(window_size) 161 | .map(|x| { 162 | let mut hasher = DefaultHasher::new(); 163 | (*x).hash(&mut hasher); 164 | hasher.finish() 165 | }) 166 | .collect() 167 | } 168 | 169 | pub fn get_submatches(hashes_1: &[u64], hashes_2: &[u64], window_size: usize) -> Vec { 170 | let mut matches = Vec::new(); 171 | 172 | let matching_hashes = hashes_1 173 | .iter() 174 | .enumerate() 175 | .filter(|(_, h)| hashes_2.contains(h)) 176 | .map(|(i, h)| InsnSeqMatch { 177 | offset1: i, 178 | offset2: hashes_2.iter().position(|x| x == h).unwrap(), 179 | length: 1, 180 | }) 181 | .collect::>(); 182 | 183 | if matching_hashes.is_empty() { 184 | return matches; 185 | } 186 | 187 | let mut match_groups: Vec> = Vec::new(); 188 | let mut cur_pos = matching_hashes[0].offset1; 189 | for mh in matching_hashes { 190 | if mh.offset1 == cur_pos + 1 { 191 | match_groups.last_mut().unwrap().push(mh); 192 | } else { 193 | match_groups.push(vec![mh]); 194 | } 195 | cur_pos = mh.offset1; 196 | } 197 | 198 | for group in match_groups { 199 | matches.push(InsnSeqMatch { 200 | offset1: group[0].offset1, 201 | offset2: group[0].offset2, 202 | length: group.len() + window_size, 203 | }); 204 | } 205 | 206 | matches 207 | } 208 | 209 | pub fn diff_symbols(sym1: &Symbol, sym2: &Symbol, threshold: f32) -> f32 { 210 | // The minimum edit distance for two strings of different lengths is `abs(l1 - l2)` 211 | // Quickly check if it's possible to beat the threshold. If it isn't, return 0 212 | let l1 = sym1.opcodes.len(); 213 | let l2 = sym2.opcodes.len(); 214 | 215 | let max_edit_dist = (l1 + l2) as f32; 216 | if (l1.abs_diff(l2) as f32 / max_edit_dist) > (1.0 - threshold) { 217 | return 0.0; 218 | } 219 | 220 | let sym1_insns_u8: Vec = sym1.opcodes.iter().flat_map(|&x| x.to_be_bytes()).collect(); 221 | let sym2_insns_u8: Vec = sym2.opcodes.iter().flat_map(|&x| x.to_be_bytes()).collect(); 222 | 223 | let bound = (max_edit_dist - (max_edit_dist * threshold)) as usize; 224 | if let Some(edit_distance) = edit_distance_bounded(&sym1_insns_u8, &sym2_insns_u8, bound) { 225 | let edit_dist = edit_distance as f32; 226 | let normalized_edit_dist = (max_edit_dist - edit_dist) / max_edit_dist; 227 | 228 | if normalized_edit_dist == 1.0 && sym1.bytes != sym2.bytes { 229 | return 0.9999; 230 | } 231 | normalized_edit_dist 232 | } else { 233 | 0.0 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /crates/db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coddog-db" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | readme = "../../README.md" 9 | description = """ 10 | A database library for finding matching sequences of code among binaries 11 | """ 12 | 13 | [dependencies] 14 | anyhow = "1" 15 | blake3 = "1.0" 16 | coddog-core = { path = "../core" } 17 | sqlx = { version = "0.8", features = ["macros", "migrate", "runtime-tokio", "postgres"] } 18 | -------------------------------------------------------------------------------- /crates/db/migrations/20250508130138_init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS projects 2 | ( 3 | id BIGSERIAL PRIMARY KEY, 4 | name TEXT NOT NULL, 5 | platform INT NOT NULL, 6 | repo_url TEXT NULL 7 | ); 8 | 9 | CREATE TABLE IF NOT EXISTS sources 10 | ( 11 | id BIGSERIAL PRIMARY KEY, 12 | hash TEXT NOT NULL, 13 | name TEXT NOT NULL, 14 | filepath TEXT NOT NULL, 15 | project_id BIGINT NOT NULL, 16 | FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE 17 | ); 18 | CREATE UNIQUE INDEX IF NOT EXISTS hash_idx ON sources (hash); 19 | 20 | CREATE TABLE IF NOT EXISTS symbols 21 | ( 22 | id BIGSERIAL PRIMARY KEY, 23 | pos BIGINT NOT NULL, 24 | len INT NOT NULL, 25 | name TEXT NOT NULL, 26 | opcode_hash BIGINT NOT NULL, 27 | equiv_hash BIGINT NOT NULL, 28 | exact_hash BIGINT NOT NULL, 29 | source_id BIGINT NOT NULL, 30 | FOREIGN KEY (source_id) REFERENCES sources (id) ON DELETE CASCADE 31 | ); 32 | CREATE INDEX IF NOT EXISTS opcode_hash_idx ON symbols (opcode_hash); 33 | CREATE INDEX IF NOT EXISTS equiv_hash_idx ON symbols (equiv_hash); 34 | CREATE INDEX IF NOT EXISTS exact_hash_idx ON symbols (exact_hash); 35 | 36 | CREATE TABLE IF NOT EXISTS windows 37 | ( 38 | id BIGSERIAL PRIMARY KEY, 39 | pos INT NOT NULL, 40 | hash BIGINT NOT NULL, 41 | symbol_id BIGINT NOT NULL, 42 | FOREIGN KEY (symbol_id) REFERENCES symbols (id) ON DELETE CASCADE 43 | ); 44 | CREATE INDEX IF NOT EXISTS hash_idx ON windows (hash); 45 | CREATE INDEX IF NOT EXISTS symbol_idx ON windows (symbol_id); 46 | CREATE INDEX IF NOT EXISTS hash_symbol_idx ON windows (hash, symbol_id); -------------------------------------------------------------------------------- /crates/db/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use coddog_core::{Platform, Symbol}; 3 | use sqlx::{PgPool, Pool, Postgres, Transaction, migrate::MigrateDatabase}; 4 | use std::fmt::{Display, Formatter}; 5 | use std::path::{Path, PathBuf}; 6 | use std::{fs, fs::File, io::Read}; 7 | 8 | const CHUNK_SIZE: usize = 100000; 9 | 10 | pub async fn db_init() -> Result { 11 | let db_path = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 12 | if !Postgres::database_exists(&db_path).await.unwrap_or(false) { 13 | match Postgres::create_database(&db_path).await { 14 | Ok(_) => { 15 | println!("Database created at {}", db_path); 16 | } 17 | Err(_) => { 18 | return Err(anyhow::anyhow!("Error creating database")); 19 | } 20 | } 21 | } 22 | 23 | let pool = PgPool::connect(&db_path).await?; 24 | 25 | let migration_results = sqlx::migrate!("./migrations").run(&pool).await; 26 | 27 | match migration_results { 28 | Ok(_) => Ok(pool), 29 | Err(e) => Err(anyhow::anyhow!("Error migrating database: {}", e)), 30 | } 31 | } 32 | 33 | pub async fn add_project( 34 | tx: &mut Transaction<'_, Postgres>, 35 | name: &str, 36 | platform: Platform, 37 | ) -> Result { 38 | let rec = sqlx::query!( 39 | "INSERT INTO projects (name, platform) VALUES ($1, $2) RETURNING id", 40 | name, 41 | platform as i32 42 | ) 43 | .fetch_one(&mut **tx) 44 | .await?; 45 | 46 | Ok(rec.id) 47 | } 48 | 49 | pub async fn add_source( 50 | tx: &mut Transaction<'_, Postgres>, 51 | project_id: i64, 52 | name: &str, 53 | filepath: &PathBuf, 54 | ) -> Result { 55 | let mut file = File::open(filepath)?; 56 | let mut buffer = Vec::new(); 57 | file.read_to_end(&mut buffer)?; 58 | let hash = blake3::hash(&buffer); 59 | 60 | let bin_path = std::env::var("BIN_PATH").expect("BIN_PATH must be set"); 61 | let target_path = Path::new(&bin_path); 62 | let target_path = target_path.join(format!("{}.bin", hash)); 63 | 64 | match sqlx::query!( 65 | "INSERT INTO sources (project_id, hash, name, filepath) VALUES ($1, $2, $3, $4) RETURNING id", 66 | project_id, 67 | &hash.to_hex().to_string(), 68 | name, 69 | target_path.to_str().unwrap(), 70 | ) 71 | .fetch_one(&mut **tx) 72 | .await 73 | .map_err(anyhow::Error::from) 74 | { 75 | Ok(r) => { 76 | fs::create_dir_all(target_path.parent().unwrap())?; // Ensure the target directory exists 77 | match fs::copy(filepath, target_path.clone()) { 78 | Ok(_) => Ok(r.id), 79 | Err(e) => Err(anyhow::anyhow!("Error copying file: {}", e)), 80 | } 81 | } 82 | Err(e) => Err(e), 83 | } 84 | } 85 | 86 | type BulkSymbolData = ( 87 | Vec, 88 | Vec, 89 | Vec, 90 | Vec, 91 | Vec, 92 | Vec, 93 | ); 94 | 95 | pub async fn add_symbols( 96 | tx: &mut Transaction<'_, Postgres>, 97 | source_id: i64, 98 | symbols: &[Symbol], 99 | ) -> Vec { 100 | let mut ret = vec![]; 101 | 102 | for chunk in symbols.chunks(CHUNK_SIZE) { 103 | let source_ids = vec![source_id; chunk.len()]; 104 | let (offsets, lens, names, opcode_hashes, equiv_hashes, exact_hashes): BulkSymbolData = 105 | chunk 106 | .iter() 107 | .map(|s| { 108 | ( 109 | s.offset as i64, 110 | s.length as i64, 111 | s.name.clone(), 112 | s.opcode_hash as i64, 113 | s.equiv_hash as i64, 114 | s.exact_hash as i64, 115 | ) 116 | }) 117 | .collect(); 118 | 119 | let rows = sqlx::query!( 120 | " 121 | INSERT INTO symbols (source_id, pos, len, name, opcode_hash, equiv_hash, exact_hash) 122 | SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::bigint[], $4::text[], $5::bigint[], $6::bigint[], $7::bigint[]) 123 | RETURNING id 124 | ", 125 | &source_ids as &[i64], 126 | &offsets as &[i64], 127 | &lens as &[i64], 128 | &names, 129 | &opcode_hashes, 130 | &equiv_hashes, 131 | &exact_hashes, 132 | ) 133 | .fetch_all(&mut **tx) 134 | .await 135 | .unwrap(); 136 | 137 | for row in rows { 138 | ret.push(row.id); 139 | } 140 | } 141 | 142 | ret 143 | } 144 | 145 | pub async fn add_symbol_window_hashes( 146 | tx: &mut Transaction<'_, Postgres>, 147 | hashes: &[u64], 148 | symbol_id: i64, 149 | ) -> Result<()> { 150 | let hashes_enumerated: Vec<(usize, &u64)> = hashes.iter().enumerate().collect(); 151 | 152 | for chunk in hashes_enumerated.chunks(CHUNK_SIZE) { 153 | let symbol_ids = vec![symbol_id; chunk.len()]; 154 | let (poses, opcode_hashes): (Vec, Vec) = 155 | chunk.iter().map(|c| (c.0 as i64, *c.1 as i64)).collect(); 156 | 157 | let r = sqlx::query!( 158 | " 159 | INSERT INTO windows (pos, hash, symbol_id) 160 | SELECT * FROM UNNEST($1::int[], $2::bigint[], $3::bigint[]) 161 | ", 162 | &poses as &[i64], 163 | &opcode_hashes as &[i64], 164 | &symbol_ids as &[i64], 165 | ) 166 | .execute(&mut **tx) 167 | .await; 168 | 169 | if let Err(e) = r { 170 | return Err(anyhow::anyhow!("Error adding symbol window hashes: {}", e)); 171 | } 172 | } 173 | Ok(()) 174 | } 175 | 176 | #[derive(Clone, Debug)] 177 | pub struct DBSymbol { 178 | pub id: i64, 179 | pub pos: i64, 180 | pub len: i32, 181 | pub name: String, 182 | pub opcode_hash: i64, 183 | pub equiv_hash: i64, 184 | pub exact_hash: i64, 185 | pub source_id: i64, 186 | pub source_name: String, 187 | pub project_id: i64, 188 | pub project_name: String, 189 | } 190 | 191 | impl Display for DBSymbol { 192 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 193 | f.write_fmt(format_args!( 194 | "{} version {} (offset {:X})", 195 | self.project_name, self.source_name, self.pos, 196 | )) 197 | } 198 | } 199 | 200 | #[derive(Clone, Debug)] 201 | pub struct DBProject { 202 | pub id: i64, 203 | pub name: String, 204 | } 205 | 206 | impl Display for DBProject { 207 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 208 | f.write_fmt(format_args!("{}", self.name)) 209 | } 210 | } 211 | 212 | pub async fn db_query_projects_by_name( 213 | conn: Pool, 214 | query: &str, 215 | ) -> Result> { 216 | let rows = sqlx::query!( 217 | "SELECT projects.name, projects.id FROM projects WHERE projects.name LIKE $1", 218 | query 219 | ) 220 | .fetch_all(&conn) 221 | .await?; 222 | 223 | let res: Vec = rows 224 | .iter() 225 | .map(|row| DBProject { 226 | id: row.id, 227 | name: query.to_string(), 228 | }) 229 | .collect(); 230 | 231 | Ok(res) 232 | } 233 | 234 | pub async fn db_delete_project(conn: Pool, id: i64) -> Result<()> { 235 | sqlx::query!("DELETE FROM projects WHERE id = $1", id) 236 | .execute(&conn) 237 | .await?; 238 | 239 | Ok(()) 240 | } 241 | 242 | pub async fn db_query_symbols_by_name(conn: Pool, query: &str) -> Result> { 243 | let rows = sqlx::query!( 244 | " 245 | SELECT symbols.id, symbols.pos, symbols.len, 246 | symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash, symbols.source_id, 247 | sources.name AS source_name, projects.name AS project_name, projects.id as project_id 248 | FROM symbols 249 | INNER JOIN sources ON sources.id = symbols.source_id 250 | INNER JOIN projects on sources.project_id = projects.id 251 | WHERE symbols.name = $1", 252 | query 253 | ) 254 | .fetch_all(&conn) 255 | .await?; 256 | 257 | let res: Vec = rows 258 | .iter() 259 | .map(|row| DBSymbol { 260 | id: row.id, 261 | pos: row.pos, 262 | len: row.len, 263 | name: query.to_string(), 264 | opcode_hash: row.opcode_hash, 265 | equiv_hash: row.equiv_hash, 266 | exact_hash: row.exact_hash, 267 | source_id: row.source_id, 268 | source_name: row.source_name.clone(), 269 | project_id: row.project_id, 270 | project_name: row.project_name.clone(), 271 | }) 272 | .collect(); 273 | 274 | Ok(res) 275 | } 276 | 277 | pub async fn db_query_symbols_by_opcode_hash( 278 | conn: Pool, 279 | symbol: &DBSymbol, 280 | ) -> Result> { 281 | let rows = sqlx::query!( 282 | " 283 | SELECT symbols.id, symbols.pos, symbols.len, symbols.name, 284 | symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash, 285 | symbols.source_id, 286 | sources.name AS source_name, projects.name AS project_name, projects.id as project_id 287 | FROM symbols 288 | INNER JOIN sources ON sources.id = symbols.source_id 289 | INNER JOIN projects on sources.project_id = projects.id 290 | WHERE symbols.opcode_hash = $1 AND NOT symbols.id = $2", 291 | symbol.opcode_hash as i64, 292 | symbol.id as i64 293 | ) 294 | .fetch_all(&conn) 295 | .await?; 296 | 297 | let res = rows 298 | .iter() 299 | .map(|row| DBSymbol { 300 | id: row.id, 301 | pos: row.pos, 302 | len: row.len, 303 | name: row.name.to_string(), 304 | opcode_hash: row.opcode_hash, 305 | equiv_hash: row.equiv_hash, 306 | exact_hash: row.exact_hash, 307 | source_id: row.source_id, 308 | source_name: row.source_name.clone(), 309 | project_id: row.project_id, 310 | project_name: row.project_name.clone(), 311 | }) 312 | .collect(); 313 | 314 | Ok(res) 315 | } 316 | 317 | pub async fn db_query_symbols_by_equiv_hash( 318 | conn: Pool, 319 | symbol: &DBSymbol, 320 | ) -> Result> { 321 | let rows = sqlx::query!( 322 | " 323 | SELECT symbols.id, symbols.pos, symbols.len, symbols.name, 324 | symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash, 325 | symbols.source_id, 326 | sources.name AS source_name, projects.name AS project_name, projects.id as project_id 327 | FROM symbols 328 | INNER JOIN sources ON sources.id = symbols.source_id 329 | INNER JOIN projects on sources.project_id = projects.id 330 | WHERE symbols.equiv_hash = $1 AND NOT symbols.id = $2", 331 | symbol.equiv_hash as i64, 332 | symbol.id as i64 333 | ) 334 | .fetch_all(&conn) 335 | .await?; 336 | 337 | let res = rows 338 | .iter() 339 | .map(|row| DBSymbol { 340 | id: row.id, 341 | pos: row.pos, 342 | len: row.len, 343 | name: row.name.to_string(), 344 | opcode_hash: row.opcode_hash, 345 | equiv_hash: row.equiv_hash, 346 | exact_hash: row.exact_hash, 347 | source_id: row.source_id, 348 | source_name: row.source_name.clone(), 349 | project_id: row.project_id, 350 | project_name: row.project_name.clone(), 351 | }) 352 | .collect(); 353 | 354 | Ok(res) 355 | } 356 | 357 | pub async fn db_query_symbols_by_exact_hash( 358 | conn: Pool, 359 | symbol: &DBSymbol, 360 | ) -> Result> { 361 | let rows = sqlx::query!( 362 | " 363 | SELECT symbols.id, symbols.pos, symbols.len, symbols.name, 364 | symbols.opcode_hash, symbols.equiv_hash, symbols.exact_hash, symbols.source_id, 365 | sources.name AS source_name, projects.name AS project_name, projects.id as project_id 366 | FROM symbols 367 | INNER JOIN sources ON sources.id = symbols.source_id 368 | INNER JOIN projects on sources.project_id = projects.id 369 | WHERE symbols.exact_hash = $1 AND NOT symbols.id = $2", 370 | symbol.exact_hash as i64, 371 | symbol.id as i64 372 | ) 373 | .fetch_all(&conn) 374 | .await?; 375 | 376 | let res = rows 377 | .iter() 378 | .map(|row| DBSymbol { 379 | id: row.id, 380 | pos: row.pos, 381 | len: row.len, 382 | name: row.name.to_string(), 383 | opcode_hash: row.opcode_hash, 384 | equiv_hash: row.equiv_hash, 385 | exact_hash: row.exact_hash, 386 | source_id: row.source_id, 387 | source_name: row.source_name.clone(), 388 | project_id: row.project_id, 389 | project_name: row.project_name.clone(), 390 | }) 391 | .collect(); 392 | 393 | Ok(res) 394 | } 395 | 396 | #[derive(Clone, Debug)] 397 | pub struct DBWindow { 398 | pub query_start: i32, 399 | pub match_start: i32, 400 | pub length: i64, 401 | pub symbol_id: i64, 402 | pub symbol_name: String, 403 | pub source_id: i64, 404 | pub source_name: String, 405 | pub project_id: i64, 406 | pub project_name: String, 407 | } 408 | pub async fn db_query_windows_by_symbol_id( 409 | conn: Pool, 410 | symbol_id: i64, 411 | min_seq_len: i64, 412 | ) -> Result> { 413 | let rows = sqlx::query!( 414 | " 415 | WITH 416 | potential_matches AS ( 417 | SELECT 418 | b.symbol_id, 419 | a.pos AS query_pos, 420 | b.pos AS match_pos, 421 | a.hash, 422 | (a.pos - b.pos) AS pos_diff 423 | FROM windows a 424 | JOIN windows b ON a.hash = b.hash 425 | WHERE a.symbol_id = $1 AND a.symbol_id != b.symbol_id 426 | ), 427 | sequence_groups AS ( 428 | SELECT 429 | hash, 430 | symbol_id, 431 | query_pos, 432 | match_pos, 433 | pos_diff, 434 | query_pos - ROW_NUMBER() OVER (PARTITION BY symbol_id, pos_diff ORDER BY query_pos) AS sequence_id 435 | FROM potential_matches 436 | ), 437 | final_sequences AS ( 438 | SELECT 439 | symbol_id, 440 | MIN(query_pos) AS start_query_pos, 441 | MIN(match_pos) AS start_match_pos, 442 | COUNT(*) AS length 443 | FROM sequence_groups 444 | GROUP BY symbol_id, pos_diff, sequence_id 445 | ) 446 | SELECT project_id, projects.name AS project_name, source_id, sources.name AS source_name, symbol_id, 447 | symbols.name as symbol_name, start_query_pos, start_match_pos, length 448 | FROM final_sequences 449 | JOIN symbols ON symbol_id = symbols.id 450 | JOIN sources ON symbols.source_id = sources.id 451 | JOIN projects ON sources.project_id = projects.id 452 | WHERE length >= $2 453 | ORDER BY project_id, source_id, symbol_id, start_query_pos, start_match_pos 454 | ",symbol_id, min_seq_len 455 | ) 456 | .fetch_all(&conn) 457 | .await?; 458 | 459 | let res: Vec = rows 460 | .iter() 461 | .map(|row| DBWindow { 462 | query_start: row.start_query_pos.unwrap(), 463 | match_start: row.start_match_pos.unwrap(), 464 | length: row.length.unwrap(), 465 | symbol_id: row.symbol_id, 466 | symbol_name: row.symbol_name.clone(), 467 | source_id: row.source_id, 468 | source_name: row.source_name.clone(), 469 | project_id: row.project_id, 470 | project_name: row.project_name.clone(), 471 | }) 472 | .collect(); 473 | 474 | Ok(res) 475 | } 476 | -------------------------------------------------------------------------------- /test/notes.txt: -------------------------------------------------------------------------------- 1 | ~/repos/pokemonsnap/tools/ido7.1/cc simple.c -O2 -G 0 -non_shared -c -o simple_mips.o 2 | mips-linux-gnu-ld -T simple.ld simple_mips.o -Map simple_mips.map -o simple_mips_linked.o 3 | mips-linux-gnu-objcopy -Obinary simple_mips_linked.o simple_mips_raw.bin 4 | -------------------------------------------------------------------------------- /test/simple.c: -------------------------------------------------------------------------------- 1 | extern float some_external_function(float a, float b); 2 | int math_op_1(int a, int b); 3 | int math_op_2(int a, int b); 4 | 5 | int cat = 1; 6 | int dog = 5; 7 | 8 | int test_1(int state) { 9 | switch (state) { 10 | case 0: 11 | return math_op_1(cat, dog); 12 | case 1: 13 | return math_op_2(cat, dog); 14 | case 2: 15 | return some_external_function(cat, dog); 16 | case 3: 17 | return 5; 18 | case 4: 19 | return 5; 20 | default: 21 | return -1; 22 | } 23 | } 24 | 25 | int test_2(int state) { 26 | switch (state) { 27 | case 0: 28 | return math_op_2(cat, dog); 29 | case 1: 30 | return math_op_1(cat, dog); 31 | case 2: 32 | return some_external_function(cat, dog); 33 | case 3: 34 | return 5; 35 | case 4: 36 | return 5; 37 | default: 38 | return -1; 39 | } 40 | } 41 | 42 | int math_op_1(int a, int b) { 43 | return a + b + some_external_function(a, b); 44 | } 45 | 46 | int math_op_2(int a, int b) { 47 | return a - b; 48 | } 49 | 50 | int math_op_1_dup(int a, int b) { 51 | return a + b + some_external_function(a, b); 52 | } 53 | -------------------------------------------------------------------------------- /test/simple.ld: -------------------------------------------------------------------------------- 1 | some_external_function = 0x00001000; 2 | -------------------------------------------------------------------------------- /test/simple_mips.map: -------------------------------------------------------------------------------- 1 | 2 | There are no discarded input sections 3 | 4 | Memory Configuration 5 | 6 | Name Origin Length Attributes 7 | *default* 0x00000000 0xffffffff 8 | 9 | Linker script and memory map 10 | 11 | 0x00001000 some_external_function = 0x1000 12 | LOAD simple_mips.o 13 | OUTPUT(simple_mips_linked.o elf32-tradbigmips) 14 | 15 | .text 0x00000000 0x230 16 | .text 0x00000000 0x230 simple_mips.o 17 | 0x00000000 test_1 18 | 0x000000b8 test_2 19 | 0x00000170 math_op_1 20 | 0x000001c8 math_op_2 21 | 0x000001d0 math_op_1_dup 22 | 23 | .rodata 0x00000230 0x30 24 | .rodata 0x00000230 0x30 simple_mips.o 25 | 26 | .reginfo 0x00000260 0x18 27 | .reginfo 0x00000260 0x18 simple_mips.o 28 | 29 | .data 0x00000280 0x10 30 | .data 0x00000280 0x10 simple_mips.o 31 | 0x00000280 cat 32 | 0x00000284 dog 33 | 34 | .options 0x00000000 0x40 35 | .options 0x00000000 0x40 simple_mips.o 36 | 37 | .gptab.data 0x00000000 0x18 38 | .gptab.data 0x00000000 0x18 simple_mips.o 39 | 40 | .mdebug 0x00000000 0x488 41 | .mdebug 0x00000000 0x450 simple_mips.o 42 | -------------------------------------------------------------------------------- /test/simple_mips.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethteck/coddog/b8e957dc2af1107de84da0e3d15d30ccfd8605eb/test/simple_mips.o -------------------------------------------------------------------------------- /test/simple_mips_linked.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethteck/coddog/b8e957dc2af1107de84da0e3d15d30ccfd8605eb/test/simple_mips_linked.o -------------------------------------------------------------------------------- /test/simple_mips_raw.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethteck/coddog/b8e957dc2af1107de84da0e3d15d30ccfd8605eb/test/simple_mips_raw.bin --------------------------------------------------------------------------------