Repository navigation
Revert the import of OpenCMR2 into port/ #51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build + Update reccmp Report | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.head_ref || github.ref_name }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| fetch-deps: | |
| name: Download original binary | |
| uses: ./.github/workflows/cmr2bin.yml | |
| build: | |
| name: Build (MSVC 6.0) | |
| needs: [fetch-deps] | |
| runs-on: "windows-latest" | |
| steps: | |
| - uses: actions/checkout@main | |
| with: | |
| submodules: recursive | |
| - name: Fetch MSVC 6.0 | |
| uses: actions/checkout@main | |
| with: | |
| repository: itsmattkc/msvc600 | |
| ref: 001c4bafdcf2ef4b474d693acccd35a91e848f40 | |
| path: msvc600 | |
| # The original was built with the VC6 SP3 compiler passes (Rich header build 8447) | |
| - name: Cache VC6 SP3 compiler passes | |
| uses: actions/cache@v4 | |
| with: | |
| path: vc6sp3 | |
| key: vc6sp3-v1 | |
| - name: Install VC6 SP3 compiler passes | |
| shell: cmd | |
| run: | | |
| python scripts/fetch_vc6sp3.py --cache vc6sp3 --msvc-root msvc600/VC98 | |
| - name: Build | |
| shell: cmd | |
| run: | | |
| python scripts/build.py --msvc-root msvc600/VC98 | |
| - name: Upload Build | |
| uses: actions/upload-artifact@main | |
| with: | |
| name: Win32 | |
| path: | | |
| build/CMR2.exe | |
| build/CMR2.pdb | |
| build/manifest.json | |
| verify: | |
| name: Verify decomp | |
| needs: [build, fetch-deps] | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@main | |
| with: | |
| ref: ${{ github.head_ref }} | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - uses: actions/download-artifact@main | |
| with: | |
| name: Win32 | |
| path: build | |
| - name: Restore cached original binary | |
| id: cache-original-binary | |
| uses: actions/cache/restore@v4 | |
| with: | |
| enableCrossOsArchive: true | |
| path: cmr2bin | |
| key: cmr2bin | |
| - name: Install reccmp | |
| shell: bash | |
| run: | | |
| pip install -r scripts/requirements.txt | |
| - name: Detect binary | |
| run: | | |
| reccmp-project detect --what original --search-path cmr2bin | |
| reccmp-project detect --what recompiled --search-path build | |
| - name: Compare Accuracy to main | |
| shell: bash | |
| env: | |
| RELEASE_URL: https://raw.githubusercontent.com/${{ github.repository }}/refs/heads/main/CMR2PROGRESS/ | |
| run: | | |
| # Download the current main state | |
| curl -fLSs -o summary-old.json $RELEASE_URL/summary.json || echo "" >summary-old.json | |
| # Compare with current main | |
| python scripts/reccmp_compat.py reccmp --target CMR2 --html index.html --json CMR2PROGRESS/summary.json --diff summary-old.json || echo "Current master not found" | |
| # decomp.dev picks up any "<version>_report" artifact holding an objdiff report.json | |
| - name: Generate objdiff report | |
| shell: bash | |
| run: | | |
| python scripts/objdiff_report.py --units function -o build/report.json | |
| - name: Upload objdiff report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: CMR2_report | |
| path: build/report.json | |
| - name: Upload HTML accuracy report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: accuracy-html | |
| path: index.html | |
| - name: Test Exports | |
| shell: bash | |
| run: | | |
| python scripts/reccmp_compat.py verexp --target CMR2 | |
| # Don't have any vtables yet | |
| # - name: Check Vtables | |
| # shell: bash | |
| # run: | | |
| # reccmp-vtable --target CMR2 | |
| - name: Check Variables | |
| shell: bash | |
| run: | | |
| python scripts/reccmp_compat.py datacmp --target CMR2 | |
| - name: Upsert PR Accuracy Report | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const TAG = '<!-- CMR2-ACCURACY-REPORT -->'; | |
| const STUB_THRESHOLD = 0.001; | |
| const LOW_ACCURACY_THRESHOLD = 80; | |
| function load(path) { | |
| if (!fs.existsSync(path)) return null; | |
| return JSON.parse(fs.readFileSync(path, 'utf8')); | |
| } | |
| const currentRaw = load('CMR2PROGRESS/summary.json'); | |
| if (!currentRaw) { | |
| core.setFailed('summary.json not found'); | |
| return; | |
| } | |
| const previousRaw = load('summary-old.json'); | |
| const current = currentRaw?.data || []; | |
| const previous = previousRaw?.data || []; | |
| if (!current.length) return; | |
| const currMap = new Map(current.map(f => [f.address, f])); | |
| const prevMap = new Map((previous || []).map(f => [f.address, f])); | |
| const increased = []; | |
| const decreased = []; | |
| const added = []; | |
| const gone = []; | |
| const stubbed = []; | |
| const lowAccuracy = []; | |
| const newlyPerfect = []; | |
| let total = current.length; | |
| let matched = 0; | |
| let fullMatch = 0; | |
| for (const fn of current) { | |
| matched += fn.matching; | |
| if (!fn.diff || fn.diff.length === 0) fullMatch++; | |
| const currPct = fn.matching * 100; | |
| const prev = prevMap.get(fn.address); | |
| const prevPct = prev ? prev.matching * 100 : null; | |
| const isStub = fn.matching <= STUB_THRESHOLD; | |
| const isPerfect = currPct === 100; | |
| // ➕ Added (record early so it doesn't get skipped) | |
| if (!prev) { | |
| added.push(fn); | |
| if (isStub) stubbed.push({ fn, reason: 'new stub' }); | |
| } | |
| // 🎉 Newly perfect (brand-new OR improved to 100%) | |
| if (isPerfect && !isStub && (prevPct === null || prevPct < 100)) { | |
| newlyPerfect.push({ | |
| fn, | |
| prevPct | |
| }); | |
| } | |
| // 🚨 Below 80% (exclude stubs, include new functions) | |
| if (!isStub && currPct < LOW_ACCURACY_THRESHOLD) { | |
| lowAccuracy.push({ | |
| fn, | |
| currPct, | |
| delta: prevPct !== null ? currPct - prevPct : null | |
| }); | |
| } | |
| // Stop here for brand-new functions (no deltas possible) | |
| if (!prev) continue; | |
| // Δ comparisons | |
| if (currPct > prevPct + 0.01) { | |
| increased.push({ fn, prevPct, currPct }); | |
| } else if (currPct < prevPct - 0.01) { | |
| decreased.push({ fn, prevPct, currPct }); | |
| } | |
| // 🧱 Regressed to stub | |
| if (prev.matching > STUB_THRESHOLD && isStub) { | |
| stubbed.push({ fn, reason: 'regressed to stub' }); | |
| } | |
| } | |
| // ❌ Gone | |
| for (const [addr, fn] of prevMap) { | |
| if (!currMap.has(addr)) gone.push(fn); | |
| } | |
| const effective = ((matched / total) * 100).toFixed(2); | |
| const fullMatchPct = ((fullMatch / total) * 100).toFixed(2); | |
| function formatName(fn) { | |
| return fn.matching * 100 === 100 ? `🏆 ${fn.name}` : fn.name; | |
| } | |
| function formatAccuracy(pct) { | |
| return pct === 100 ? '**100.00% ✅**' : `${pct.toFixed(2)}%`; | |
| } | |
| function deltaTable(rows) { | |
| if (!rows.length) return '_None_'; | |
| return [ | |
| '| Address | Function | Before | After |', | |
| '|--------:|----------|--------|-------|', | |
| ...rows.map(r => | |
| `| \`${r.fn.address}\` | ${formatName(r.fn)} | ${r.prevPct.toFixed(2)}% | ${formatAccuracy(r.currPct)} |` | |
| ) | |
| ].join('\n'); | |
| } | |
| function simpleTable(rows) { | |
| if (!rows.length) return '_None_'; | |
| return [ | |
| '| Address | Function | Accuracy |', | |
| '|--------:|----------|----------|', | |
| ...rows.map(f => | |
| `| \`${f.address}\` | ${formatName(f)} | ${formatAccuracy(f.matching * 100)} |` | |
| ) | |
| ].join('\n'); | |
| } | |
| function newlyPerfectTable(rows) { | |
| if (!rows.length) return '_None_'; | |
| return [ | |
| '| Address | Function | Before | Now |', | |
| '|--------:|----------|--------|-----|', | |
| ...rows.map(r => { | |
| const before = r.prevPct === null ? '—' : `${r.prevPct.toFixed(2)}%`; | |
| return `| \`${r.fn.address}\` | 🏆 ${r.fn.name} | ${before} | **100.00% ✅** |`; | |
| }) | |
| ].join('\n'); | |
| } | |
| function lowAccuracyTable(rows) { | |
| if (!rows.length) return '_None_'; | |
| return [ | |
| '| Address | Function | Accuracy | Δ Since Last PR |', | |
| '|--------:|----------|----------|----------------|', | |
| ...rows | |
| .sort((a, b) => a.currPct - b.currPct) | |
| .map(r => { | |
| const delta = | |
| r.delta === null | |
| ? '—' | |
| : `${r.delta > 0 ? '+' : ''}${r.delta.toFixed(2)}%`; | |
| return `| \`${r.fn.address}\` | ${r.fn.name} | **${r.currPct.toFixed(2)}%** | ${delta} |`; | |
| }) | |
| ].join('\n'); | |
| } | |
| const goneTable = gone.length | |
| ? [ | |
| '| Address | Function | Previous Accuracy |', | |
| '|--------:|----------|-------------------|', | |
| ...gone.map(f => | |
| `| \`${f.address}\` | ${f.name} | ${(f.matching * 100).toFixed(2)}% |` | |
| ) | |
| ].join('\n') | |
| : '_None_'; | |
| const stubTable = stubbed.length | |
| ? [ | |
| '| Address | Function | Reason |', | |
| '|--------:|----------|--------|', | |
| ...stubbed.map(s => | |
| `| \`${s.fn.address}\` | ${s.fn.name} | ${s.reason} |` | |
| ) | |
| ].join('\n') | |
| : '_None_'; | |
| const timestamp = new Date().toISOString().replace('T', ' ').replace(/\..+/, ' UTC'); | |
| const body = [ | |
| TAG, | |
| '', | |
| '## 📊 CMR2 Accuracy Report', | |
| '', | |
| `**Functions:** ${total} `, | |
| `**Effective Accuracy:** **${effective}%** `, | |
| `**Fully Matching:** ${fullMatch} (${fullMatchPct}%)`, | |
| '', | |
| '---', | |
| '', | |
| '### 🎉 Newly Perfect Since Last PR', | |
| newlyPerfectTable(newlyPerfect), | |
| '', | |
| '---', | |
| '', | |
| '### 🚨 Functions Below 80% Accuracy (Non-Stubs)', | |
| '_Note: Missing data may be causing low accuracy on some of these_', | |
| lowAccuracyTable(lowAccuracy), | |
| '', | |
| '---', | |
| '', | |
| '### ⚠️ Decreased', | |
| deltaTable(decreased), | |
| '', | |
| '---', | |
| '', | |
| '### ✅ Increased', | |
| deltaTable(increased), | |
| '', | |
| '---', | |
| '', | |
| '### ➕ Added', | |
| simpleTable(added), | |
| '', | |
| '---', | |
| '', | |
| '### 🧱 Stubbed', | |
| stubTable, | |
| '', | |
| '---', | |
| '', | |
| '### ❌ Gone', | |
| goneTable, | |
| '', | |
| `_Last updated by CMR2Decomp Bot on ${timestamp}_` | |
| ].join('\n'); | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const comments = await github.paginate( | |
| github.rest.issues.listComments, | |
| { owner, repo, issue_number } | |
| ); | |
| const existing = comments.find(c => c.body.includes(TAG)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| } |