Rework to use content collections

This commit is contained in:
Luke Street
2026-03-05 10:10:37 -07:00
parent 75f8939bd5
commit 493b408890
18 changed files with 374 additions and 251 deletions
+1
View File
@@ -9,6 +9,7 @@
},
"dependencies": {
"astro": "^5.7.0",
"marked": "^17.0.4",
"sharp": "^0.34.5"
},
"pnpm": {
+39
View File
@@ -11,6 +11,9 @@ importers:
astro:
specifier: ^5.7.0
version: 5.18.0(rollup@4.59.0)(typescript@5.9.3)
marked:
specifier: ^17.0.4
version: 17.0.4
sharp:
specifier: ^0.34.5
version: 0.34.5
@@ -400,89 +403,105 @@ packages:
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
@@ -556,66 +575,79 @@ packages:
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.59.0':
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.59.0':
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.59.0':
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.59.0':
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.59.0':
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.59.0':
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.59.0':
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.59.0':
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.59.0':
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.59.0':
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
@@ -1057,6 +1089,11 @@ packages:
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
marked@17.0.4:
resolution: {integrity: sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==}
engines: {node: '>= 20'}
hasBin: true
mdast-util-definitions@6.0.0:
resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==}
@@ -2614,6 +2651,8 @@ snapshots:
markdown-table@3.0.4: {}
marked@17.0.4: {}
mdast-util-definitions@6.0.0:
dependencies:
'@types/mdast': 4.0.4
+118 -52
View File
@@ -1,54 +1,66 @@
---
import { Image } from 'astro:assets';
import type { Project } from '../data/projects';
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import { marked } from "marked";
interface Props {
project: Project;
project: CollectionEntry<"projects">;
}
const { project } = Astro.props;
const { Content } = await project.render();
// Render meta as markdown inline
const metaHtml = project.data.meta
? marked.parseInline(project.data.meta)
: null;
---
<article class="project">
<h2><a href={project.data.url}>{project.data.name}</a></h2>
<div class="project-badges">
<span class="project-langs">
{project.langs.map((lang) => (
<span class="lang">
<span class="lang-dot" style={`background-color: ${lang.color}`} />
{lang.name}
</span>
))}
{
project.data.langs.map((lang) => (
<span class="lang">
<span class="lang-dot" style={`background-color: ${lang.color}`} />
<span class="lang-name">{lang.name}</span>
</span>
))
}
</span>
<span class="github-star">
<a
class="github-button"
href={`https://github.com/${project.repo}`}
href={`https://github.com/${project.data.repo}`}
data-color-scheme="no-preference: dark_dimmed; light: light; dark: dark_dimmed;"
data-icon="octicon-star"
data-show-count="true"
aria-label={`Star ${project.repo} on GitHub`}
>Star</a>
aria-label={`Star ${project.data.repo} on GitHub`}>Star</a
>
</span>
</div>
<h2><a href={project.url}>{project.name}</a></h2>
<p class="project-desc">{project.desc}</p>
<p class="project-desc">{project.data.desc}</p>
<Fragment set:html={project.body} />
<div class="project-body">
<Content />
</div>
{project.meta && (
<p class="project-meta" set:html={project.meta} />
)}
{metaHtml && <p class="project-meta" set:html={metaHtml} />}
{project.images && (
<div class="project-images">
{project.images.map((img) => (
<a href={img.href} target="_blank" rel={img.rel}>
<Image src={img.src} alt={img.alt} width={800} />
</a>
))}
</div>
)}
{
project.data.images && (
<div class="project-images">
{project.data.images.map((img) => (
<a href={img.href} target="_blank" rel={img.rel}>
<Image src={img.src} alt={img.alt} width={800} />
</a>
))}
</div>
)
}
</article>
<style>
@@ -59,17 +71,23 @@ const { project } = Astro.props;
background: var(--surface-bg);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
cursor: pointer;
&:hover {
&:hover:not(:has(.project-body a:hover)):not(
:has(.project-meta a:hover)
):not(:has(.project-badges:hover)):not(:has(.project-images:hover)) {
border-color: var(--gray-400);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
}
@media (prefers-color-scheme: light) {
.project:hover {
.project:hover:not(:has(.project-body a:hover)):not(
:has(.project-meta a:hover)
):not(:has(.project-badges:hover)):not(:has(.project-images:hover)) {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
}
@@ -77,18 +95,26 @@ const { project } = Astro.props;
@media (max-width: 30em) {
.project {
padding: var(--spacer);
display: flex;
flex-direction: column;
}
}
h2 {
margin-top: 0;
margin-bottom: .25rem;
margin-bottom: 0.25rem;
font-size: 1.125rem;
}
h2 a {
color: var(--accent);
text-decoration: none;
transition: color 0.15s ease;
}
h2 a:hover,
h2 a:focus {
color: var(--link-hover-color);
}
/* Stretch h2 link over the whole card */
@@ -104,7 +130,7 @@ const { project } = Astro.props;
}
/* All other interactive elements sit above the stretched link */
.project :global(p a),
.project-body :global(a),
.project-meta :global(a),
.project-images a,
.project-badges {
@@ -113,32 +139,36 @@ const { project } = Astro.props;
}
.project-desc {
font-size: .9375rem;
font-size: 0.9375rem;
color: var(--gray-600);
font-style: italic;
margin-bottom: .75rem;
margin-bottom: 0.75rem;
}
.project :global(p) {
font-size: .9375rem;
margin-bottom: .75rem;
.project-body:not(:last-child) {
margin-bottom: 0.75rem;
}
.project :global(p:last-child) {
.project-body :global(p) {
font-size: 0.9375rem;
margin-bottom: 0.75rem;
}
.project-body :global(p:last-child) {
margin-bottom: 0;
}
.project-images {
display: grid;
grid-template-columns: 1fr 1fr;
gap: .5rem;
margin-top: .75rem;
gap: 0.5rem;
margin-top: 0.75rem;
margin-bottom: 0;
}
.project-images a {
display: block;
border-radius: calc(var(--border-radius) - 1px);
}
.project-images :global(img) {
@@ -147,27 +177,50 @@ const { project } = Astro.props;
border: 1px solid var(--border-color);
border-radius: calc(var(--border-radius) - 1px);
margin-bottom: 0;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.project-images a:hover :global(img),
.project-images a:focus :global(img) {
border-color: var(--gray-400);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
@media (prefers-color-scheme: light) {
.project-images a:hover :global(img),
.project-images a:focus :global(img) {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
}
.project :global(.project-meta) {
font-size: .8125rem;
font-size: 0.8125rem;
color: var(--gray-500);
font-style: italic;
margin-bottom: 0;
}
.project-badges {
position: absolute;
top: var(--spacer-2);
right: var(--spacer-2);
display: flex;
flex-wrap: wrap;
gap: .75rem;
float: right;
gap: 0.75rem;
}
@media (max-width: 30em) {
.project-badges {
float: none;
position: static;
margin-bottom: 1rem;
justify-content: space-between;
order: -1;
}
h2 {
order: 0;
}
}
@@ -175,25 +228,38 @@ const { project } = Astro.props;
line-height: 0;
}
/* If the GitHub button fails to load, hide the container */
.github-star:has(> a) {
display: none;
}
.project-langs {
--lang-optical-nudge: clamp(0px, 0.12em, 0.7px);
display: flex;
gap: .5rem;
gap: 0.5rem;
font-family: var(--heading-font);
font-size: .6875rem;
font-size: 0.6875rem;
color: var(--gray-500);
line-height: 1;
}
.lang {
display: flex;
display: inline-flex;
align-items: center;
gap: .3rem;
gap: 0.3rem;
}
.lang-name {
display: inline-block;
line-height: 1;
transform: translateY(var(--lang-optical-nudge));
}
.lang-dot {
display: inline-block;
width: .5rem;
height: .5rem;
display: block;
flex: 0 0 0.5rem;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
}
</style>
+27
View File
@@ -0,0 +1,27 @@
import { defineCollection, z } from 'astro:content';
const projects = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
order: z.number(),
name: z.string(),
url: z.string().url(),
repo: z.string(),
desc: z.string(),
langs: z.array(z.object({
name: z.string(),
color: z.string(),
})),
meta: z.string().optional(),
images: z.array(z.object({
src: image(),
alt: z.string(),
href: z.string(),
rel: z.string().optional(),
})).optional(),
}),
});
export const collections = {
projects,
};
+12
View File
@@ -0,0 +1,12 @@
---
order: 8
name: 'aurora'
url: 'https://github.com/encounter/aurora'
repo: 'encounter/aurora'
desc: 'Source-level GameCube & Wii compatibility layer for decompilation projects'
langs:
- name: 'C++'
color: '#f34b7d'
---
Aurora reimplements the GX API, translating the calls to native graphics backends like Vulkan, Metal, D3D12, and WebGPU.
+23
View File
@@ -0,0 +1,23 @@
---
order: 2
name: 'decomp.dev'
url: 'https://decomp.dev'
repo: 'encounter/decomp.dev'
desc: 'Progress hub for decompilation projects'
langs:
- name: 'Rust'
color: '#dea584'
images:
- src: '../../assets/decomp.dev-home.png'
alt: 'decomp.dev'
href: 'https://decomp.dev'
rel: 'noopener'
- src: '../../assets/decomp.dev-prime.png'
alt: 'Metroid Prime on decomp.dev'
href: 'https://decomp.dev/PrimeDecomp/prime'
rel: 'noopener'
---
`decomp.dev` tracks progress for more than 80 decompilation projects. With data driven by `objdiff`'s progress reports, it provides granular information down to individual translation units and functions, plus a tree view for exploring project structure. Projects can categorize units, track multiple game versions, and navigate full commit-by-commit history.
All of this information is also exposed through its [API](https://decomp.dev/api), along with a badge system for embedding live progress in project `README`s.
+15
View File
@@ -0,0 +1,15 @@
---
order: 4
name: 'decomp-toolkit'
url: 'https://github.com/encounter/decomp-toolkit'
repo: 'encounter/decomp-toolkit'
desc: 'PowerPC static analyzer, binary delinker, and GameCube/Wii swiss army knife'
langs:
- name: 'Rust'
color: '#dea584'
meta: 'Used by more than 50 decompilation projects, including several now-complete ones like [The Legend of Zelda: Twilight Princess](https://github.com/zeldaret/tp) and [Mario Party 4](https://github.com/mariopartyrd/marioparty4).'
---
`decomp-toolkit` takes a compiled binary and produces fully relinkable objects. Its analyzer handles function and object boundary detection, signature analysis, and rebuilding relocations, all with minimal configuration. [dtk-template](https://github.com/encounter/dtk-template) is a project template and build system built on top of decomp-toolkit.
Its approach to matching decompilation has since been applied to other platforms, including [ds-decomp](https://github.com/AetiasHax/ds-decomp) for Nintendo DS and [jeff](https://github.com/rjkiv/jeff) for Xbox 360.
+22
View File
@@ -0,0 +1,22 @@
---
order: 7
name: 'ghidra-panel'
url: 'https://github.com/encounter/ghidra-panel'
repo: 'encounter/ghidra-panel'
desc: 'Self-service portal for managing access to shared Ghidra repositories'
langs:
- name: 'Go'
color: '#00add8'
- name: 'Java'
color: '#b07219'
meta: 'Powers the collaboration infrastructure on [decomp.dev](https://ghidra.decomp.dev).'
images:
- src: '../../assets/ghidra-panel-home.png'
alt: 'ghidra-panel home'
href: 'https://media.githubusercontent.com/media/encounter/ghidra-panel/main/.github/img/home.png'
- src: '../../assets/ghidra-panel-repo.png'
alt: 'ghidra-panel repository view'
href: 'https://media.githubusercontent.com/media/encounter/ghidra-panel/main/.github/img/repo.png'
---
`ghidra-panel` integrates with Ghidra Server through an in-process gRPC plugin, allowing repository administrators to manage users and permissions. Users authenticate with Discord and can request repository access, sending a notification with a one-click approval link for admins.
+15
View File
@@ -0,0 +1,15 @@
---
order: 9
name: 'metaforce'
url: 'https://github.com/AxioDL/metaforce'
repo: 'AxioDL/metaforce'
desc: 'Native reimplementation of the Metroid Prime engine'
langs:
- name: 'C++'
color: '#f34b7d'
meta: 'Currently on hiatus as the [matching decompilation of Metroid Prime](https://github.com/PrimeDecomp/prime) progresses.'
---
Metaforce (formerly URDE) started as a passion project reimplementing parts of the Metroid Prime engine, and eventually transformed into a nearly-complete non-matching decompilation.
[Project website](https://axiodl.com)
+15
View File
@@ -0,0 +1,15 @@
---
order: 5
name: 'nod'
url: 'https://github.com/encounter/nod'
repo: 'encounter/nod'
desc: 'GameCube and Wii disc image library and CLI tool'
langs:
- name: 'Rust'
color: '#dea584'
meta: 'Used by [TinyWiiBackupManager](https://github.com/mq1/TinyWiiBackupManager).'
---
`nod` supports reading and converting all GameCube and Wii disc image formats, with a simple and performant API. Open any disc image and get a `Read + Seek + BufRead` handle. Converting to ISO is just reading from that handle and writing to a file, regardless of the source format. Open a partition and get the same interface, transparently handling Wii encryption and hashing.
Reading and writing are multithreaded, and `nod` produces smaller disc images faster than both Dolphin and NKit v2. C bindings are available for FFI.
+23
View File
@@ -0,0 +1,23 @@
---
order: 1
name: 'objdiff'
url: 'https://github.com/encounter/objdiff'
repo: 'encounter/objdiff'
desc: 'Diffing tool for decompilation projects'
langs:
- name: 'Rust'
color: '#dea584'
- name: 'TypeScript'
color: '#3178c6'
images:
- src: '../../assets/screen-symbols.png'
alt: 'Symbol Screenshot'
href: 'https://raw.githubusercontent.com/encounter/objdiff/refs/heads/main/assets/screen-symbols.png'
- src: '../../assets/screen-diff.png'
alt: 'Diff Screenshot'
href: 'https://raw.githubusercontent.com/encounter/objdiff/refs/heads/main/assets/screen-diff.png'
---
`objdiff` compares object files across functions and data. It supports ARM, ARM64, MIPS, PowerPC, SuperH, and x86(-64). It provides a GUI, TUI, and JSON output for integration with other tools and agentic workflows.
The core diffing engine compiles to WASM and runs as a [web frontend](https://github.com/encounter/objdiff-web) in [decomp.me](https://decomp.me) and as a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=decomp-dev.objdiff). Its progress reporting system powers [decomp.dev](https://decomp.dev).
+15
View File
@@ -0,0 +1,15 @@
---
order: 6
name: 'powerpc-rs'
url: 'https://github.com/encounter/powerpc-rs'
repo: 'encounter/powerpc-rs'
desc: 'PowerPC disassembler and assembler'
langs:
- name: 'Rust'
color: '#dea584'
meta: 'Used as the PowerPC backend for objdiff and decomp-toolkit.'
---
`powerpc-rs` is driven by a declarative instruction set definition that is compiled into Rust at build time, similar to LLVM's TableGen. It supports the full PowerPC instruction set along with Gekko/Broadway paired singles (GameCube/Wii) and Xenon VMX128 (Xbox 360) extensions.
The disassembler has been fuzzed over all 4.29 billion possible 32-bit instruction values and runs at ~275M instructions per second.
+15
View File
@@ -0,0 +1,15 @@
---
order: 3
name: 'wibo'
url: 'https://github.com/decompals/wibo'
repo: 'decompals/wibo'
desc: 'Lightweight Win32 binary loader for Linux and macOS'
langs:
- name: 'C++'
color: '#f34b7d'
meta: 'Used by [decomp.me](https://decomp.me) to run containerized Windows compilers.'
---
`wibo` runs 32-bit Windows command-line tools with minimal overhead. It implements a substantial portion of the Win32 API: file I/O, threading, heap management, DLL loading, TLS, and async I/O with platform-specific backends (`io_uring`, `epoll`, `kqueue`).
On 64-bit hosts, it constrains the guest address space to the lower 2GB and bridges calling conventions with generated trampolines. Runs on macOS under Rosetta 2.
-176
View File
@@ -1,176 +0,0 @@
import type { ImageMetadata } from 'astro';
import decompDevHome from '../assets/decomp.dev-home.png';
import decompDevPrime from '../assets/decomp.dev-prime.png';
import screenSymbols from '../assets/screen-symbols.png';
import screenDiff from '../assets/screen-diff.png';
import ghidraPanelHome from '../assets/ghidra-panel-home.png';
import ghidraPanelRepo from '../assets/ghidra-panel-repo.png';
export interface ProjectImage {
src: ImageMetadata;
alt: string;
href: string;
rel?: string;
}
export interface Project {
name: string;
url: string;
repo: string;
desc: string;
langs: { name: string; color: string }[];
body: string;
meta?: string;
images?: ProjectImage[];
}
const LANG = {
Rust: { name: 'Rust', color: '#dea584' },
TypeScript: { name: 'TypeScript', color: '#3178c6' },
Cpp: { name: 'C++', color: '#f34b7d' },
Go: { name: 'Go', color: '#00add8' },
Java: { name: 'Java', color: '#b07219' },
} as const;
export const projects: Project[] = [
{
name: 'objdiff',
url: 'https://github.com/encounter/objdiff',
repo: 'encounter/objdiff',
desc: 'Diffing tool for decompilation projects',
langs: [LANG.Rust, LANG.TypeScript],
body: `
<p><code>objdiff</code> compares object files across functions and data. It supports ARM, ARM64, MIPS, PowerPC, SuperH, and x86(-64). It provides a GUI, TUI, and JSON output for integration with other tools and agentic workflows.</p>
<p>The core diffing engine compiles to WASM and runs as a <a href="https://github.com/encounter/objdiff-web">web frontend</a> in <a href="https://decomp.me">decomp.me</a> and as a <a href="https://marketplace.visualstudio.com/items?itemName=decomp-dev.objdiff">VS Code extension</a>. Its progress reporting system powers <a href="https://decomp.dev">decomp.dev</a>.</p>
`,
images: [
{
src: screenSymbols,
alt: 'Symbol Screenshot',
href: 'https://raw.githubusercontent.com/encounter/objdiff/refs/heads/main/assets/screen-symbols.png',
},
{
src: screenDiff,
alt: 'Diff Screenshot',
href: 'https://raw.githubusercontent.com/encounter/objdiff/refs/heads/main/assets/screen-diff.png',
},
],
},
{
name: 'decomp.dev',
url: 'https://decomp.dev',
repo: 'encounter/decomp.dev',
desc: 'Progress hub for decompilation projects',
langs: [LANG.Rust],
body: `
<p><code>decomp.dev</code> tracks progress for more than 80 decompilation projects. With data driven by <code>objdiff</code>'s progress reports, it provides granular information down to individual translation units and functions, plus a tree view for exploring project structure. Projects can categorize units, track multiple game versions, and navigate full commit-by-commit history.</p>
<p>All of this information is also exposed through its <a href="https://decomp.dev/api">API</a>, along with a badge system for embedding live progress in project <code>README</code>s.</p>
`,
images: [
{
src: decompDevHome,
alt: 'decomp.dev',
href: 'https://decomp.dev',
rel: 'noopener',
},
{
src: decompDevPrime,
alt: 'Metroid Prime on decomp.dev',
href: 'https://decomp.dev/PrimeDecomp/prime',
rel: 'noopener',
},
],
},
{
name: 'wibo',
url: 'https://github.com/decompals/wibo',
repo: 'decompals/wibo',
desc: 'Lightweight Win32 binary loader for Linux and macOS',
langs: [LANG.Cpp],
body: `
<p><code>wibo</code> runs 32-bit Windows command-line tools with minimal overhead. It implements a substantial portion of the Win32 API: file I/O, threading, heap management, DLL loading, TLS, and async I/O with platform-specific backends (io_uring, epoll, kqueue). On 64-bit hosts, it constrains the guest address space to the lower 2GB and bridges calling conventions with generated trampolines. Runs on macOS under Rosetta 2.</p>
`,
meta: 'Used by <a href="https://decomp.me">decomp.me</a> to run Windows compilers in containers.',
},
{
name: 'decomp-toolkit',
url: 'https://github.com/encounter/decomp-toolkit',
repo: 'encounter/decomp-toolkit',
desc: 'PowerPC static analyzer, binary delinker, and GameCube/Wii swiss army knife',
langs: [LANG.Rust],
body: `
<p><code>decomp-toolkit</code> takes a compiled binary and produces fully relinkable objects. Its analyzer handles function and object boundary detection, signature analysis, and rebuilding relocations, all with minimal configuration. <a href="https://github.com/encounter/dtk-template">dtk-template</a> is a project template and build system built on top of decomp-toolkit.</p>
<p>Its approach to matching decompilation has since been applied to other platforms, including <a href="https://github.com/AetiasHax/ds-decomp">ds-decomp</a> for Nintendo DS and <a href="https://github.com/rjkiv/jeff">jeff</a> for Xbox 360.</p>
`,
meta: 'Used by more than 50 decompilation projects, including several now-complete ones like <a href="https://github.com/zeldaret/tp">The Legend of Zelda: Twilight Princess</a> and <a href="https://github.com/mariopartyrd/marioparty4">Mario Party 4</a>.',
},
{
name: 'nod',
url: 'https://github.com/encounter/nod',
repo: 'encounter/nod',
desc: 'GameCube and Wii disc image library and CLI tool',
langs: [LANG.Rust],
body: `
<p><code>nod</code> supports reading and converting all GameCube and Wii disc image formats, with a simple and performant API. Open any disc image and get a <code>Read + Seek + BufRead</code> handle. Converting to ISO is just reading from that handle and writing to a file, regardless of the source format. Open a partition and get the same interface, transparently handling Wii encryption and hashing.</p>
<p>Reading and writing are multithreaded, and <code>nod</code> produces smaller disc images faster than both Dolphin and NKit v2. C bindings are available for FFI.</p>
`,
meta: 'Used by <a href="https://github.com/mq1/TinyWiiBackupManager">TinyWiiBackupManager</a>.',
},
{
name: 'powerpc-rs',
url: 'https://github.com/encounter/powerpc-rs',
repo: 'encounter/powerpc-rs',
desc: 'PowerPC disassembler and assembler',
langs: [LANG.Rust],
body: `
<p><code>powerpc-rs</code> is driven by a declarative instruction set definition that is compiled into Rust at build time, similar to LLVM's TableGen. It supports the full PowerPC instruction set along with Gekko/Broadway paired singles (GameCube/Wii) and Xenon VMX128 (Xbox 360) extensions.</p>
<p>The disassembler has been fuzzed over all 4.29 billion possible 32-bit instruction values and runs at ~275M instructions per second.</p>
`,
meta: 'Used as the PowerPC backend for objdiff and decomp-toolkit.',
},
{
name: 'ghidra-panel',
url: 'https://github.com/encounter/ghidra-panel',
repo: 'encounter/ghidra-panel',
desc: 'Self-service portal for managing access to shared Ghidra repositories',
langs: [LANG.Go, LANG.Java],
body: `
<p><code>ghidra-panel</code> integrates with Ghidra Server through an in-process gRPC plugin, allowing repository administrators to manage users and permissions. Users authenticate with Discord and can request repository access, sending a notification with a one-click approval link for admins.</p>
`,
meta: 'Powers the collaboration infrastructure on <a href="https://ghidra.decomp.dev">decomp.dev</a>.',
images: [
{
src: ghidraPanelHome,
alt: 'ghidra-panel home',
href: 'https://media.githubusercontent.com/media/encounter/ghidra-panel/main/.github/img/home.png',
},
{
src: ghidraPanelRepo,
alt: 'ghidra-panel repository view',
href: 'https://media.githubusercontent.com/media/encounter/ghidra-panel/main/.github/img/repo.png',
},
],
},
{
name: 'aurora',
url: 'https://github.com/encounter/aurora',
repo: 'encounter/aurora',
desc: 'Source-level GameCube & Wii compatibility layer for decompilation projects',
langs: [LANG.Cpp],
body: `
<p>Aurora reimplements the GX API, translating the calls to native graphics backends like Vulkan, Metal, D3D12, and WebGPU.</p>
`,
},
{
name: 'metaforce',
url: 'https://github.com/AxioDL/metaforce',
repo: 'AxioDL/metaforce',
desc: 'Native reimplementation of the Metroid Prime engine',
langs: [LANG.Cpp],
body: `
<p>Metaforce (formerly URDE) started as a passion project reimplementing parts of the Metroid Prime engine, and eventually transformed into a nearly-complete non-matching decompilation.</p>
<p><a href="https://axiodl.com">Project website</a></p>
`,
meta: 'Currently on hiatus as the <a href="https://github.com/PrimeDecomp/prime">matching decompilation of Metroid Prime</a> progresses.',
},
];
+13 -6
View File
@@ -23,7 +23,10 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
<meta property="og:title" content={title} />
{description && <meta property="og:description" content={description} />}
<meta property="og:site_name" content="@encounter" />
<meta property="og:image" content={new URL('/apple-touch-icon-precomposed.png', Astro.site)} />
<meta
property="og:image"
content={new URL("/apple-touch-icon-precomposed.png", Astro.site)}
/>
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content={title} />
@@ -36,7 +39,11 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap"
/>
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/apple-touch-icon-precomposed.png" />
<link
rel="apple-touch-icon-precomposed"
sizes="144x144"
href="/apple-touch-icon-precomposed.png"
/>
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="darkreader-lock" />
</head>
@@ -104,10 +111,10 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
.masthead-title small {
display: block;
margin-top: .25rem;
margin-top: 0.25rem;
font-family: var(--body-font);
font-weight: 400;
font-size: .875rem;
font-size: 0.875rem;
color: var(--gray-500);
letter-spacing: 0.02em;
}
@@ -117,11 +124,11 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
padding-bottom: var(--spacer-2);
border-top: 1px solid var(--border-color);
font-family: var(--heading-font);
font-size: .75rem;
font-size: 0.75rem;
color: var(--gray-500);
}
</style>
<style is:global>
@import '../styles/global.css';
@import "../styles/global.css";
</style>
+4 -3
View File
@@ -1,11 +1,12 @@
---
import Layout from '../layouts/Layout.astro';
import Layout from "../layouts/Layout.astro";
---
<Layout title="404: Page not found &middot; @encounter">
<Layout title="404: Page not found · @encounter">
<h1>404: Page not found</h1>
<p class="lead">
Sorry, we've misplaced that URL or it's pointing to something that doesn't exist.
Sorry, we've misplaced that URL or it's pointing to something that doesn't
exist.
<a href="/">Head back home</a> to try finding it again.
</p>
</Layout>
+15 -12
View File
@@ -1,15 +1,20 @@
---
import Layout from '../layouts/Layout.astro';
import ProjectCard from '../components/ProjectCard.astro';
import { projects } from '../data/projects';
import Layout from "../layouts/Layout.astro";
import ProjectCard from "../components/ProjectCard.astro";
import { getCollection } from "astro:content";
const projects = (await getCollection("projects")).sort(
(a, b) => a.data.order - b.data.order,
);
---
<Layout
title="@encounter &middot; Luke Street"
title="@encounter · Luke Street"
description="I build tools for game decompilation and reverse engineering, with a focus on GameCube and Wii."
>
<p class="intro">
I build tools for game decompilation and reverse engineering, with a focus on GameCube and Wii.
I build tools for game decompilation and reverse engineering, with a focus
on GameCube and Wii.
</p>
<div class="social-links">
@@ -20,9 +25,7 @@ import { projects } from '../data/projects';
<h1 class="section-heading">Projects</h1>
{projects.map((project) => (
<ProjectCard project={project} />
))}
{projects.map((project) => <ProjectCard project={project} />)}
</Layout>
<style>
@@ -30,7 +33,7 @@ import { projects } from '../data/projects';
font-size: 1.125rem;
color: var(--gray-700);
line-height: 1.6;
margin-bottom: .5rem;
margin-bottom: 0.5rem;
}
@media (prefers-color-scheme: light) {
@@ -41,7 +44,7 @@ import { projects } from '../data/projects';
.social-links {
font-family: var(--heading-font);
font-size: .8125rem;
font-size: 0.8125rem;
margin-bottom: var(--spacer-2);
}
@@ -59,7 +62,7 @@ import { projects } from '../data/projects';
.sep {
color: var(--gray-500);
font-family: var(--heading-font);
font-size: .8125rem;
font-size: 0.8125rem;
}
.section-heading {
@@ -69,7 +72,7 @@ import { projects } from '../data/projects';
letter-spacing: 0.08em;
color: var(--gray-500);
border-bottom: 1px solid var(--border-color);
padding-bottom: .5rem;
padding-bottom: 0.5rem;
margin-top: var(--spacer-2);
margin-bottom: var(--spacer);
}
+2 -2
View File
@@ -27,8 +27,8 @@
--body-bg: var(--gray-000);
--surface-bg: var(--gray-100);
--link-color: #58a6ff;
--link-hover-color: #79c0ff;
--link-color: #79c0ff;
--link-hover-color: #a5d8ff;
--heading-color: var(--gray-900);