<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://boricj.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://boricj.net/" rel="alternate" type="text/html" /><updated>2025-10-25T10:11:46+02:00</updated><id>https://boricj.net/feed.xml</id><title type="html">boricj’s entropy-increasing blog</title><subtitle>Increasing entropy inside the universe.</subtitle><author><name>Jean-Baptiste Boric</name><email>jean.baptiste.boric@gmail.com</email></author><entry><title type="html">ghidra-delinker-extension and boricj do ACM CCS 2025</title><link href="https://boricj.net/2025/10/25/ghidra-delinker-extension-and-boricj-do-acm-ccs-2025.html" rel="alternate" type="text/html" title="ghidra-delinker-extension and boricj do ACM CCS 2025" /><published>2025-10-25T02:00:00+02:00</published><updated>2025-10-25T02:00:00+02:00</updated><id>https://boricj.net/2025/10/25/ghidra-delinker-extension-and-boricj-do-acm-ccs-2025</id><content type="html" xml:base="https://boricj.net/2025/10/25/ghidra-delinker-extension-and-boricj-do-acm-ccs-2025.html"><![CDATA[<h2 id="what">What?</h2>

<ul>
  <li><a href="https://www.acm.org/">ACM</a> (Association for Computing Machinery): the world’s largest society for the study of computing.</li>
  <li><a href="https://www.sigsac.org/">SIGSAC</a> (Special Interest Group on Security, Audit and Control): one of 38 
groups inside of the ACM, this one is focused on cybersecurity.</li>
  <li><a href="https://www.sigsac.org/ccs.html">ACM CCS</a> (Computer and Communications Security): the flagship annual conference of SIGSAC.</li>
  <li><a href="https://sure-workshop.org/">SURE workshop</a> (Software Understanding and Reverse-Engineering): one of many, <em>many</em> workshops held in the ACM CCS conference, this one is brand-spanking new in the 2025 edition.</li>
</ul>

<p><em>Ergo</em>:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/assets/2025/10/25/acm-ccs-2025-banner-boricj.jpg" />
                <figcaption>Figure 1: boricj at ACM CCS 2025.</figcaption>
            </figure>
        </div>
    </label>
</div>

<h2 id="no-i-meant-what-the-fuck">No, I meant <em>WHAT THE FUCK</em>?</h2>

<p>I don’t know!</p>

<p>I was shitposting in a Discord server and next thing I know, I’m standing next to a poster in Taipei, showcasing to world-class cybersecurity experts <em>Mad Max</em>-driven development!</p>

<div class="box box-information">
  <p>I’d better start over from the beginning.
I assume you already know about my delinking work; if not, you may imagine Doctor Frankenstein, but with bits instead of gibs.</p>
</div>

<h3 id="act-1-the-universe-conspires-to-make-me-do-something">Act 1: the Universe conspires to make me do something</h3>

<h4 id="one-does-not-simply-stumble-into-the-sure-workshop">One does not simply stumble into the SURE workshop</h4>

<p>Right after we left off <a href="/2025/10/11/ghidra-delinker-extension-still-snowballing-out-of-control.html">last time</a>, I casually stumbled upon an invite link to the SURE Discord server whilst idling around the <a href="https://decomp.me">decomp.me</a> Discord server, as one does.
There, I saw a <a href="https://sure-workshop.org/cfp/">call for papers</a> and this fateful statement was uttered in a Discord channel by <del>a stark raving lunatic</del> yours truly:</p>

<blockquote>
  <p>Ooh, they have a workshop upcoming and they want non-academics to submit papers? I’ve never had an opportunity to melt down the brains of lots of academics at once before…</p>
</blockquote>

<p>… and people actually <em>cheered</em> me for it.</p>

<div class="box box-warning">
  <p>That should’ve been the first hint that something was about to go horribly right.</p>
</div>

<h4 id="whats-a-paper">What’s a paper?</h4>

<p>The call for papers was sent out two months before the deadline and a month had already passed by the time I saw it.
I mean, I’m quite adept at burning down computer science literature, how hard can it be to write some?</p>

<p><strong>Incredibly hard.</strong></p>

<p>My initial plans for a long (12 page) paper with quantitative case studies were quickly crushed by time constraints.
I cut down the paper down to the bone and then some into a short (6 page) paper with an introduction to the topic and two qualitative case reports, working on it on a month’s worth of spare time.</p>

<div class="box box-warning">
  <p>It took all of the combined might of Gemini and Copilot to massage my first draft into something that vaguely looks like academic vernacular.
Because if you haven’t picked up on this by now, my usual style of writing is anything <em>but</em> academic.</p>
</div>

<p>So the day arrives, the paper… <em>exists</em> I guess and I submit it, exhausted and deeply unsatisfied by it.
Surely, the worst they can say is —</p>

<blockquote>
  <p>Although we were unable to accept your work for publication, the PC
still found your work interesting and worth broader discussion at the
workshop.
We want to invite you to present your work as a poster at the
SURE Workshop on October 13th.
We clarify that this work will not appear
in proceedings, but we think it can still bring value to authors.</p>
</blockquote>

<p>— get over here?</p>

<p><strong>WHAT</strong>.</p>

<p>I have received an invitation from a workshop’s program committee, to present a poster at one of the world’s most prestigious academic cybersecurity conferences…
on account of a so-called ‘paper’ where I describe the butchering of computer programs into unholy chimeras <em>as a hobby</em>.</p>

<div class="box box-information">
  <p>This is the kind of world-class conference with a $1810 entry ticket, happening in a convention center whose total area is measured in hectares, attended by professional members of an academic, industrial or state organization, all expenses paid in a five star hotel room with a king bed and a marble bathroom.</p>
</div>

<div class="box box-warning">
  <p>You’re attending because you have either managed to land one of the accepted papers (with is by itself an achievement given the exclusive acceptance rate) or you have business to attend alongside the brightest of the field.
You don’t just happen to receive an invitation from a program committee to present anything there <em>as a hobbyist</em>.
That just, like, doesn’t happen, <strong>ever</strong>.</p>
</div>

<p>There’s only so much I can try to convey how this is utterly impossible in one go, so we’ll set that aside for now.</p>

<p>As for the paper itself, it received two positive and one negative reviews.
The main criticisms were:</p>

<ul>
  <li>The paper failed to state anything about the availability of the tooling (because I’m a doofus that thought anonymization for a double-blind review meant <em>when in doubt, redact and omit everything</em>).</li>
  <li>The paper fails to demonstrate this approach is applicable to multiple architecture and platforms (my case studies used Windows and Linux platforms, but only on x86).</li>
  <li>The evaluation is weak, containing only two targets and has no quantifiable data.</li>
  <li>The paper missed closely related prior art.</li>
</ul>

<div class="box box-information">
  <p>Turns out academia had done this idea about a decade ago, with the following papers:</p>
  <ul>
    <li><a href="https://www.usenix.org/conference/usenixsecurity15/technical-sessions/presentation/wang-shuai">Reassembleable Disassembling</a></li>
    <li><a href="https://www.ndss-symposium.org/ndss2017/ndss-2017-programme/ramblr-making-reassembly-great-again/">Ramblr: Making Reassembly Great Again</a></li>
  </ul>

  <p>I missed that work, partly because when I looked around I only thought about object files and not assembly as a suitable file format.</p>
</div>

<p>I will not put this attempt of a paper online.
I already thought that it was very weak as a piece of scientific literature before the review.
Now that I have the full context on how it fared, bringing it up to a satisfactory standard would take a <strong>massive</strong> amount of work.
Like, a master’s thesis amount of work.</p>

<h4 id="oh-crap-i-need-papersinternational-cardsbagsclothestravel-supplies">Oh crap, I need papers/international cards/bags/clothes/travel supplies/…</h4>

<p>Right, so I’ve basically received an invitation from <em>Hogwarts, school of Witchcraft and Wizardry</em>, to attend their flagship conference.
Oh, and it’s in two months at the other side of the planet, whereas I’ve never ventured further away than the next county in my entire adult life… and groaning audibly whenever I do so.</p>

<p>I mean, I don’t have a passport and as a chaotic individual I harbor a deeply-ingrained distrust of bureaucracy.
Surely, the authorities will —</p>

<table>
  <thead>
    <tr>
      <th>Milestone</th>
      <th>Driving license</th>
      <th>Identity card</th>
      <th>Passport</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Requested on</td>
      <td>2025-08-17</td>
      <td>2025-08-21</td>
      <td>2025-08-21</td>
    </tr>
    <tr>
      <td>In production</td>
      <td>2025-08-19</td>
      <td>2025-08-26</td>
      <td>2025-08-27</td>
    </tr>
    <tr>
      <td>Arrived on</td>
      <td>2025-09-01</td>
      <td>2025-09-02</td>
      <td>2025-09-02</td>
    </tr>
  </tbody>
</table>

<p>— possibly break the speed record of processing on all documents?</p>

<p>That’s less than two weeks, even for my identity card which was renewed for a reason (still valid but requesting the new format) that is explicitly deprioritized.
<em>Supposedly</em>, it should’ve taken anywhere from four to six weeks…</p>

<div class="box box-warning">
  <p>Now I’m beginning to believe that this is a fixed point in time, and that failure to attend would unravel the very fabric of the space-time continuum and destroy the entire Universe.</p>
</div>

<p>The rest of the required paperwork and supplies were sourced at a leisurely pace (not buying supplies without a conference registration, can’t pay the conference registration fee or hotel deposit without a Visa Premier card, not getting the card until I heard back from my employer…).
In hindsight, I could’ve reordered my acquisitions and registrations to better spread the amount of outgoing money.</p>

<h4 id="i-cant-believe-im-making-academic-art">I can’t believe I’m making academic art</h4>

<p>So I said yes to presenting an academic poster.
That means I actually need to have something to hang on a panel.
Seeing how bad I’m at writing academic papers, surely me making an academic poster will —</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/assets/acm-ccs-2025-poster-delinking-stripping-programs-for-parts.png" />
                <figcaption>Figure 2: "Delinking: stripping programs for parts", SURE 2025.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>— turn out decent?</p>

<div class="box box-information">
  <p>You can download this poster here:</p>
  <ul>
    <li><a href="/assets/acm-ccs-2025-poster-delinking-stripping-programs-for-parts.pdf" target="_blank">PDF</a></li>
    <li><a href="/assets/acm-ccs-2025-poster-delinking-stripping-programs-for-parts.svg" target="_blank">SVG</a></li>
  </ul>
</div>

<p>The irony that all of the time that I’ve spent doodling… <em>art</em>… during my tertiary education in class, turning out to be a useful skill in an academical context eight years later, is not lost on me.
Though I must admit that digital, vector and serious art are all new to me.
I also had <em>no</em> idea what an academic poster was supposed to look like, nor did I know what was the meta around them.</p>

<p>It took me six week-ends to wrangle Inkscape into doing something I consider passable (<em>why oh why doesn’t it have geometric constraints?</em>).
Yet, despite the amount of work that I’ve put into this, I still hate lots of things about it:</p>
<ul>
  <li>The headings for each section are basically background noise, they simply don’t pop out.</li>
  <li>It lacks breathing space on the vertical axis, it’s stacked too tightly.</li>
  <li>The second and third diagrams of the first row don’t quite work, as people need to do a double take to actually understand what goes on.</li>
  <li>The diagrams on the second row aren’t complete, self-contained explanations; the linking one doesn’t show relocation resolving and the delinking one doesn’t show how references turn into relocations, but I don’t know how to draw that without overloading them.</li>
  <li>The second diagram of the third row doesn’t work; it’s supposed to have the horizontal axis showing time and the vertical axis showing decompilation progress, but it fails to convey both intuitively.</li>
  <li>The links aren’t underlined and in blue to evoke hyperlinks.</li>
  <li>The join arrows of the first and third diagrams of the third row are so goddamn <strong>ugly</strong>.</li>
</ul>

<div class="box box-warning">
  <p>You may think that I’m really hard on myself.
It’s the downside of developing an artist’s eye: you spot all of the mistakes in your own work and you are unable to fix them, regardless how many times you’re redoing these parts.
Oh, and the longer you stare at your own work, the more mistakes you notice…</p>
</div>

<h4 id="i-cant-believe-im-making-business-art">I can’t believe I’m making business art</h4>

<p>I figured I should probably get a business card, as it sounded like something professionals would do.
So, I spent another afternoon the week before the departure coming up with this:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/assets/boricj.png" />
                <figcaption>Figure 3: boricj's business card.</figcaption>
            </figure>
        </div>
    </label>
</div>

<div class="box box-information">
  <p>You can download this business card here:</p>
  <ul>
    <li><a href="/assets/boricj.pdf" target="_blank">PDF</a></li>
    <li><a href="/assets/boricj.svg" target="_blank">SVG</a></li>
    <li><a href="/assets/boricj.vcf" target="_blank">vCard</a></li>
  </ul>
</div>

<p>My requirements are that I don’t want to order a new print anytime soon, so I carefully designed it so that it contains all of my information that isn’t subject to change, excluding things like job title or employer.
I don’t see a point in putting my face on it and although putting my online avatar (an absurdly adorable picture of my cat’s face) was tempting, I’ve decided to put a QR code containing a vCard instead.</p>

<p>I ordered a print of 250 cards on bamboo paper (90% bamboo, 10% cotton).
Not because it is ecological, but because it’s unconventional and it gave a look and feel reminiscent of static, analog noise.</p>

<h4 id="a-trip-to-the-other-side-of-the-world">A trip to the other side of the world</h4>

<p>As the start of the conference draws near, so does my round-trip to Taiwan.
This is my outbound journey:</p>

<bw-trip-segment class="ng-star-inserted">
	<bwc-flight-segment class="bw-trip-segment bw-trip-segment__travel-container ng-star-inserted">
		<div class="bwc-flight-segment bwc-typo-body-m-regular">
			<div class="bwc-flight-segment__origin-container">
				<div class="bwc-flight-segment__node bwc-flight-segment__node--start bwc-typo-bold">
					<span class="bwc-flight-segment__node-dot"></span>
					<div class="bwc-flight-segment__time"><span class="bwc-flight-segment__time-span">14:05 </span></div>
					<span class="bwc-flight-segment__airport"> Lyon, aéroport Saint Exupéry (LYS)</span>
				</div>
				<div class="bwc-flight-segment__flight-status ng-star-inserted">
					<bwc-flight-status data-hg="MdLC8">
						<div class="bwc-flight-status bwc-flight-status__success ng-star-inserted"><span class="bwc-typo-body-m-regular"><span>Arrived</span></span></div>
					</bwc-flight-status>
				</div>
				<div class="bwc-flight-segment__details bwc-typo-caption">
					<div class="bwc-flight-segment__details-border"></div>
					<div>
						<bwc-flight-segment-details>
							<div class="bwc-flight-segment__description">
								<div class="bwc-flight-segment__travel-container">
									<div>
										<translate-flight-details-terminal-gate>
											<translate-flight-details-terminal-gate>
												<bw-trip-segment-details flightstatus="departure">
													<div class="ng-star-inserted"><span>Terminal 1</span><span class="ng-star-inserted"> - </span><span class="ng-star-inserted">Gate B02</span></div>
												</bw-trip-segment-details>
											</translate-flight-details-terminal-gate>
										</translate-flight-details-terminal-gate>
									</div>
									<div class="bwc-flight-segment__operatedBy">
										<translate-provide-by>
											<translate-provide-by>Operated by</translate-provide-by>
										</translate-provide-by>
										<span> KLM, <span data-test="bwc-flight-segment__flight-information">KL1432</span></span><span></span>
									</div>
									<div class="bwc-typo-caption bwc-flight-segment__details-item">
										<translate-aircraft></translate-aircraft>
										<span> Embraer 190</span>
									</div>
									<div class="bwc-flight-segment__travel ng-star-inserted">
										<span data-test="bwc-flight-segment-details__travel-time">
											<translate-travel-time>
												<translate-travel-time class="ng-star-inserted">Duration:</translate-travel-time>
											</translate-travel-time>
											1h35 <span class="ng-star-inserted">, <span data-test="bwc-flight-segment-details__cabin-class">Economy Class</span></span>
										</span>
									</div>
									<segment-detail></segment-detail>
								</div>
							</div>
						</bwc-flight-segment-details>
					</div>
				</div>
			</div>
			<div class="bwc-flight-segment__node bwc-flight-segment__node--end bwc-typo-bold">
				<span class="bwc-flight-segment__node-dot"></span>
				<div class="bwc-flight-segment__time"><span class="bwc-flight-segment__time-span">15:40 </span></div>
				<div>
					<span class="bwc-flight-segment__airport"> Amsterdam, Amsterdam Schiphol Airport (AMS) </span>
					<div class="bwc-flight-segment__airport bwc-typo-caption">
						<translate-flight-details-arrival-terminal-gate>
							<translate-flight-details-arrival-terminal-gate>
								<bw-trip-segment-details flightstatus="arrival">
									<div class="ng-star-inserted"><span>Terminal 2</span><span class="ng-star-inserted"> - </span><span class="ng-star-inserted">Gate B36</span></div>
								</bw-trip-segment-details>
							</translate-flight-details-arrival-terminal-gate>
						</translate-flight-details-arrival-terminal-gate>
					</div>
				</div>
			</div>
		</div>
	</bwc-flight-segment>
	<bwc-flight-segment class="bw-trip-segment bw-trip-segment__travel-container ng-star-inserted">
		<div class="bwc-flight-segment bwc-typo-body-m-regular">
			<div class="bwc-flight-segment__origin-container">
				<div class="bwc-flight-segment__node bwc-flight-segment__node--start bwc-typo-bold">
					<span class="bwc-flight-segment__node-dot"></span>
					<div class="bwc-flight-segment__time"><span class="bwc-flight-segment__time-span">20:15 </span></div>
					<span class="bwc-flight-segment__airport"> Amsterdam, Amsterdam Schiphol Airport (AMS)</span>
				</div>
				<div class="bwc-flight-segment__flight-status ng-star-inserted">
					<bwc-flight-status data-hg="MdLC8">
						<div class="bwc-flight-status bwc-flight-status__success ng-star-inserted"><span class="bwc-typo-body-m-regular"><span>Arrived</span></span></div>
					</bwc-flight-status>
				</div>
				<div class="bwc-flight-segment__details bwc-typo-caption">
					<div class="bwc-flight-segment__details-border"></div>
					<div>
						<bwc-flight-segment-details>
							<div class="bwc-flight-segment__description">
								<div class="bwc-flight-segment__travel-container">
									<div>
										<translate-flight-details-terminal-gate>
											<translate-flight-details-terminal-gate>
												<bw-trip-segment-details flightstatus="departure">
													<div class="ng-star-inserted"><span>Terminal F</span><span class="ng-star-inserted"> - </span><span class="ng-star-inserted">Gate F08</span></div>
												</bw-trip-segment-details>
											</translate-flight-details-terminal-gate>
										</translate-flight-details-terminal-gate>
									</div>
									<div class="bwc-flight-segment__operatedBy">
										<translate-provide-by>
											<translate-provide-by>Operated by</translate-provide-by>
										</translate-provide-by>
										<span> KLM, <span data-test="bwc-flight-segment__flight-information">KL807</span></span><span></span>
									</div>
									<div class="bwc-typo-caption bwc-flight-segment__details-item">
										<translate-aircraft></translate-aircraft>
										<span> Boeing 787-9</span>
									</div>
									<div class="bwc-flight-segment__travel ng-star-inserted">
										<span data-test="bwc-flight-segment-details__travel-time">
											<translate-travel-time>
												<translate-travel-time class="ng-star-inserted">Duration:</translate-travel-time>
											</translate-travel-time>
											12h45 <span class="ng-star-inserted">, <span data-test="bwc-flight-segment-details__cabin-class">Economy Class</span></span>
										</span>
									</div>
									<segment-detail></segment-detail>
								</div>
							</div>
						</bwc-flight-segment-details>
					</div>
				</div>
			</div>
			<div class="bwc-flight-segment__node bwc-flight-segment__node--end bwc-typo-bold">
				<span class="bwc-flight-segment__node-dot"></span>
				<div class="bwc-flight-segment__time"><span class="bwc-flight-segment__time-span">15:00 <span class="bwc-typo-caption bwc-flight-segment__arrival-day-diff ng-star-inserted">+1</span></span></div>
				<div>
					<span class="bwc-flight-segment__airport"> Taipei, Taoyuan International Airport (TPE) </span>
					<div class="bwc-flight-segment__airport bwc-typo-caption">
						<translate-flight-details-arrival-terminal-gate>
							<translate-flight-details-arrival-terminal-gate>
								<bw-trip-segment-details flightstatus="arrival"></bw-trip-segment-details>
							</translate-flight-details-arrival-terminal-gate>
						</translate-flight-details-arrival-terminal-gate>
					</div>
				</div>
			</div>
		</div>
	</bwc-flight-segment>
</bw-trip-segment>

<p>This is my return trip:</p>

<bw-trip-segment class="ng-star-inserted">
	<bwc-flight-segment class="bw-trip-segment bw-trip-segment__travel-container ng-star-inserted">
		<div class="bwc-flight-segment bwc-typo-body-m-regular">
			<div class="bwc-flight-segment__origin-container">
				<div class="bwc-flight-segment__node bwc-flight-segment__node--start bwc-typo-bold">
					<span class="bwc-flight-segment__node-dot"></span>
					<div class="bwc-flight-segment__time">
						<span class="bwc-flight-segment__time-span">
						18:40 
						</span>
					</div>
					<span class="bwc-flight-segment__airport">
					Taipei, Taoyuan International Airport (TPE)
					</span>
				</div>
				<div class="bwc-flight-segment__flight-status ng-star-inserted">
					<bwc-flight-status data-hg="MdLC8">
					</bwc-flight-status>
				</div>
				<div class="bwc-flight-segment__details bwc-typo-caption">
					<div class="bwc-flight-segment__details-border"></div>
					<div>
						<bwc-flight-segment-details>
							<div class="bwc-flight-segment__description">
								<div class="bwc-flight-segment__travel-container">
									<div>
										<translate-flight-details-terminal-gate>
											<translate-flight-details-terminal-gate>
												<bw-trip-segment-details flightstatus="departure">
												</bw-trip-segment-details>
											</translate-flight-details-terminal-gate>
										</translate-flight-details-terminal-gate>
									</div>
									<div class="bwc-flight-segment__operatedBy">
										<translate-provide-by>
											<translate-provide-by>Operated by</translate-provide-by>
										</translate-provide-by>
										<span>
										Cathay Pacific, <span data-test="bwc-flight-segment__flight-information">CX473</span>
										</span>
										<span>
										</span>
									</div>
									<div class="bwc-typo-caption bwc-flight-segment__details-item">
										<translate-aircraft></translate-aircraft>
										<span> Airbus A330-300</span>
									</div>
									<div class="bwc-flight-segment__travel ng-star-inserted">
										<span data-test="bwc-flight-segment-details__travel-time">
											<translate-travel-time>
												<translate-travel-time class="ng-star-inserted">Duration:</translate-travel-time>
											</translate-travel-time>
											2h00 <span class="ng-star-inserted">, <span data-test="bwc-flight-segment-details__cabin-class">Economy Class</span></span>
										</span>
									</div>
									<segment-detail>
									</segment-detail>
								</div>
							</div>
						</bwc-flight-segment-details>
					</div>
				</div>
			</div>
			<div class="bwc-flight-segment__node bwc-flight-segment__node--end bwc-typo-bold">
				<span class="bwc-flight-segment__node-dot"></span>
				<div class="bwc-flight-segment__time">
					<span class="bwc-flight-segment__time-span">
					20:40 
					</span>
				</div>
				<div>
					<span class="bwc-flight-segment__airport">
					Hong Kong, Hong Kong International Airport (HKG) 
					</span>
					<div class="bwc-flight-segment__airport bwc-typo-caption">
						<translate-flight-details-arrival-terminal-gate>
							<translate-flight-details-arrival-terminal-gate>
								<bw-trip-segment-details flightstatus="arrival">
								</bw-trip-segment-details>
							</translate-flight-details-arrival-terminal-gate>
						</translate-flight-details-arrival-terminal-gate>
					</div>
				</div>
			</div>
		</div>
	</bwc-flight-segment>
	<bwc-flight-segment class="bw-trip-segment bw-trip-segment__travel-container ng-star-inserted">
		<div class="bwc-flight-segment bwc-typo-body-m-regular">
			<div class="bwc-flight-segment__origin-container">
				<div class="bwc-flight-segment__node bwc-flight-segment__node--start bwc-typo-bold">
					<span class="bwc-flight-segment__node-dot"></span>
					<div class="bwc-flight-segment__time">
						<span class="bwc-flight-segment__time-span">
						22:20 
						</span>
					</div>
					<span class="bwc-flight-segment__airport">
					Hong Kong, Hong Kong International Airport (HKG)
					</span>
				</div>
				<div class="bwc-flight-segment__flight-status ng-star-inserted">
					<bwc-flight-status data-hg="MdLC8">
						<div class="bwc-flight-status bwc-flight-status__success ng-star-inserted">
							<span class="bwc-typo-body-m-regular">
							<span>Arrived</span>
							</span>
						</div>
					</bwc-flight-status>
				</div>
				<div class="bwc-flight-segment__details bwc-typo-caption">
					<div class="bwc-flight-segment__details-border"></div>
					<div>
						<bwc-flight-segment-details>
							<div class="bwc-flight-segment__description">
								<div class="bwc-flight-segment__travel-container">
									<div>
										<translate-flight-details-terminal-gate>
											<translate-flight-details-terminal-gate>
												<bw-trip-segment-details flightstatus="departure">
													<div class="ng-star-inserted">
														<span>Terminal 1</span><span class="ng-star-inserted"> - </span><span class="ng-star-inserted">Gate 30</span>
													</div>
												</bw-trip-segment-details>
											</translate-flight-details-terminal-gate>
										</translate-flight-details-terminal-gate>
									</div>
									<div class="bwc-flight-segment__operatedBy">
										<translate-provide-by>
											<translate-provide-by>Operated by</translate-provide-by>
										</translate-provide-by>
										<span>
										Air France, <span data-test="bwc-flight-segment__flight-information">AF185</span>
										</span>
										<span>
										</span>
									</div>
									<div class="bwc-typo-caption bwc-flight-segment__details-item">
										<translate-aircraft></translate-aircraft>
										<span> Airbus A350-900</span>
									</div>
									<div class="bwc-flight-segment__travel ng-star-inserted">
										<span data-test="bwc-flight-segment-details__travel-time">
											<translate-travel-time>
												<translate-travel-time class="ng-star-inserted">Duration:</translate-travel-time>
											</translate-travel-time>
											13h30 
										</span>
									</div>
									<segment-detail>
									</segment-detail>
								</div>
							</div>
						</bwc-flight-segment-details>
					</div>
				</div>
			</div>
			<div class="bwc-flight-segment__node bwc-flight-segment__node--end bwc-typo-bold">
				<span class="bwc-flight-segment__node-dot"></span>
				<div class="bwc-flight-segment__time">
					<span class="bwc-flight-segment__time-span">
					05:50 <span class="bwc-typo-caption bwc-flight-segment__arrival-day-diff ng-star-inserted">+1</span>
					</span>
				</div>
				<div>
					<span class="bwc-flight-segment__airport">
					Paris, aéroport Paris-Charles de Gaulle (CDG) 
					</span>
					<div class="bwc-flight-segment__airport bwc-typo-caption">
						<translate-flight-details-arrival-terminal-gate>
							<translate-flight-details-arrival-terminal-gate>
								<bw-trip-segment-details flightstatus="arrival">
									<div class="ng-star-inserted">
										<span>Terminal 2E</span>
									</div>
								</bw-trip-segment-details>
							</translate-flight-details-arrival-terminal-gate>
						</translate-flight-details-arrival-terminal-gate>
					</div>
				</div>
			</div>
		</div>
	</bwc-flight-segment>
	<bwc-flight-segment class="bw-trip-segment bw-trip-segment__travel-container ng-star-inserted">
		<div class="bwc-flight-segment bwc-typo-body-m-regular">
			<div class="bwc-flight-segment__origin-container bwc-flight-segment__origin-container--has-certificate">
				<div class="bwc-flight-segment__node bwc-flight-segment__node--start bwc-typo-bold">
					<span class="bwc-flight-segment__node-dot"></span>
					<div class="bwc-flight-segment__time">
						<span class="bwc-flight-segment__time-span">
						08:25 <span class="bwc-typo-caption bwc-flight-segment__arrival-day-diff ng-star-inserted">+1</span>
						</span>
					</div>
					<span class="bwc-flight-segment__airport">
					Paris, aéroport Paris-Charles de Gaulle (CDG)
					</span>
				</div>
				<div class="bwc-flight-segment__flight-status ng-star-inserted">
					<bwc-flight-status data-hg="MdLC8">
						<div class="bwc-flight-status bwc-flight-status__success ng-star-inserted">
							<span class="bwc-typo-body-m-regular">
							<span>Arrived</span>
							</span>
						</div>
					</bwc-flight-status>
				</div>
				<div class="bwc-flight-segment__details bwc-typo-caption">
					<div class="bwc-flight-segment__details-border"></div>
					<div>
						<bwc-flight-segment-details>
							<div class="bwc-flight-segment__description">
								<div class="bwc-flight-segment__travel-container">
									<div>
										<translate-flight-details-terminal-gate>
											<translate-flight-details-terminal-gate>
												<bw-trip-segment-details flightstatus="departure">
													<div class="ng-star-inserted">
														<span>Terminal 2F</span><span class="ng-star-inserted"> - </span><span class="ng-star-inserted">Gate F48</span>
													</div>
												</bw-trip-segment-details>
											</translate-flight-details-terminal-gate>
										</translate-flight-details-terminal-gate>
									</div>
									<div class="bwc-flight-segment__operatedBy">
										<translate-provide-by>
											<translate-provide-by>Operated by</translate-provide-by>
										</translate-provide-by>
										<span>
										Air France, <span data-test="bwc-flight-segment__flight-information">AF7362</span>
										</span>
										<span>
										</span>
									</div>
									<div class="bwc-typo-caption bwc-flight-segment__details-item">
										<translate-aircraft></translate-aircraft>
										<span> Airbus A321</span>
									</div>
									<div class="bwc-flight-segment__travel ng-star-inserted">
										<span data-test="bwc-flight-segment-details__travel-time">
											<translate-travel-time>
												<translate-travel-time class="ng-star-inserted">Duration:</translate-travel-time>
											</translate-travel-time>
											1h05 <span class="ng-star-inserted">, <span data-test="bwc-flight-segment-details__cabin-class">Economy Class</span></span>
										</span>
									</div>
									<segment-detail>
									</segment-detail>
								</div>
							</div>
						</bwc-flight-segment-details>
					</div>
				</div>
			</div>
			<div class="bwc-flight-segment__node bwc-flight-segment__node--end bwc-typo-bold">
				<span class="bwc-flight-segment__node-dot"></span>
				<div class="bwc-flight-segment__time">
					<span class="bwc-flight-segment__time-span">
					09:30 <span class="bwc-typo-caption bwc-flight-segment__arrival-day-diff ng-star-inserted">+1</span>
					</span>
				</div>
				<div>
					<span class="bwc-flight-segment__airport">
					Lyon, aéroport Saint Exupéry (LYS) 
					</span>
					<div class="bwc-flight-segment__airport bwc-typo-caption">
						<translate-flight-details-arrival-terminal-gate>
							<translate-flight-details-arrival-terminal-gate>
								<bw-trip-segment-details flightstatus="arrival">
									<div class="ng-star-inserted">
										<span>Terminal 1</span><span class="ng-star-inserted"> - </span><span class="ng-star-inserted">Gate AB1</span>
									</div>
								</bw-trip-segment-details>
							</translate-flight-details-arrival-terminal-gate>
						</translate-flight-details-arrival-terminal-gate>
					</div>
				</div>
			</div>
		</div>
	</bwc-flight-segment>
</bw-trip-segment>

<p>The stage is set, all that remains to do is <del>survive</del> enjoy the ride.
After all, it’s not like my first trip abroad as an adult is to the other side of the planet, or that the last time I flew on an airplane was back when I was a kid, or that I’m heading out there all alone.
No, that’s a <em>brilliant</em> idea.</p>

<h2 id="act-2-a-heretic-walking-on-holy-ground">Act 2: a heretic walking on holy ground</h2>

<p>Triple-checking that I didn’t put my trusty customized N°09 carbon steel Opinel knife in my pocket or cabin luggage, because apparently you can’t really <em>Crocodile Dundee</em> on a commercial airplane anymore, I get all the way to Taipei, unsure of what I’m about to walk into.</p>

<div class="box box-information">
  <p>This isn’t your typical conference report.
I won’t cite here papers or people in particular because I am not a journalist.
Instead, I’ll give a <em>meta-report</em>, my general vibe about it.</p>
</div>

<p>I’ll set the keynote talks aside, as they were done by engaging and experienced people.
They were mostly about giving context regarding a field or a topic, whether for the conference or the workshop.
I’ve learned a lot of useful information from them, they were really enlightening and interesting.</p>

<h3 id="day-1-pre-conference-workshops">Day 1: pre-conference workshops</h3>

<p>I hung out in the SURE workshop all day.
It had a lot of attendance (especially for a first edition), never dipping below 40 people and at times the room was nearly completely packed with ~80 people.
Overall it was a pleasant experience, quite interesting and insightful, a gentle introduction to the world of academic conferences.</p>

<h4 id="poster-session">Poster session</h4>

<p>The SURE workshop poster presentation session lasted 90 minutes, concurrently with lunch; I first spent 45 minutes having lunch because I’m French, then I spent 45 minutes in front of my poster, talking non-stop to about 15 people in total.</p>

<p>My poster was an <strong>absolute</strong> hit, at least within the context of a workshop as it had only three posters, a deliberate choice by the program committee for a focused session.
The plastic print was absolutely immaculate and looked even better in person than I hoped for.
I’ve had people taking pictures of the poster just because they liked the design that much, before even actually reading it.
Some of them showed said pictures to their doctoral advisor, who in turn promptly <em>demanded</em> that their next poster shall look like that.</p>

<div class="box box-warning">
  <p>The main practical problem about my poster during the poster session was that nobody saw the QR codes at the bottom, unless I pointed them out.
That whole bottom row was poorly designed in the first place…</p>
</div>

<h3 id="day-2-4-main-conference">Day 2-4: main conference</h3>

<p>I’ve mostly stayed in the software security track, mostly because I was too lazy to actually pick-and-choose what I ought to see.</p>

<h4 id="paper-presentations">Paper presentations</h4>

<p>Imagine sitting all day in a classroom and having a marathon of 15-minute talks on a wide variety of topics given by inexperienced speakers.</p>

<p>I don’t want to be harsh, but most of them were grueling to watch.
Stressed-out undergraduates or doctoral students, with thick accents and varying degrees of English fluency, their doctoral advisor most likely in the audience, armed with a set of passable but unremarkable PowerPoint slides…</p>

<p>I have the academic and technical background to understand what was being presented in the software security track, at least at a high level.
I don’t have the energy to immerse myself in eighteen or so different topics of research papers in one day, if my brain is already cooked after trying to decode the first set of six talks.
It takes a lot of effort to do so and once your mental energy is depleted by that task, you just mentally check out for a couple of minutes.</p>

<div class="box box-information">
  <p>I actually fell back into my old habits and started doodling on a notepad between my bursts of note-taking, as if I was back in school.
Months since I last attempted a drawing, <em>years</em> since I last completed one and by the end of the conference I had a dozen of them.</p>

  <p>Somehow, I’ve rekindled a lost hobby of mine.
I didn’t see that coming.
My drawings were also a hit with the attendees, especially at the bar later on.</p>
</div>

<p>It still was worthwhile to attend them, as I’ve learned quite a lot on topics I usually don’t have much experience in.
It is however a challenge to stay focused in these conditions all day for those of us like me, who don’t consume caffeinated beverages.</p>

<h4 id="poster-session-1">Poster session</h4>

<p>The main track poster session was quite unlike the one from the workshop (and not only because it showcased over 40 posters).
By that point, I had watched the <a href="https://www.youtube.com/watch?v=1RwJbhkCA58">Better Posters Generation 1 video</a> and all I will say is that it was spot-on, except that the noise in that cavernous room was also <em>deafening</em>.</p>

<p>The one poster that I found really good and effective was from the people behind <a href="https://wiretap.fail/">WireTap</a> and they also did a demo exhibit… only for me to find out that they were not part of the best poster award competition.
I wondered how my poster would’ve fared in this session, but alas it wasn’t meant to be, for mine was confined in a workshop and not on display here.</p>

<h3 id="day-5-post-conference-workshop">Day 5: post-conference workshop</h3>

<p>I didn’t attend, except to eat at the lunch buffet.
By that point, I had already developed a really nasty cough, probably because my immune system hadn’t encountered a real challenge in a long time, due to me never adventuring further away than 150 kilometers from home in the past decade.
I just chilled, visiting Taipei Civic Plaza and Taipei 101, hanging out with some of the attendees.</p>

<h3 id="b-but-i-really-wanted-a-conference-report">…b-but I really wanted a conference report!</h3>

<p><em>Fine</em>, but I’ll do it the lazy way.</p>

<p>I’ve kept notes throughout the conference, but they are basically undecipherable except by me and the Gemini 2.5 Pro chat conversation that contains all of the history regarding my attendance, dating back to me accepting the invitation.
I therefore asked it to give a summary, which was structured around the three themes you can read below.</p>

<div class="box box-ai">
  <h4 id="the-unrelenting-arms-race-of-automation-fueled-by-aillms">The Unrelenting Arms Race of Automation (Fueled by AI/LLMs)</h4>

  <p>This was arguably the dominant narrative, set by the opening keynote tracing vulnerability research from manual art to autonomous systems driven by competitions like the DARPA Cyber Grand Challenge.
Fuzzing remains a primary weapon, now being enhanced with AI/LLMs, Rust-specific awareness, and kernel parameter integration.
Defensively, AI is being used for tasks like packer detection, and the fight against obfuscation continues with new synthesis techniques.
The field is increasingly characterized by this rapid, automated back-and-forth.</p>

  <h4 id="hardware-as-the-new-foundation">Hardware as the New Foundation</h4>

  <p>A significant trend is the shift “down the stack,” leveraging hardware features as trust anchors for security.
Capabilities, particularly CHERI, were prominent, used for memory safety, sandboxing, and even de-privileging GPU drivers.
Trusted Execution Environments (TEEs) and confidential computing were major focus areas, applied to securing I/O for LLMs, defending against firmware attacks, and creating attestable build pipelines.
Other hardware interactions, from CPU caches and SSDs to ARM’s MTE and DMA, were also explored as both attack surfaces and defense mechanisms.</p>

  <h4 id="the-quest-for-correct-abstractions--bridging-the-human-gap">The Quest for Correct Abstractions &amp; Bridging the Human Gap</h4>

  <p>Echoing the SURE workshop’s mission to close the “software understanding gap”, there was a clear focus on finding better ways to manage complexity and ensure correctness.
This ranged from the highly formal (mechanizing GDPR compliance with temporal logic, creating provably correct parsers) to the practical (improving function signature recovery).
A recurring pain point was the “human gap”—developers writing vulnerable code due to fundamental misunderstandings of underlying platforms (like Windows file systems) or security boundaries (like Kubernetes implicit permissions).
The SURE panel explicitly debated the need for new, higher-level abstractions beyond source code itself.</p>
</div>

<h3 id="what-about-delinking">What about delinking?</h3>

<p>There has been three very distinct responses:</p>

<ul>
  <li>From people unfamiliar with the technique, I looked like <em>Hackerman</em>, performing awe-inspiring black magic heresy while pissing all over CS 101.
In particular, I’ve noticed that academics coming from Asian institutions seemed to be especially impressed by it.</li>
  <li>From people who were familiar with the inner workings of toolchains, the response was less astonishment and more “ooh, neat party trick”.
I guess it really shows that once you’ve mastered the rules, you then know how to break them and get away with it.</li>
  <li>From people who actually co-authored the paper on binary reassembling, the consensus was that this technique was academically speaking obsolete, as focus shifted to decompilation and then recompilation, which promised a more general solution, unshackled by instruction sets or ABI constraints.</li>
</ul>

<div class="box box-information">
  <p>As it turns out, by the time you’ve actually solved split relocations, you’ve already created several pieces of a decompiler.</p>
</div>

<p>That doesn’t mean that this technique is not useful anymore or not worthy of further study; on the contrary, the fact that I had a growing and enthusiastic user-base in the video game decompilation community was especially of interest.</p>

<h2 id="act-3-the-universe-decides-to-toy-with-me">Act 3: the Universe decides to toy with me</h2>

<p>With the conference finished, I figured that the worst was behind me and that I was home-free.</p>

<div class="box box-warning">
  <p>How wrong I was.
The real fun was about to begin.</p>
</div>

<h3 id="for-want-of-a-dollar">For want of a dollar</h3>

<p>Come Saturday morning, how hard can it be to check out of a hotel?</p>

<ul>
  <li>My Visa Premier card was declined when I tried to settle my bill.</li>
  <li>I then tried three different ATMs from three different banks around the hotel, running around in +30°C sunny, humid weather to no avail.</li>
  <li>I then tried to call the <em>Service Premier</em> emergency number on the back of my card, but my French SIM card doesn’t work in Taiwan (as in, it’s not even <em>connecting</em> to the cell network).</li>
  <li>I then ask the hotel reception to call that number, which connects after a couple of tries (missed the +33 prefix, forgot to take out the leading 0 on an international call…).</li>
  <li>The operator informed me that my card had been blocked due to suspicions of fraud activity and the bill was also slightly over my remaining monthly payment limit (all €9000 of it). They needed to confirm my identity… by calling my mobile phone number, which doesn’t work in Taiwan.</li>
  <li>Being unable to confirm my identity, I am then informed that I would need to call my local bank branch so that they can sort this out, but it was 6 AM in France and by the time they would open three hours later, I had to be at the Taoyuan International Airport.</li>
</ul>

<p>Only after explaining to the hotel staff that I was shit out of luck and needed them to suggest a solution, did they proceed to simply use my deposit made during check-in, as it was more than enough to cover my bill.</p>

<div class="box box-information">
  <p>What did I learn from this?</p>
  <ul>
    <li>Don’t just have more cash than you think you will need, have an amount as <em>stupidly</em> large as the Universe can be.</li>
    <li>Don’t just use a card twice in a foreign country on the other side of the planet to settle four digit bills.</li>
    <li>Next time, just ask to use the deposit when the card declines.</li>
  </ul>
</div>

<p>While being rattled by the experience, even stuck with a non-functional card I still had around NT$4000 and €70 in cash when I left the hotel.
It’s not a whole lot, but with a NT$1400 taxi fare to the Taoyuan International Airport as the only foreseeable mandatory expense until my sister grabs me at the Lyon Saint-Exupéry Airport, I figured I was probably fine as long as nothing else went majorly wrong.</p>

<div class="box box-warning">
  <p>I can’t believe I’ve managed to keep it together though this mess.
And here I thought that being aloof through immigration with the Taiwan Arrival Card was bad enough…</p>
</div>

<h3 id="for-want-of-a-transit">For want of a transit</h3>

<p>Remember that short layover at the Hong Kong International airport?</p>

<ul>
  <li>Boarding was supposed to happen at 6:10 PM, it got delayed to 6:25 PM.</li>
  <li>Departure was supposed to happen at 6:40 PM, it got delayed to 7:15 PM.</li>
  <li>Take-off at 7:35 PM.</li>
  <li>Landing at 8:55 PM.</li>
  <li>Plane reached the gate at 9:10 PM (crap, gotta pee).</li>
  <li>Got out of the plane at 9:20 PM.</li>
  <li>Passed passport check at 9:25 PM.</li>
  <li>Passed security check at 9:30 PM (crap, I need to drink all of the water in my half-liter bottle).</li>
  <li>Reached the gate at 9:35 PM.</li>
  <li>Exited the toilets at 9:40 PM (crap, I really gotta stop filling that bottle).</li>
  <li>Boarded the plane at 9:45 PM.</li>
  <li>Boarding complete at 10:00 PM, with the gate originally set to close at 10:10 PM.</li>
</ul>

<div class="box box-warning">
  <p>I had originally 100 minutes to perform this layover.
The delay stripped 45 minutes off of that, leaving me with less than one hour to speedrun it, which is officially not enough to do it.</p>
</div>

<p>Thankfully I didn’t have to find out what would’ve happened should things have gone further awry (more delays, jam and/or issues at the checks…), but that was not a fun experience to do on a full bladder.</p>

<h3 id="for-want-of-a-different-kind-of-transit">For want of a different kind of transit</h3>

<p>Something quite minor, but I’ve made the mistake of eating a yogurt served during breakfast at the end of my HKG-CDG trip.
Apparently in the last 15 years since I last ate one, yogurts are no longer welcome in my stomach.</p>

<p>Now that was queasy but manageable… if I hadn’t filled again my water bottle and had to go through another security check after disembarking, drinking yet another half liter of water.
It ultimately stayed in throughout the travel area, but not without a fight.</p>

<h2 id="how">How?</h2>

<p>I don’t know!</p>

<p>In case you haven’t noticed, I’m chaos incarnate.
I’m the kind of person who <a href="https://mail-index.netbsd.org/port-playstation2/2014/12/04/msg000141.html">ports the NetBSD kernel to the PlayStation 2</a> while hopped up on anesthetics, after having all of my wisdom teeth pulled out, because I was too smashed to do anything else.</p>

<p>This entire sequence of events, from stumbling upon a call for papers seemingly at random all the way to presenting a poster at ACM CCS 2025 in Taiwan, was so ludicrous that it might as well be taken from a Hollywood movie.
This was <strong>madness</strong> and I have no idea how <a href="/reverse-engineering/2023/05/01/introduction.html">attempting to decompile a PlayStation video game</a> could’ve possibly snowballed out of control up to that point…</p>

<p>My only explanation is chaos.
Pure, unadulterated chaos.</p>

<div class="box box-warning">
  <p>There is so much more to this story than just me performing a live rendition of <em>Crocodile Dundee</em> in the world of software engineering.
However, this touches a range of topics that I don’t want to get into on this blog, for a variety of reasons.</p>

  <p>Maybe one day I’ll release the Director’s cut of it, but it’s doubtful that I would ever do it publicly.
I did share some of these details with the attendees and they were absolutely <strong>astounded</strong>.
As in, the kind of incredible story that you can’t made up, even if you tried.</p>
</div>

<h3 id="how-much">How much?</h3>

<p>Well, that trip was entirely self-funded for reasons that I won’t go into and I did keep track of my expenses.
So, what does attending ACM CSS 2025 as a hobbyist cost?</p>

<table>
<thead>
<tr><th>Category</th><th>Description</th><th>EUR</th><th>NT$</th><th>US$</th></tr>
</thead>
<tbody>
<tr><td>Conference</td><td>ACM CCS 2025<br />Full package registration</td><td class="td-expense">-1,552.32</td><td></td><td class="td-informational">-1,810.00</td></tr>
<tr><td>Transport</td><td>Air France<br />Airfare, taxes and options</td><td class="td-expense">-2,920.17</td><td></td><td></td></tr>
<tr><td>Transport</td><td>Air France<br />Airfare insurance</td><td class="td-expense">-150.00</td><td></td><td></td></tr>
<tr><td>Supplies</td><td>Amazon<br />Travel Supplies (bags, adapter, etc.)</td><td class="td-expense">-275.40</td><td></td><td></td></tr>
<tr><td>Supplies</td><td>VistaPrint<br />250 Business Cards<br />Priority shipping</td><td class="td-expense">-54.53</td><td></td><td></td></tr>
<tr><td>Personal</td><td>Clothes</td><td class="td-expense">-2,213.00</td><td></td><td></td></tr>
<tr><td>Personal</td><td>Fnac<br />Sony WH-1000MX6 Headphones</td><td class="td-expense">-419.99</td><td></td><td></td></tr>
<tr><td>Food &amp; Drinks</td><td>Lyon–Saint Exupéry Airport<br />Sandwich</td><td class="td-expense">-6.50</td><td></td><td></td></tr>
<tr><td>Currency</td><td>Bank of Taiwan<br />EUR to NT$ conversion</td><td class="td-expense">-300.00</td><td class="td-income">+10,401</td><td></td></tr>
<tr><td>Currency</td><td>Bank of Taiwan<br />Conversion charge</td><td></td><td class="td-expense">-30.00</td><td></td></tr>
<tr><td>Supplies</td><td>Taiwan Mobile<br />SIM Card (7 days unlimited data)<br />5G Short-term Prepaid Card</td><td></td><td class="td-expense">-800.00</td><td></td></tr>
<tr><td>Transport</td><td>Taxi<br />TPE to Grand Hyatt Taipei</td><td></td><td class="td-expense">-1,500.00</td><td></td></tr>
<tr><td>Accommodation</td><td>Grand Hyatt Taipei<br />6 night reservation deposit<br />(authorization hold)</td><td class="td-expense">-1,695.97</td><td class="td-informational">-65,000.00</td><td></td></tr>
<tr><td>Food &amp; Drinks</td><td>Posino Taipei<br />777 Wednesday drinks<br />For eight</td><td></td><td class="td-expense">-4,160.00</td><td></td></tr>
<tr><td>Entertainment</td><td>Taipei 101<br />Indoor Observatory Deck ticket</td><td></td><td class="td-expense">-600.00</td><td></td></tr>
<tr><td>Food &amp; Drinks</td><td>Milksha<br />Boba</td><td></td><td class="td-expense">-150.00</td><td></td></tr>
<tr><td>Food &amp; Drinks</td><td>Milksha<br />Boba - coupon reduction</td><td></td><td class="td-income">+20.00</td><td></td></tr>
<tr><td>Souvenir</td><td>Taipei 101 Souvenir Shop<br />Set of tea bags</td><td></td><td class="td-expense">-580.00</td><td></td></tr>
<tr><td>Souvenir</td><td>Taipei 101 Souvenir Shop<br />Set of three whiskey glasses</td><td></td><td class="td-expense">-600.00</td><td></td></tr>
<tr><td>Currency</td><td>Grand Hyatt Taipei<br />Advanced cash lending</td><td></td><td class="td-income">+2,000.00</td><td></td></tr>
<tr><td>Transport</td><td>Taxi<br />Grand Hyatt Taipei to TPE</td><td></td><td class="td-expense">-1,400.00</td><td></td></tr>
<tr><td>Currency</td><td>Mega International Commercial Bank<br />NT$ to EUR conversion</td><td class="td-income">+20.00</td><td class="td-expense">-725.00</td><td></td></tr>
<tr><td>Currency</td><td>Mega International Commercial Bank<br />Conversion charge</td><td></td><td class="td-expense">-30.00</td><td></td></tr>
<tr><td>Accommodation</td><td>Grand Hyatt Taipei<br />Remainder from the deposit</td><td class="td-income">+195.26</td><td class="td-informational">+11,908</td><td></td></tr>
</tbody>
<thead>
<tr><th>Total</th><th></th><th class="th-expense">-9,372.62</th><th class="th-income">+1,846</th><th class="th-informational">0.00</th></tr>
</thead>
</table>

<div class="box box-warning">
  <p>Holy shit, that’s just about five digits!
This is one <strong>expensive</strong> mid-life crisis.
It’s ahead of schedule too…</p>
</div>

<p>The remaining NT$1,846 is cold, hard cash.
I’ll keep it as a souvenir, as it is a full set of banknotes and coins, missing only the NT$½ and NT$20 coins as well as the NT$200 and NT$2,000 bills.
Besides, it’s only worth about €50 and it’s somewhat hard to convert them here in France (my local bank branch for example does not deal with NT$ cash).</p>

<h3 id="how-was">How was…</h3>

<div class="box box-warning">
  <p>This isn’t a travel blog.
Therefore, I’ll spare you all of the various ways a Frenchman, with severely atrophied social skills and a decade out of practice with face-to-face conversational English, bumbles around on the other side of the planet.</p>
</div>

<p>But if you really <strong>must</strong> know my opinions:</p>

<dl>
  <dt>Lyon Saint-Exupéry Airport</dt>
  <dd>Is big, but not <span style="font-style: italic;">big</span> big.</dd>
  <dt>Embraer 190</dt>
  <dd>Like a 1980s B-segment car: small, very loud, bare-bones and is probably more fun as a pilot than as a passenger.</dd>
  <dt>Netherlands</dt>
  <dd>Very green, very flat, very overcast and in the middle of a ground war against water.</dd>
  <dt>Amsterdam Airport Schiphol</dt>
  <dd>Is a <span style="font-weight: bold;">big</span> one; the airplane spent so long just taxiing to the gate, I think it qualifies as a shuttle service.</dd>
  <dt>Boeing 787-9</dt>
  <dd>Like a 2010s C-segment car: bigger, quieter, touchscreen-enabled and is probably more fun as a passenger than as a pilot.</dd>
  <dt>Taipei</dt>
  <dd>My stereotypes on Asian cities learned through various media weren't all correct, but they weren't all wrong either.</dd>
  <dt>Grand Hyatt Taipei</dt>
  <dd>First time checking in a hotel and it is one step removed from <span style="font-style: italic;">John Wick</span>'s Continental; has a kick-ass concierge that procured me a carry tube for my poster in less than 36 hours.</dd>
  <dt>Taipei Food Market district</dt>
  <dd>So many dumplings...</dd>
  <dt>Taipei International Convention Center</dt>
  <dd>Bigger in the Plenary Hall than on the outside.</dd>
  <dt>Taipei 101</dt>
  <dd>A sight to behold and ear poppings to behead.</dd>
  <dt>Taiwan Taoyuan International Airport</dt>
  <dd>Is an airport with shopping malls on the side, rather than shopping malls with an airport on the side.</dd>
  <dt>Airbus A330-300</dt>
  <dd>Like a 2000s C-segment car with an aftermarket car stereo and revamped interior: a stealthy rollercoaster ride when the pilot decides to book it.</dd>
  <dt>Hong Kong</dt>
  <dd>Wouldn't know, it was night time.</dd>
  <dt>Hong Kong International Airport</dt>
  <dd>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</dd>
  <dt>Airbus A350-900</dt>
  <dd>The best ride and the best economy class a modern European widebody airplane can offer, and not just because at least one of these things is French.</dd>
  <dt>Paris</dt>
  <dd><span style="font-weight: bold;">Fucking</span> <span style="font-style: italic;">Paris</span>, third time I'm transiting through there in my life, that's three times too many for a proud Savoyard like me...</dd>
  <dt>Paris Charles de Gaulle Airport</dt>
  <dd>Is a restaurant with gates on the side, as things should be; consequently, sparrows roam the gates without caring for "staff only" signs, as things should be.</dd>
  <dt>Airbus A321</dt>
  <dd>Like a 1990s hotrod: a bit past its prime yet still pushes you back into the seat, rowdy during maneuvers and it looks about as much fun as a passenger than as a pilot.</dd>
</dl>

<h2 id="why">Why?</h2>

<p>I… I think I do know now.</p>

<p>I won’t tell you though, because that would require the Director’s cut of this story and I’ve already decided against publishing it for now.
There is however another piece of the puzzle that I can provide at this point.
Amid all of that chaos, one small thing still stands out.</p>

<p>A couple of weeks before the conference, I’ve <a href="https://news.ycombinator.com/item?id=45313557#45315533">rambled</a> yet again on Hacker News, on a thread about writing science papers.
This time, while the comment itself got relatively little traction, I received an email.
Not a technical inquiry about my black magic, but one regarding how I was invited to the ACM CCS conference.
Here’s a little snippet from it:</p>

<blockquote>
  <p>This win of yours is frankly, inspirational, and it motivates me to put my work out there, combatting my imposter syndrome.</p>
</blockquote>

<p>A <em>fan</em> email.
Oh dear Satan, I’m a role model for someone.</p>

<p>Back then, this was a <strong>horrifying</strong> thought for me.
Now, after having completed this <em>extremely</em> out-of-character excursion for me… maybe I can come to terms with things like that.
As for the rest, perhaps in time I’ll manage to work through the rest of my personal issues.</p>

<h2 id="now-what">Now what?</h2>

<p>Well, I attended the one of the biggest, top-tier academic conferences out there and all I got was a nice thermos flask and a virtual certificate of participation:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/assets/2025/10/25/AttendanceBadgeGenerator_acmcc_202510_20259590_template_1r4t13_4k.jpg" />
                <figcaption>Figure 4: Certificate of attendance.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>I did also come back with the poster and something I never, <em>ever</em> thought I would ever have:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/assets/2025/10/25/acm-ccs-2025-poster-delinking-stripping-programs-for-parts-citation-screenshot.png" />
                <figcaption>Figure 5: My poster as a citable work.</figcaption>
            </figure>
        </div>
    </label>
</div>

<div class="box box-warning">
  <p>Sure, it’s <em>technically</em> out-of-proceedings regarding the main ACM CCS conference and it’s just a poster, but it still counts I think.</p>
</div>

<p>More seriously, that was an experience that I’m glad to have had once.
I’ve learned a lot of things, some of them unsuitable for publication on this blog, as well as discussed with a bunch of people I would’ve never met otherwise.
I don’t know if I’ll do it again (and not just because of the staggering cost of self-funding this), but the attendees did recommend me to check out an industrial or community conference next time instead.</p>

<p>Now if you excuse me, I’ll need some time to recover from this crazy story…
and not only because I’m just done coughing my lungs out.
This has been an <em>utterly</em> exhausting endeavor over the past three months; beyond the trip itself, I’ve juggled writing a paper, designing a poster, providing support to the users of ghidra-delinker-extension and a couple of other things alongside a full-time job.</p>

<p>Simply put, I’m <em>spent</em>.</p>]]></content><author><name>Jean-Baptiste Boric</name></author><summary type="html"><![CDATA[What?]]></summary></entry><entry><title type="html">ghidra-delinker-extension is still snowballing out of control, one year later</title><link href="https://boricj.net/2025/10/11/ghidra-delinker-extension-still-snowballing-out-of-control.html" rel="alternate" type="text/html" title="ghidra-delinker-extension is still snowballing out of control, one year later" /><published>2025-10-11T02:00:00+02:00</published><updated>2025-10-11T02:00:00+02:00</updated><id>https://boricj.net/2025/10/11/ghidra-delinker-extension-still-snowballing-out-of-control</id><content type="html" xml:base="https://boricj.net/2025/10/11/ghidra-delinker-extension-still-snowballing-out-of-control.html"><![CDATA[<p>It’s been a while.</p>

<p>Life has been harrowing lately, but you’re not here for that.
Besides, not enough was happening in delinking land to warrant a dedicated post, so I figured I might roll everything up into one update.</p>

<h2 id="tenchu-stealth-assassins">Tenchu: Stealth Assassins</h2>

<p>My decompilation/reverse-engineering project is still on hiatus, although something did happen.</p>

<p>Back in August 2024, I received an email inquiring about level geometry and freecam.
Fast forward a couple of weeks and Sir Rando released a <a href="https://www.youtube.com/watch?v=O0wUf5pTqA8">YouTube video</a> with a challenge run for completing the game as Rikimaru without using his sword.
You can watch the video here:</p>

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/O0wUf5pTqA8?si=qLW7srCeBIMEzLW8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>The video features in-game B-roll footage with a freecam.
I’ve retrofitted this feature into the game with the power of delinking, by replacing the function in charge of the camera with a modified one that gives manual control of it, and keeping the rest of the game’s original code.</p>

<div class="box box-warning">
  <p>Unfortunately, since I stopped at fully delinking <em>Rittai Ninja Katsugeki Tenchu</em>, the two extra levels from the later releases couldn’t benefit from this freecam mod, but the end result still looks nice.</p>
</div>

<h2 id="ghidra-delinker-extension">ghidra-delinker-extension</h2>

<p>My extension is still snowballing out of control, steadily.</p>

<p>I’ve addressed some tech-debt and released <a href="https://github.com/boricj/ghidra-delinker-extension/releases/tag/v0.7.0">version 0.7.0</a> back in July 2025.
The biggest achievement was refactoring the relocation table from tracking symbol names to tracking target addresses.
This solved a whole bunch of problems around symbol name conflicts and symbol name priorization, a feature that picks mangled symbols if available rather than the primary one.</p>

<p>Alongside major improvements in logging and error handling, the extension no longer requires divination (or debugging) to troubleshoot issues with it, meaning that the everyday reverse-engineer has a fighting chance of using it successfully.</p>

<div class="box box-information">
  <p>The extension seems to have carved a niche for itself in video game decompilation for Windows games.
I’ve mentored a fair number of people who reached out to me about using it, with a wide variety of use-cases.</p>
</div>

<p>I have second-hand hearsay that my extension is spreading by word of mouth and is being used by people that haven’t reached out to me, but except for <a href="https://tooomm.github.io/github-release-stats/?username=boricj&amp;repository=ghidra-delinker-extension">download stats</a> (245 downloads for 0.7.0 at the time of writing), I don’t really have any hard data about it.</p>

<h2 id="yet-another-sober-outburst-on-hacker-news">Yet another sober outburst on Hacker News</h2>

<p>I know, I know, I really should stop being baited into writing those…
Alas, I’ve managed to nail <a href="https://news.ycombinator.com/item?id=44083467#44083863">the top comment</a> of a story that stayed at the top of the feed for a while about, fittingly enough, reinventing the wheel.</p>

<p>Two comments from this thread are notable, the first one being:</p>

<blockquote>
  <p>In my almost 15 years in this community this is the first comment where I don’t have a fucking clue of what is described.</p>

  <p><a href="https://news.ycombinator.com/item?id=44083467#44084244">elorant</a></p>
</blockquote>

<p>Best Hacker News comment of all times as far as I’m concerned, right after this one:</p>

<blockquote>
  <p>Pretty awesome work!</p>

  <p><a href="https://news.ycombinator.com/item?id=44083467#44085674">WalterBright</a></p>
</blockquote>

<p>That’s from Walter Bright.
<strong>The</strong> Walter Bright.</p>

<p>I guess I know now how it feels when a living legend gives you a thumbs up.</p>

<h2 id="thats-all">That’s all?</h2>

<p>Oh no, I did not unearth this blog and titled this post like so just for that.
Remember when I said that ghidra-delinker-extension was snowballing out of control last year?</p>

<p>I had no idea.</p>

<p>You’ll read all about it in my next post… as soon as I get back home from abroad.</p>]]></content><author><name>Jean-Baptiste Boric</name></author><summary type="html"><![CDATA[It’s been a while.]]></summary></entry><entry><title type="html">ghidra-delinker-extension is snowballing out of control</title><link href="https://boricj.net/2024/09/23/ghidra-delinker-extension-snowballing-out-of-control.html" rel="alternate" type="text/html" title="ghidra-delinker-extension is snowballing out of control" /><published>2024-09-23T02:00:00+02:00</published><updated>2024-09-23T02:00:00+02:00</updated><id>https://boricj.net/2024/09/23/ghidra-delinker-extension-snowballing-out-of-control</id><content type="html" xml:base="https://boricj.net/2024/09/23/ghidra-delinker-extension-snowballing-out-of-control.html"><![CDATA[<p>As the lack of updates might suggest, I’ve taken a break from my <em>Tenchu: Stealth Assassins</em> <a href="/tenchu1/2024/02/05/introduction.html">reverse-engineering/decompilation project</a>.
There’s a bunch of reasons for that and some of them are possibly relevant for whatever readership I have, so I figured I might as well write about them here.</p>

<h2 id="i-cant-believe-its-not-tenchu-stealth-assassins-news">I can’t believe it’s not Tenchu: Stealth Assassins news</h2>

<p>For context, as part of my reverse-engineering/decompilation efforts I’ve developed <a href="https://github.com/boricj/ghidra-delinker-extension">a Ghidra extension</a> that can export relocatable object files.
I’ve been working on this tooling over the past two and a half years on my own, but lately something unexpected happened: it started gaining users.</p>

<p>This is a summary of what happened with it over the last couple of months.</p>

<h3 id="teaching-an-old-elf-coff-tricks">Teaching an old elf COFF tricks</h3>

<p>Exporting object files is a rather unconventional approach given that assembly files would be a more obvious and natural choice.
The key insight here is that object files are essentially containers for arrays of bytes and the container part is mostly unopinionated about its contents.
That makes them the real <em>lingua franca</em> of traditional toolchains.</p>

<div class="box box-information">
  <p>In contrast, there are a <strong>lot</strong> of assemblers out there, each with their own syntax and quirks.
Just for Windows x86 there’s MASM, FASM, NASM, YASM, GAS…</p>

  <p>Simply put, object file formats are standardized, interoperable and far less numerous.</p>
</div>

<p>Taking this observation into account, I’ve made efforts to architecture my Ghidra extension in a manner that would allow supporting multiple object file formats and instruction sets, even back when all it supported was generating ELF object files for 32-bit little endian MIPS.</p>

<p>Eventually, I’ve added i386 support in order to carry out <a href="/atari-jaguar-sdk/2023/11/27/introduction.html">a case study</a>, but the object file format consideration stayed theoretical until someone <a href="https://github.com/boricj/ghidra-delinker-extension/pull/5">contributed</a> a COFF object file exporter.
It was <em>very</em> rough around the edges initially, but after spending some weeks <a href="https://github.com/boricj/ghidra-delinker-extension/issues/6">fixing all the problems</a> reported by another highly motivated user, it’s fully usable now.</p>

<h3 id="what-do-you-mean-youre-not-relinking-it">What do you mean, you’re not relinking it?</h3>

<p>Soon after, someone else stumbled upon my Ghidra extension with <a href="https://github.com/boricj/ghidra-delinker-extension/issues/8">an unusual use-case</a>: as part of a video game decompilation project, they wanted to use my extension to delink a program into object files.
So far so good, but these object files would not be reused into a program or a library like I usually do.</p>

<p>Instead, these delinked object files would be used as a reference to compare against the output of their decompilation efforts using <a href="https://github.com/encounter/objdiff">objdiff</a>, a fancy object file comparator.
I’ll admit I was at first a bit skeptical about this, because while my tooling can produce object files that are interchangeable with the original ones, I haven’t designed it to produce <em>equivalent</em> ones.</p>

<p>Fortunately, objdiff has an option for relaxed relocation diffs, where different but equivalent relocations are considered equal.
The only actual issue was one of symbol visibility for labels and switch tables, which was accommodated with some new heuristics and options inside the exporters.
I didn’t even have to implement section-relative relocations, a feature that I keep postponing.</p>

<div class="box box-information">
  <p>This incidentally brought me to the <a href="https://decomp.me/">decomp.me</a> Discord server, where I’ve discovered that the decompilation community has a term for this kind of tooling: binary splitters, of which <a href="https://github.com/ethteck/splat">splat</a> appears to the most well known.
However, my Ghidra extension is quite different than what the community produced, where the focus is more on creating ready-to-use decompilation frameworks.</p>
</div>

<h3 id="a-new-ghidra-setup-github-action">A new Ghidra setup GitHub action</h3>

<p>As an aside, I’ve also received a pull request for <a href="https://github.com/boricj/ghidra-delinker-extension/pull/9">an up-to-date Ghidra setup GitHub action</a>.
I’ve done the responsible thing, that is quickly scrolling through the GitHub action repository (which is written in a programming language I’m not familiar with) until I said <em>fuck it, I’ll merge it</em>.</p>

<p>After this decision, I started worrying about software supply-chain security for my Ghidra extension.
It’s a niche tool with unusual applications, but it’s the kind of stuff that some people with… <em>interesting</em> life stories might use, which in turn might attract attention from other people with… <em>different</em> life stories.</p>

<div class="box box-warning">
  <p>All I’m saying is that while I do not plan on willingly adding undocumented features like a backdoor to my Ghidra extension (not that it has any documentation anyways), that doesn’t mean that Ghidra extensions aren’t a possible threat vector.</p>

  <p>It’s very unlikely for my tooling to generate this kind of interest at this point in time, but the <a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor">XZ Utils backdoor incident</a> has shown that no overworked open-source maintainer is immune to malicious actors.</p>
</div>

<h3 id="big-endian-and-position-independent-code-adventures">Big-endian and position-independent code adventures</h3>

<p>Yet another person contacted me about using my tooling to delink artifacts that run on IRIX, an Unix operating system by Silicon Graphics, Inc. (or SGI).
It seemed straightforward enough, as the ELF and MIPS support was by far the oldest and most mature part of my tooling.</p>

<p>How wrong I was.</p>

<p>The first unanticipated issue was that IRIX ran on <em>big-endian</em> MIPS systems, whereas my tooling was only ever used on little-endian systems up to that point.
I had made some nominal accommodations for endianness, but I still ended up <a href="https://github.com/boricj/ghidra-delinker-extension/commit/d0c34ac171c736a67fb75c968bf0e951ce01390e">refactoring around a quarter of the entire extension in one massive commit</a> anyway.</p>

<div class="box box-information">
  <p>At least now it should be <em>theoretically</em> possible to delink code for the Nintendo 64 (which is a big-endian system) with my tooling.
I’m not going to try though, because I’m <em>far</em> too deep into multiple rabbit holes already.</p>
</div>

<p>The second unanticipated issue was position-independent code, which has distinct ABI requirements and specific relocation types (like <code class="language-plaintext highlighter-rouge">R_MIPS_GOT16</code>) compared to the kind of MIPS code I usually delink.
Fortunately, retrofitting this into the MIPS analyzers doesn’t appear to require large modifications, as it is fairly similar to the <code class="language-plaintext highlighter-rouge">R_MIPS_GPREL16</code> scheme which is already supported.</p>

<h3 id="24-hours-of-fame-on-hacker-news">24 hours of fame on Hacker News</h3>

<p>Somewhere in the middle of all of this, I figured it was time to try my luck on Hacker News again and <a href="https://news.ycombinator.com/item?id=41318133">this time it took the bait</a>.
It stayed for a day on the front page and was even the top item for two hours, as can be seen from <a href="https://news.social-protocols.org/stats?id=41318133">these statistics</a>.</p>

<p>In parallel, the <a href="https://github.com/boricj/ghidra-delinker-extension/stargazers">GitHub star count</a> ballooned from around 75 stars to over 350 (making it the fourth most starred GitHub repository in the <code class="language-plaintext highlighter-rouge">ghidra-extension</code> topic) and the latest version of my Ghidra extension <a href="https://tooomm.github.io/github-release-stats/?username=boricj&amp;repository=ghidra-delinker-extension">was downloaded</a> 90 times, but for all I know it could’ve been caused by someone integrating my extension as part of a CICD pipeline.
My GitHub account itself was even trending for a bit… near the bottom of the top 25 list in the Java category.</p>

<div class="box box-warning">
  <p>All that ruckus probably doesn’t amount to much.
While I’ve put a lot of effort into polishing it, I refuse to believe that my Ghidra extension is so user-friendly that all these people managed to master a very esoteric reverse-engineering technique on their first try without anyone asking for help.</p>
</div>

<h2 id="so-whats-the-problem">So what’s the problem?</h2>

<p>On the surface, all of this sounds good: I’ve created a tool that solves a problem so well that it’s attracting users.
While it is unconventional even by the standards of its niche (remember, <em>the French copies nobody and nobody copies the French</em>), somehow a couple of people managed to understand my ramblings and leveraged said tool successfully for their own use-cases.</p>

<h3 id="a-fractal-of-edge-cases">A fractal of edge-cases</h3>

<p>Under the surface, what was supposed to be a side-quest snowballed into a project that is a <strong>massive</strong> software engineering challenge.
I’ve been meaning to write in detail about it for quite some time, but I keep trashing drafts because I sound like a rambling, stark raving lunatic in them.</p>

<p>Instead, I’ll just state that at this rate it’s simply not sustainable.
Mucking around in the internals of ghidra-delinker-extension requires an in-depth technical knowledge about multiple ISAs, object file formats, platforms and toolchains.
Making this work flawlessly requires a staggering level of exactness and failure to uphold it will invoke some <em>very</em> exotic undefined behavior.</p>

<div class="box box-warning">
  <p>Currently, my extension supports ELF/MIPS, ELF/x86 and COFF/x86.
My grasp on x86, COFF and the MSVC toolchain is tenuous at best and yet it’s most of my known userbase right there.</p>

  <p>There are <em>hundreds</em> of valid object file formats and ISAs combinations out there.
If people start contributing random combinations to fulfill their use-case, I have no hope of keeping up with that.</p>
</div>

<p>Furthermore, despite all my efforts spent on code quality and regression testing, I’m having a hard time keeping the extension from imploding under its own weight.
The many, <em>many</em> refactorings I’ve carried out in order to extend the data model and algorithms to fit the ever-increasing scope don’t lend to a sense of stability when the slightest of oversights will break the magic in downright malevolent ways.</p>

<h3 id="whats-that-over-the-horizon">What’s that over the horizon?</h3>

<p>Even if one magically handwaves all of these problems away, there are many more hidden behind them.
It’s one thing to delink a program, it’s another to reuse the pieces successfully.</p>

<p>The ability to debug the delinked code is an ongoing struggle because my extension does not generate debugging symbols.
That would be another dimension for the support matrix on top of ISAs and object file formats, especially if call frame information is involved in order to enable stepping through it.
It’s one rabbit hole that I keep restraining myself from jumping into, because that one looks especially cavernous.</p>

<p>My extension also allows delinking to a platform that is different from the original program, in spite of ABIs or common sense.
I’ve successfully managed to <em>slightly</em> bend closely-related ABIs together in my experiments, but to really take advantage of this would require some sort of tooling, one that allows mixing and matching ABIs, possibly even ISAs with impunity.</p>

<div class="box box-information">
  <p>Even if you don’t want to create unholy chimeras, it would be necessary to deal with link-time optimizations in a scalable manner.
In that case, toolchains can basically choose to disregard ABIs in the name of size or performance, as long as they can get away with it.</p>

  <p>I expect that for the purpose of delinking, this would translate mostly into functions with made-up, nonstandard calling conventions.
That sort of tooling would paper over these digressions so that the API boundary presented by the delinked object file respects the ABI standard of its platform.</p>
</div>

<p>This one I don’t even know how to begin to solve.</p>

<h2 id="so-whats-the-solution">So what’s the solution?</h2>

<p>I don’t know.</p>

<p>All I’ve wanted was a way to divide and conquer my decompilation project where I rejected the perfect matching approach used by many others.
Instead, I’ve stumbled upon what appears to be an entire unexplored field of computer sciences straight out of some post-apocalyptic cyberpunk universe.</p>

<p>Pages and pages of binary code, ripped out of the carcass of existing programs and stitched together to make new ones, like Frankenstein’s monsters made out of bits instead of gibs.
Functions and data backflowing through the one-way toolchain like a big ball of wibbly wobbly, bitsy wimey stuff.
Source code and object code blurred past the point where the implications for the GNU General Public License might give lawyers at the Free Software Foundations cirrhosis.
<em>Mad Max</em>, but with people scavenging modules out of compiled programs instead of salvaging mechanical parts from cars.</p>

<p>It’s preposterous.
Blasphemous, even.
Downright heretical.
Sorry about that.</p>

<p>What I do know is that I don’t have it in me to do a PhD, let alone several of them, which is what it would take to explore this.
Heck, I can barely handle ghidra-delinker-extension by myself in its current form and at times developing it really was like having a strange mood from Dwarf Fortress.
This abyss I’ve gazed into is far deeper than I’ve bargained for and it’s giving me vertigo at the moment.</p>

<div class="box box-warning">
  <p>I’m probably going to take a break from developing ghidra-extension-delinker for a couple of weeks or a couple of months.
After two and a half years working on giving computer sciences the finger, it’s time for a much-needed break.</p>
</div>

<p>I’m not planning on abandoning this at all because it’s far too useful even in its current nascent state, but I need to touch grass with things that are easier on one’s sanity.
There’s only so much one feeble human can buck against common wisdom at a time and I’m starting to sound like <a href="https://news.ycombinator.com/item?id=41612578#41613042">an insane person on Hacker News</a>.</p>]]></content><author><name>Jean-Baptiste Boric</name></author><summary type="html"><![CDATA[As the lack of updates might suggest, I’ve taken a break from my Tenchu: Stealth Assassins reverse-engineering/decompilation project. There’s a bunch of reasons for that and some of them are possibly relevant for whatever readership I have, so I figured I might as well write about them here.]]></summary></entry><entry><title type="html">Decompiling Tenchu: Stealth Assassins part 12: when stuffing PlayStation code inside a Linux MIPS process is no longer worth the trouble</title><link href="https://boricj.net/tenchu1/2024/06/19/part-12.html" rel="alternate" type="text/html" title="Decompiling Tenchu: Stealth Assassins part 12: when stuffing PlayStation code inside a Linux MIPS process is no longer worth the trouble" /><published>2024-06-19T02:00:00+02:00</published><updated>2024-06-19T02:00:00+02:00</updated><id>https://boricj.net/tenchu1/2024/06/19/part-12</id><content type="html" xml:base="https://boricj.net/tenchu1/2024/06/19/part-12.html"><![CDATA[<p><a href="/tenchu1/2024/05/31/part-11.html">Previously</a> in this <a href="/tenchu1/2024/02/05/introduction.html">series of articles</a>, we’ve perfected the art of modifying the code of <em>Tenchu: Stealth Assassins</em> on its native platform as if it was made out of Lego™, thanks to the power of delinking.
In this part, I finally went one step too far with my heresy against computer science and I’m forced to backtrack lest I go <em>completely</em> insane.
As a cautionary tale, I’ll document here the dangers of succumbing to the dark side of delinking, as applied to this project.</p>

<h2 id="the-road-to-hell-is-paved-with-good-intentions">The road to hell is paved with good intentions</h2>

<p>As mentioned in the <a href="/tenchu1/2024/02/05/introduction.html">introduction of this series of articles</a>, one of the objectives of this project was to make a working, native Linux MIPS port of <em>Tenchu: Stealth Assassins</em> by taking its PlayStation code and shoving it into a Linux process.
The idea was to make a Linux port on the cheap as a stepping stone to a fully decompiled and portable version of the game.
This “cheap” port concept relied on several assumptions, some of them turned out to be wrong.</p>

<div class="box box-warning">
  <p>I was so preoccupied with whether I could, I didn’t stop to think if I should.
It turns out that there’s a price to pay for violating ABIs willy-nilly and I’ve severely underestimated what it would take to achieve this.</p>

  <p>In other words, I knew just enough to get into a <em>lot</em> of trouble, but not enough to get out of it.</p>
</div>

<h3 id="non-standard-instructions-and-direct-hardware-access">Non-standard instructions and direct hardware access</h3>

<p>While <code class="language-plaintext highlighter-rouge">qemu-mipsel</code> can emulate a MIPS CPU, it doesn’t support the exact CPU model as found on a PlayStation.
That’s not a problem <em>except</em> for the proprietary geometry transformation engine (or GTE), which is implemented as a coprocessor.
Trying to execute one of its opcodes there will lead to an illegal instruction exception instead of the expected result.
Similarly, <code class="language-plaintext highlighter-rouge">qemu-mipsel</code> emulates a user-land process and not a PlayStation, so direct access to hardware registers will not work either.</p>

<p>Luckily, <em>Tenchu: Stealth Assassins</em> is not an optimized game that leverages
PlayStation-specific features directly within its code, instead all of that is mediated through the Psy-Q SDK.
Therefore, replacing that layer with a drop-in compatible replacement completely eliminates these issues, because the game code itself isn’t strongly tied to the PlayStation hardware.
My delinking tooling has already shown this to be possible, even with statically-linked libraries as found in these games.</p>

<p>The fact that the game was written in C on top of the Psy-Q SDK without using any optimization tricks depending on the PlayStation hardware was the one assumption that turned out to be correct.
Even if the game accessed the GTE or the hardware directly I can think of multiple ways to deal with that, but I haven’t investigated them due to a lack of need.</p>

<h3 id="code-hostile-to-delinking">Code hostile to delinking</h3>

<p><a href="/tenchu1/2024/03/11/part-5.html">As discussed before</a>, there are ways of writing code in a manner that resists delinking when compiling for the MIPS architecture.
This mostly revolves around casting integer constants as pointers, which tends to create instruction patterns (<code class="language-plaintext highlighter-rouge">LUI</code>/<code class="language-plaintext highlighter-rouge">ORI</code>) that do not match those used for relocations (<code class="language-plaintext highlighter-rouge">LUI</code>/<code class="language-plaintext highlighter-rouge">ADDIU</code>), meaning we can’t generate a relocation for it.</p>

<div class="box box-warning">
  <p>If you want your MIPS code to be harder to delink, cast raw integer constants as pointers whenever you can.
In particular, do <strong>not</strong> declare an external variable in your source code and then define the symbol with the linker, either though the linker script or with <code class="language-plaintext highlighter-rouge">--defsym SYMBOL=EXPRESSION</code>, like you’re supposed to.</p>
</div>

<p>Normally, these can fixed by binary patching the sequence of instructions into a normalized pattern.
However, if the integer constant had its lower 16 bits set to zeroes then the compiler might just load the upper 16 bits with <code class="language-plaintext highlighter-rouge">LUI</code> and omit the <code class="language-plaintext highlighter-rouge">ORI</code> instruction.
Unless there’s a NOP instruction we can leverage, for example inside an unused branch delay slot, then this can’t be fixed in-place.</p>

<div class="box box-information">
  <p>I’ve sorted out the remaining apparent delinking issues from the last part, so things like in-game music and cutscenes are working now with the relinked executables on the PlayStation.
That being said, not all issues were actually fixed, for references with incorrect instruction patterns to the persistent state and the scratchpad remains.</p>

  <p>The fact that the game’s code can survive a round trip through the delinker and still work on the PlayStation doesn’t mean it will work on Linux.</p>
</div>

<h3 id="position-independent-code">Position-independent code</h3>

<p>The PlayStation executable code as built by the Psy-Q SDK is what we would call now <code class="language-plaintext highlighter-rouge">-fno-pic</code>.
My previous experiments on running that code on Linux relied on building static programs, with position-dependent code and executables, in order to match the ABIs.
As long as I was building console applications, this was a workable solution.</p>

<p>Unfortunately, it is impractical for GUI applications on Linux to be statically linked.
The PsyCross SDK depends on SDL, which itself depends on multiple system libraries, including OpenGL.
Shared libraries on MIPS mandate position-independent code, which causes the following ABI mismatches:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>No PIC</th>
      <th>PIC</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">t9</code></td>
      <td>Caller-saved</td>
      <td>Holds address of callee function</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">gp</code></td>
      <td>Callee-saved</td>
      <td>Caller-saved</td>
    </tr>
  </tbody>
</table>

<p>This can’t be solved with a simple one-way trampoline.
The <code class="language-plaintext highlighter-rouge">gp</code> register <strong>must</strong> be restored when the function call returns, otherwise we might start using the small data pool of a different shared object than ours, with catastrophic results.
Furthermore, if we divert the execution flow through a thunk, we’d also have to preserve the <code class="language-plaintext highlighter-rouge">ra</code> register, otherwise we wouldn’t know where to return to.</p>

<p>Right now I have a kludge of a thunk made up of Makefile magic, shell one-liners and assembly macros.
It saves the <code class="language-plaintext highlighter-rouge">ra</code> and <code class="language-plaintext highlighter-rouge">gp</code> registers on a shadow stack: it works, but it is not ABI compliant and GDB can’t follow the call chain through it when generating a backtrace.
Fixing this would require implementing a proper thunk mechanism, but doing that correctly and covering all of the edge cases would be a fair amount of work.</p>

<h3 id="psycross-is-a-driver-2-runtime">PsyCross is a Driver 2 runtime</h3>

<p>Getting PsyCross to cross-compile for Linux MIPS was a bit of a chore, but actually using it was another story.
There are dozens of Psy-Q SDK functions that are missing and that prevents linking an executable with the game’s entire delinked code.
After stubbing all of this (and outright stealing <code class="language-plaintext highlighter-rouge">libgs</code> from Psy-Q because PsyCross doesn’t provide it <em>at all</em>), what is implemented doesn’t match Tenchu’s expectations: <code class="language-plaintext highlighter-rouge">libcd</code> for example is missing multiple important features that the game relies on in order to load data off the AFS archive.</p>

<div class="box box-information">
  <p>PsyCross appears to have been originally created for a decompilation project of <em>Tomb Raider: Chronicles</em> before being leveraged for REDRIVER2, the decompilation project for <em>Driver 2: Back on the Streets</em>.
It’s a reimplementation of Psy-Q that’s good enough for running a specific game rather than a drop-in compatible replacement for it.</p>
</div>

<p>Even though I can get the game executable to link and even technically run on Linux, I do not have a suitable implementation of Psy-Q for this platform to actually play the game.
Simply put, PsyCross appears to be way off the mark from what the original game’s code expects and bridging the gap as-is would be a lot of work.</p>

<h3 id="terrible-debugging-experience">Terrible debugging experience</h3>

<p>One feature I’ve always postponed for my Ghidra extension is the generation of debugging symbols, mostly because I expect this to be a lot of work even for a partial implementation.
So far I’ve accepted a crappy, instruction-based debugging experience inside GDB, but my thunks manage to worsen it significantly.</p>

<p>On top of that, having to run the Frankenstein program through <code class="language-plaintext highlighter-rouge">qemu-mipsel</code> adds another layer of pain because QEMU tends to hang and the GDB remote becomes unresponsive when my black magic implodes under its own weight.
This PlayStation-code-on-Linux endeavor requires copious amounts of debugging to sort out issues and I am not willing to entertain this level of development abuse.</p>

<h2 id="conclusion">Conclusion</h2>

<p>With the game’s code on Linux currently stuck inside <code class="language-plaintext highlighter-rouge">libcd</code> trying to load data off the AFS archive, it’s executing far enough to demonstrate that an unoptimized PlayStation game can <em>probably</em> be made to run inside a Linux MIPS process, if you abuse it hard enough.
However, the massive number of issues that remains to be solved means that turning this proof-of-concept into a working port or even a useful development environment is currently out of reach.</p>

<p>I’ve already spent two years perfecting the delinking technique as a prelude to this project and right now I’m just not motivated by the idea of going on another Moby-Dick quest to make this work, just to make a point on the Internet.
The objective of crafting a Linux MIPS port out of the pieces of the original game’s code as laid out in the <a href="/tenchu1/2024/02/05/introduction.html">introduction</a> is therefore abandoned for the time being.</p>

<p>The lesson learned here is that I need to wield delinking more responsibly: keeping the delinked code on the PlayStation is far easier than trying to shove it by force inside a Linux MIPS process.
Given how grueling this investigation was, I’ll probably take a break from this project for now.
I still want to reverse-engineer and decompile this game, but not at the cost of the remaining scraps of my sanity.</p>

<div style="display: grid; grid-template-columns: 1fr 1fr; margin-top: 1ch; margin-bottom: 1ch;">
<div style="padding-right: 15px;">


<a class="prev" href="/tenchu1/2024/05/31/part-11.html">&laquo; Decompiling Tenchu: Stealth Assassins part 11: a modding framework powered by the tears of CS101 teachers</a>

</div>
<div style="padding-left: 15px; text-align: right;">

</div>
</div>]]></content><author><name>Jean-Baptiste Boric</name></author><category term="tenchu1" /><summary type="html"><![CDATA[Previously in this series of articles, we’ve perfected the art of modifying the code of Tenchu: Stealth Assassins on its native platform as if it was made out of Lego™, thanks to the power of delinking. In this part, I finally went one step too far with my heresy against computer science and I’m forced to backtrack lest I go completely insane. As a cautionary tale, I’ll document here the dangers of succumbing to the dark side of delinking, as applied to this project.]]></summary></entry><entry><title type="html">Decompiling Tenchu: Stealth Assassins part 11: a modding framework powered by the tears of CS101 teachers</title><link href="https://boricj.net/tenchu1/2024/05/31/part-11.html" rel="alternate" type="text/html" title="Decompiling Tenchu: Stealth Assassins part 11: a modding framework powered by the tears of CS101 teachers" /><published>2024-05-31T02:00:00+02:00</published><updated>2024-05-31T02:00:00+02:00</updated><id>https://boricj.net/tenchu1/2024/05/31/part-11</id><content type="html" xml:base="https://boricj.net/tenchu1/2024/05/31/part-11.html"><![CDATA[<p><a href="/tenchu1/2024/05/15/part-10.html">Previously</a> in this <a href="/tenchu1/2024/02/05/introduction.html">series of articles</a>, I’ve provided a potpourri-style update on this project due to a lack of demonstrable progress.
Since then, <a href="https://github.com/boricj/ghidra-delinker-extension/commit/6df91a0fb18b1495110a17ed05c2412115942021?diff=unified&amp;w=0#diff-829e116ddb65f04f8e0e7b7c74d2337579211e92460def2c4d5ba91329e05f1fR462-R553">the wretched demon of optimizing MIPS assemblers vacuuming up copies of instructions with HI16 relocations inside branch delay slots</a> has been tamed, so we’re back on our irregularly-scheduled program.</p>

<p>In this part, I’ve managed to pull off yet another affront to computer science: a rather extreme and unconventional take on binary patching.
Specifically, we’ll leverage the delinking technique heralded in this blog to chunk a program into giblets, swap out the parts we want and make the linker mend it all back together in one piece.</p>

<div class="box box-warning">
  <p>This blog post mutilates the desiccated remains of a computer program from 1998 in a most gruesome and unnatural way, all in the name of science.
Its contents may be offensive to linker developers, software supply chain specialists or CS101 teachers.</p>

  <p>As such, this is your one and only warning to look away before I start the chainsaw.</p>
</div>

<h2 id="previously-on-decompiling-tenchu-stealth-assassins">Previously, on <em>Decompiling Tenchu: Stealth Assassins</em>…</h2>

<p>To understand where this comes from, a quick summary of the history of this project is needed.</p>

<p>As part of my ongoing project to decompile <em>Tenchu: Stealth Assassins</em>, I’ve managed to build <a href="(/tenchu1/2024/04/15/part-9.html)">a decently annotated Ghidra database</a> of <em>Rittai Ninja Katsugeki Tenchu</em>’s <code class="language-plaintext highlighter-rouge">GAME.EXE</code>, the executable for the original JP release.
It’s already a very useful resource for modders of the game, but by itself is only a repository of knowledge, with no practical applications.</p>

<p>In parallel, over the past two years I’ve developed and fine-tuned a rather peculiar reverse-engineering technique known as delinking.
What started as a bunch of scrappy Jython scripts eventually evolved into a <a href="https://github.com/boricj/ghidra-delinker-extension">Ghidra extension</a> that enables one to export a program selection as a working, relocatable object file in two mouse clicks.</p>

<p>These two things combined mean that I can turn <code class="language-plaintext highlighter-rouge">GAME.EXE</code> into one or more reusable ELF object files.
In turn, I can relink them to create a new executable and still get something that manages to go in-game without crashing… but I needed something a little extra to write an article on this latest breakthrough.</p>

<div class="box box-warning">
  <p>There are still a bunch of problems to fix inside the Ghidra database and probably also in my Ghidra extension that results in various bugs and crashes, if you tickle the rearranged artifact in the wrong spot.</p>

  <p>Nevertheless, I’m still bewildered that this abomination runs <em>at all</em>, let alone that you can stroll around the level, use items and fight enemies as usual.</p>
</div>

<h2 id="frankensteins-monster-computer-program-edition">Frankenstein’s monster, computer program edition</h2>

<p>So, we can delink <code class="language-plaintext highlighter-rouge">GAME.EXE</code> back into object files, put these back together with a linker and it mostly works.
This is neat by itself, but it’s not very useful to craft versions of the game with functions and variables just shuffled around in memory.
However, while sitting in a voice chat on the <a href="https://discord.gg/UpAAyKk">Tenchu Speedrunning Discord server</a>, observing a live stream where bytes were being meticulously patched by hand using <a href="https://www.cheatengine.org/">Cheat Engine</a>, I got an idea…</p>

<p>What happens if we start changing some of the pieces?</p>

<div class="box box-information">
  <p>This is something I’ve done <a href="/reverse-engineering/2023/08/28/part-10.html">previously</a>, but only as a proof-of-concept on a toy program.
This time, I’m doing it on a closed-source executable that’s orders of magnitude bigger.</p>

  <p>In other words, this is <em>for real</em>.</p>
</div>

<h3 id="the-butchers-tools-dripping-wet-with-blood-binary-code">The butcher’s tools, dripping wet with <del>blood</del> binary code</h3>

<p>We can’t just stuff a C source code file at the end of the linker’s invocation alongside the rest of the original object files, as we would run into multiple symbol definition conflicts between the original game’s code and our additional source code.
We could manually leave out the parts from the program that we are replacing during object file exportation, but that’s tedious, error-prone and worst of all <em>boring</em>.</p>

<p>What we need is a way to use the original game’s code as-is, but easily override the parts we want with our own code.
So after tinkering a bit to find a practical workflow, I ended up with this innocent-looking Makefile:</p>

<div class="language-make highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!make
</span>
<span class="k">-include</span><span class="sx"> .env.mak</span>
<span class="k">include</span><span class="sx"> settings.mak</span>

<span class="k">define</span> <span class="nv">makelibs</span>
<span class="nf">$(</span><span class="nb">foreach</span> lib,<span class="p">$(</span>1<span class="p">)</span>,-l<span class="p">$(</span>lib<span class="p">))</span>
<span class="k">endef</span>

<span class="k">define</span> <span class="nv">makedefsyms</span>
<span class="nf">$(</span><span class="nb">foreach</span> sym,<span class="p">$(</span>1<span class="p">)</span>,--defsym<span class="o">=</span><span class="p">$(</span>sym<span class="p">))</span>
<span class="k">endef</span>

<span class="k">define</span> <span class="nv">makesrcobjs</span>
<span class="nf">$(</span><span class="nb">subst</span> <span class="p">$(</span>1<span class="p">)</span>,<span class="p">$(</span>3<span class="p">)</span>,<span class="p">$(</span>subst <span class="p">$(</span>2<span class="p">)</span>,.o,<span class="p">$(</span>wildcard <span class="p">$(</span>1<span class="p">)</span>/<span class="k">*</span><span class="p">$(</span>2<span class="p">))))</span>
<span class="k">endef</span>

<span class="k">define</span> <span class="nv">makeoverridableobjs</span>
<span class="nf">$(</span><span class="nb">filter-out</span> <span class="p">$(</span><span class="nb">sort</span> <span class="p">$(</span>patsubst %,%.o,<span class="p">$(</span>subst <span class="p">$(</span>2<span class="p">)</span>/,<span class="p">$(</span>1<span class="p">)</span>/,<span class="p">$(</span><span class="nb">basename</span> <span class="p">$(</span>wildcard <span class="p">$(</span>2<span class="p">)</span>/<span class="k">*</span><span class="p">)))))</span>,<span class="p">$(</span>wildcard <span class="p">$(</span>1<span class="p">)</span>/<span class="k">*</span><span class="p">))</span>
<span class="k">endef</span>

<span class="nl">all</span><span class="o">:</span> <span class="nf">build/$(EXECUTABLE).exe</span>

<span class="nl">clean</span><span class="o">:</span>
	<span class="nb">rm</span> <span class="nt">-rf</span> build/<span class="k">*</span>.exe build/<span class="k">*</span>.elf build/<span class="k">*</span>.o

<span class="nl">build/%.o</span><span class="o">:</span> <span class="nf">src/%.c</span>
	<span class="p">$(</span>CROSS_TOOLCHAIN<span class="p">)</span>gcc <span class="nt">-fno-pic</span> <span class="nt">-mno-abicalls</span> <span class="nt">-Wa</span>,-mno-pdr <span class="nt">-march</span><span class="o">=</span>r3000 <span class="nt">-mfp32</span> <span class="nt">-I</span> include/ <span class="p">$(</span>CFLAGS<span class="p">)</span> <span class="nt">-c</span> <span class="nv">$&lt;</span> <span class="nt">-o</span> <span class="nv">$@</span>

<span class="nl">build/%.o</span><span class="o">:</span> <span class="nf">src/%.S</span>
	<span class="p">$(</span>CROSS_TOOLCHAIN<span class="p">)</span>as <span class="nt">-march</span><span class="o">=</span>r3000 <span class="nt">-mfp32</span> <span class="nt">-mno-pdr</span> <span class="nt">-I</span> include/ <span class="p">$(</span>AFLAGS<span class="p">)</span> <span class="nv">$&lt;</span> <span class="nt">-o</span> <span class="nv">$@</span>

<span class="nl">build/%.weakened.o</span><span class="o">:</span> <span class="nf">obj/%.o</span>
	<span class="p">$(</span>CROSS_TOOLCHAIN<span class="p">)</span>objcopy <span class="nt">--weaken-symbols</span><span class="o">=</span>weak-symbols.txt <span class="nv">$&lt;</span> <span class="nv">$@</span>

<span class="nl">build/$(EXECUTABLE).elf</span><span class="o">:</span> <span class="nf">$(call makesrcobjs</span><span class="p">,</span><span class="nf">src</span><span class="p">,</span><span class="nf">.c</span><span class="p">,</span><span class="nf">build) $(call makesrcobjs</span><span class="p">,</span><span class="nf">src</span><span class="p">,</span><span class="nf">.S</span><span class="p">,</span><span class="nf">build) $(patsubst obj/%.o</span><span class="p">,</span><span class="nf">build/%.weakened.o</span><span class="p">,</span><span class="nf">$(call makeoverridableobjs</span><span class="p">,</span><span class="nf">obj</span><span class="p">,</span><span class="nf">src))</span>
	<span class="p">$(</span>CROSS_TOOLCHAIN<span class="p">)</span>ld <span class="nt">-nostdlib</span> <span class="nt">-no-pie</span> <span class="nt">-static</span> <span class="nt">-melf32ltsmip</span> <span class="nt">-T</span> <span class="p">$(</span>PSn00bSDK<span class="p">)</span>/libpsn00b/ldscripts/exe.ld <span class="nt">-Ttext</span><span class="o">=</span><span class="p">$(</span>TEXT_START<span class="p">)</span> <span class="p">$(</span>LDFLAGS<span class="p">)</span> <span class="p">$(</span>call makedefsyms,<span class="p">$(</span>DEFSYMS<span class="p">))</span> <span class="nv">$^</span> <span class="p">$(</span>call makelibs,<span class="p">$(</span>LIBS<span class="p">))</span> <span class="nt">-o</span> <span class="nv">$@</span>

<span class="nl">build/$(EXECUTABLE).exe</span><span class="o">:</span> <span class="nf">build/$(EXECUTABLE).elf</span>
	ps1-packer <span class="nv">$&lt;</span> <span class="nt">-o</span> <span class="nv">$@</span>
</code></pre></div></div>

<p>What this Makefile does is create a program from both object files <em>and</em> source code files.
To override the original game’s code with our own, we can either provide a source code file that automatically overrides an object file with the same name, or weaken specific symbols from the delinked object files so that our new code is prioritized by the linker.</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-11/diagram-makefile-flow.svg" />
                <figcaption>Figure 1: Diagram of this particular modding workflow (simplified)</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>The layout is as follows:</p>
<ul>
  <li>The object files delinked from the original game are stored inside <code class="language-plaintext highlighter-rouge">obj/</code> ;</li>
  <li>The declaration headers for the game’s code are placed inside <code class="language-plaintext highlighter-rouge">include/</code> ;</li>
  <li>The source code newly written by us is placed inside <code class="language-plaintext highlighter-rouge">src/</code> ;</li>
  <li>The build artifacts are outputted to <code class="language-plaintext highlighter-rouge">build/</code>.</li>
</ul>

<p>The jigs and fixtures of our modding factory are set, all we need to do now is push code through it.</p>

<h3 id="let-the-chimeras-roam-free">Let the chimeras roam free</h3>

<p>Say we want to modify the debug menu of the game, located within the <code class="language-plaintext highlighter-rouge">DoInfoViewProc()</code> function.
With binary patching, we’d have to patch or detour the function, find a place to put our code and data if they don’t fit inline, mend anything that we have moved around or modified, hoping that we didn’t miss anything that would cause crashes or glitches at run-time…</p>

<p>… or we could just write our code, as if nothing is out of the ordinary:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"adt.h"</span><span class="cp">
#include</span> <span class="cpf">"camera.h"</span><span class="cp">
#include</span> <span class="cpf">"human.h"</span><span class="cp">
#include</span> <span class="cpf">"infoview.h"</span><span class="cp">
#include</span> <span class="cpf">"semng.h"</span><span class="cp">
#include</span> <span class="cpf">"uncategorized.h"</span><span class="cp">
</span>
<span class="cp">#define NULL (0)
</span>
<span class="kt">void</span> <span class="nf">DoOptions</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">TAdtSelect</span> <span class="n">menu</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">{</span> <span class="s">"Yes"</span><span class="p">,</span> <span class="mi">0</span> <span class="p">},</span>
        <span class="p">{</span> <span class="s">"No"</span><span class="p">,</span> <span class="mi">1</span> <span class="p">},</span>
        <span class="p">{</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">2</span> <span class="p">},</span>
    <span class="p">};</span>

    <span class="k">switch</span> <span class="p">(</span><span class="n">AdtSelect</span><span class="p">(</span><span class="s">"Are you cheating son?"</span><span class="p">,</span> <span class="n">menu</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">case</span> <span class="mi">0</span><span class="p">:</span> <span class="n">AdtMessageBox</span><span class="p">(</span><span class="s">"Naughty boy!"</span><span class="p">);</span> <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="mi">1</span><span class="p">:</span> <span class="n">AdtMessageBox</span><span class="p">(</span><span class="s">"Good boy!"</span><span class="p">);</span> <span class="k">break</span><span class="p">;</span>
        <span class="nl">default:</span> <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">DoSelectItem</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">short</span> <span class="n">padTrig</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">SelectedItem</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">direction</span><span class="p">;</span>
    
    <span class="k">if</span> <span class="p">((</span><span class="n">padTrig</span> <span class="o">&amp;</span> <span class="mh">0x2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">((</span><span class="n">padTrig</span> <span class="o">&amp;</span> <span class="mh">0x1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">direction</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">direction</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">do</span> <span class="p">{</span>
        <span class="n">i</span> <span class="o">+=</span> <span class="n">direction</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">&gt;</span> <span class="mh">0x18</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">i</span> <span class="o">=</span> <span class="mh">0x18</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">while</span> <span class="p">(((</span><span class="n">CamState</span><span class="p">.</span><span class="n">Owner</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">item</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">i</span> <span class="o">!=</span> <span class="n">SelectedItem</span><span class="p">));</span>

    <span class="n">SelectedItem</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
    <span class="n">SoundEx</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="mh">0xb</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">DoInfoViewProc</span><span class="p">()</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">short</span> <span class="n">nowPad</span> <span class="o">=</span> <span class="n">GetPad</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="kt">unsigned</span> <span class="kt">short</span> <span class="n">padData</span> <span class="o">=</span> <span class="p">((</span><span class="n">CamState</span><span class="p">.</span><span class="n">Owner</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">pad</span><span class="p">).</span><span class="n">data</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">short</span> <span class="n">padTrig</span> <span class="o">=</span> <span class="p">((</span><span class="n">CamState</span><span class="p">.</span><span class="n">Owner</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">pad</span><span class="p">).</span><span class="n">trig</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">fInitialize</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">InitializeInfoView</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">((</span><span class="n">SystemFlag</span> <span class="o">&amp;</span> <span class="mi">2</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">nowPad</span> <span class="o">==</span> <span class="mh">0x0003</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">DoOptions</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">((</span><span class="n">padData</span> <span class="o">&amp;</span> <span class="mh">0x10</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">DoSelectItem</span><span class="p">(</span><span class="n">padTrig</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">PutItemList</span><span class="p">();</span>
    <span class="n">PutLifeBar</span><span class="p">(</span><span class="o">-</span><span class="mh">0x6e</span><span class="p">,</span> <span class="mh">0x61</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)(</span><span class="n">CamState</span><span class="p">.</span><span class="n">Owner</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">life</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)(</span><span class="n">CamState</span><span class="p">.</span><span class="n">Owner</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">lifemax</span><span class="p">);</span>
    <span class="n">PutLifeBarS</span><span class="p">();</span>
    <span class="n">PutStrain</span><span class="p">();</span>

    <span class="k">if</span> <span class="p">(((</span><span class="n">nowPad</span> <span class="o">&amp;</span> <span class="mh">0x0100</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">SystemFlag</span> <span class="o">&amp;</span> <span class="mi">5</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">PutMapMode</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">PutMap</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="n">PauseProc</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="box box-information">
  <p>Being a PlayStation game from 1998, developed by a newly-created studio staffed with talented but inexperienced people, <code class="language-plaintext highlighter-rouge">DoInfoViewProc()</code> does a bunch of things that probably should’ve been divided up into multiple, separate functions.</p>

  <p>Since we detour it, our replacement needs to replicate its functionality, hence all the extraneous boilerplate.
Keep in mind that this is a <em>cleaned-up</em> version of the function, the original <code class="language-plaintext highlighter-rouge">DoInfoViewProc()</code> is quite a bit more messy and convoluted than this.</p>
</div>

<p>Then, we add <code class="language-plaintext highlighter-rouge">DoInfoViewProc</code> into <code class="language-plaintext highlighter-rouge">weak-symbols.txt</code>, plop object files delinked from <code class="language-plaintext highlighter-rouge">GAME.EXE</code> inside <code class="language-plaintext highlighter-rouge">obj/</code>, stash our source code inside <code class="language-plaintext highlighter-rouge">src/</code> and let the linker do the work for us:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ make
mipsel-linux-gnu-gcc -fno-pic -mno-abicalls -Wa,-mno-pdr -march=r3000 -mfp32 -I include/ -std=c11 -Os -Wall -Werror -nostdinc -isystem /home/boricj/Documents/PsyQ/elf/include -c src/cheats.c -o build/cheats.o
mipsel-linux-gnu-gcc -fno-pic -mno-abicalls -Wa,-mno-pdr -march=r3000 -mfp32 -I include/ -std=c11 -Os -Wall -Werror -nostdinc -isystem /home/boricj/Documents/PsyQ/elf/include -c src/libsn.c -o build/libsn.o
mipsel-linux-gnu-gcc -fno-pic -mno-abicalls -Wa,-mno-pdr -march=r3000 -mfp32 -I include/ -std=c11 -Os -Wall -Werror -nostdinc -isystem /home/boricj/Documents/PsyQ/elf/include -c src/__main.c -o build/__main.o
mipsel-linux-gnu-gcc -fno-pic -mno-abicalls -Wa,-mno-pdr -march=r3000 -mfp32 -I include/ -std=c11 -Os -Wall -Werror -nostdinc -isystem /home/boricj/Documents/PsyQ/elf/include -c src/valloc.c -o build/valloc.o
mipsel-linux-gnu-as -march=r3000 -mfp32 -mno-pdr -I include/ -I /home/boricj/Documents/PsyQ/elf/include src/start.S -o build/start.o
mipsel-linux-gnu-objcopy --weaken-symbols=weak-symbols.txt obj/game.o build/game.weakened.o
mipsel-linux-gnu-objcopy --weaken-symbols=weak-symbols.txt obj/psyq.o build/psyq.weakened.o
mipsel-linux-gnu-ld -nostdlib -no-pie -static -melf32ltsmip -T /home/boricj/Documents/PSn00bSDK/libpsn00b/ldscripts/exe.ld -Ttext=0x80010100 -L /home/boricj/Documents/PsyQ/elf/lib --defsym=Scratchpad=0x1f800000 --defsym=PersistentState=0x80010000 --defsym=MemoryPool=0x80200000 --defsym=MemoryDiskSentinel=0x807f0000 build/cheats.o build/libsn.o build/__main.o build/valloc.o build/start.o build/game.weakened.o build/psyq.weakened.o -lcd -lgs -lgpu -lgte -lpad -lmcrd -lcard -lsnd -lspu -letc -lc -lapi -o build/game.elf
ps1-packer build/game.elf -o build/game.exe

ps1-packer by Nicolas "Pixel" Noble
https://github.com/grumpycoders/pcsx-redux/tree/main/tools/ps1-packer/

Input file: build/game.elf
pc: 0x800106b0  gp: 0x00000000  sp: 0x00000000
file size: 700444 -&gt; 194560

File build/game.exe created. All done.
</code></pre></div></div>

<p>We end up with both an ELF executable file, straight out of our modern toolchain, as well as a PS-EXE file native to the PlayStation.
I could repack the ISO of the game with it, but I want fast iterations so we’ll just start the game, pause execution the moment <code class="language-plaintext highlighter-rouge">GAME.EXE</code> gets loaded and inject our version instead:</p>

<div class="two-columns">
    <figure>
    <video controls="" preload="metadata" width="100%">
        <source src="/tenchu1/assets/part-11/original-executable.webm" type="video/webm" />
    </video>
    <figcaption>Figure 2: Unmodified GAME.EXE with original debug menu</figcaption>
</figure>
    <figure>
    <video controls="" preload="metadata" width="100%">
        <source src="/tenchu1/assets/part-11/modified-executable.webm" type="video/webm" />
    </video>
    <figcaption>Figure 3: Modified GAME.EXE with custom debug menu</figcaption>
</figure>
</div>

<p>There are some visible bugs in the modified executable (and it will crash when entering a cutscene), but <em>it mostly works</em>.
That program has gone through a meat grinder and got chunked into object files, some bits were taken out and some bits were added… and the linker then stitched it all back together into something that somehow runs.</p>

<p>I feel like a hacksaw-wielding maniac that just got away with mutilating a program in plain sight.</p>

<div class="box box-information">
  <p>CS101 teachers assume that toolchains are a strict progression of source code through compilers, assemblers and linkers to executable, but actually from a non-linear, unacademic viewpoint it’s more like a big ball of wibbly wobbly, bits-y wimey… stuff.</p>

  <p>This is why I say that this modding framework is powered by their tears.
If I were pulling this kind of stunts back in university, I would’ve been branded a heretic… or at the very least gave the teaching staff <em>huge</em> migraines.</p>
</div>

<h3 id="i-feel-the-need-the-need-for-heap">I feel the need… The need for heap.</h3>

<p>This is but a single symbol being overridden.
What if we wanted to make more ambitious modifications?</p>

<p>Say we want to inject a lot of extra code and data into the game.
The original game’s heap occupies the address range <code class="language-plaintext highlighter-rouge">0x800dc000-0x801fc000</code>, leaving only 16 KiB for the stack at the end of the RAM.
Delinking it will work, but there’s only so much wiggle room left in the program’s memory layout before we collide with the stack.</p>

<p>One way to address it would be to leverage a PlayStation dev kit and its 8 MiB of RAM, so that we can stuff the heap there by telling the linker that the <code class="language-plaintext highlighter-rouge">MemoryPool</code> symbol is at address <code class="language-plaintext highlighter-rouge">0x80200000</code>.
This frees 1152 KiB for our modifications, but if our modifications requires a bunch of dynamic allocations then we’re still limited by the original memory allocator’s heap size (and whatever the game left us to play with).</p>

<p>Instead, let’s do something radical and rip it out entirely.
We can do so by writing a reimplementation where we can control the heap size, here set to 2 MiB:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"valloc.h"</span><span class="cp">
</span>
<span class="cp">#define NULL (0)
</span>
<span class="kt">void</span> <span class="o">*</span><span class="nf">memcpy</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dst</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">src</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">len</span><span class="p">);</span>
<span class="kt">void</span> <span class="o">*</span><span class="nf">memset</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dst</span><span class="p">,</span> <span class="kt">int</span> <span class="n">c</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">len</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">sprintf</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">fmt</span><span class="p">,</span> <span class="p">...);</span>

<span class="cp">#include</span> <span class="cpf">"adt.h"</span><span class="cp">
</span>
<span class="k">extern</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">MemoryPool</span><span class="p">[];</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">MemoryPoolSize</span> <span class="o">=</span> <span class="mh">0x200000</span><span class="p">;</span>

<span class="cp">#define IS_FREE(node) ((node-&gt;size &amp; 0x80000000L) == 0)
#define IS_USED(node) ((node-&gt;size &amp; 0x80000000L) != 0)
</span>
<span class="cp">#define MARK_FREE(node) (node-&gt;size &amp;= 0x7fffffffL)
#define MARK_USED(node) (node-&gt;size |= 0x80000000L)
</span>
<span class="cp">#define TO_NODE(ptr) (((struct VMhead *)ptr) - 1)
#define FROM_NODE(node) ((void *)(node + 1))
</span>
<span class="cp">#define GET_NODE_SIZE(node) (node-&gt;size &amp; 0x7fffffffL)
#define SET_NODE_SIZE(node, newsize) (node-&gt;size = (node-&gt;size &amp; 0x80000000L) | newsize)
</span>
<span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">virtual_memory_pool</span><span class="p">;</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">merge_free_nodes</span><span class="p">(</span><span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">node</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&amp;&amp;</span> <span class="n">IS_FREE</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">+=</span> <span class="n">GET_NODE_SIZE</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">)</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">VMhead</span><span class="p">);</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">split_node</span><span class="p">(</span><span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">node</span><span class="p">,</span> <span class="kt">int</span> <span class="n">offset</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">GET_NODE_SIZE</span><span class="p">(</span><span class="n">node</span><span class="p">)</span> <span class="o">&gt;</span> <span class="p">(</span><span class="n">offset</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">VMhead</span><span class="p">)))</span> <span class="p">{</span>
        <span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">rest</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>

        <span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">newNode</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="p">)(</span><span class="n">FROM_NODE</span><span class="p">(</span><span class="n">node</span><span class="p">)</span> <span class="o">+</span> <span class="n">offset</span><span class="p">);</span>
        <span class="n">newNode</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">=</span> <span class="n">GET_NODE_SIZE</span><span class="p">(</span><span class="n">node</span><span class="p">)</span> <span class="o">-</span> <span class="n">offset</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">VMhead</span><span class="p">);</span>
        <span class="n">newNode</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">rest</span><span class="p">;</span>

        <span class="n">SET_NODE_SIZE</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">offset</span><span class="p">);</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="n">newNode</span><span class="p">;</span>

        <span class="n">merge_free_nodes</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">vinit</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">adr</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">adr</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">adr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">MemoryPool</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">size</span> <span class="o">=</span> <span class="n">MemoryPoolSize</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">virtual_memory_pool</span> <span class="o">=</span> <span class="n">adr</span><span class="p">;</span>
    <span class="n">virtual_memory_pool</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">=</span> <span class="n">size</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">VMhead</span><span class="p">);</span>
    <span class="n">virtual_memory_pool</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">unsigned</span> <span class="kt">long</span> <span class="nf">vgetmaxsize</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">node</span> <span class="o">=</span> <span class="n">virtual_memory_pool</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">maxSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span><span class="n">node</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">IS_FREE</span><span class="p">(</span><span class="n">node</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">maxSize</span> <span class="o">&lt;</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">size</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">maxSize</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">size</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">node</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">maxSize</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">unsigned</span> <span class="kt">long</span> <span class="nf">vgetfreesize</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">node</span> <span class="o">=</span> <span class="n">virtual_memory_pool</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">freeSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">while</span> <span class="p">(</span><span class="n">node</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">IS_FREE</span><span class="p">(</span><span class="n">node</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">freeSize</span> <span class="o">+=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">size</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">node</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">freeSize</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">unsigned</span> <span class="kt">long</span> <span class="nf">vsize</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">TO_NODE</span><span class="p">(</span><span class="n">ptr</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">&amp;</span> <span class="mh">0x7fffffffL</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="o">*</span><span class="nf">valloc</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">virtual_memory_pool</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">vinit</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">size</span> <span class="o">=</span> <span class="p">(</span><span class="n">size</span> <span class="o">+</span> <span class="mi">3</span><span class="p">)</span> <span class="o">&amp;</span> <span class="o">-</span><span class="mi">4</span><span class="p">;</span>

    <span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">node</span> <span class="o">=</span> <span class="n">virtual_memory_pool</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">node</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">IS_FREE</span><span class="p">(</span><span class="n">node</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">&gt;=</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">split_node</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
            <span class="n">MARK_USED</span><span class="p">(</span><span class="n">node</span><span class="p">);</span>

            <span class="k">return</span> <span class="n">FROM_NODE</span><span class="p">(</span><span class="n">node</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="n">node</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
    <span class="kt">int</span> <span class="n">maxSize</span> <span class="o">=</span> <span class="n">vgetmaxsize</span><span class="p">();</span>
    <span class="kt">int</span> <span class="n">freeSize</span> <span class="o">=</span> <span class="n">vgetfreesize</span><span class="p">();</span>
    <span class="n">sprintf</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="s">"OUT OF MEMORY</span><span class="se">\n</span><span class="s">REQUEST=%d</span><span class="se">\n</span><span class="s">FREE=%d(%d)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">size</span><span class="p">,</span> <span class="n">maxSize</span><span class="p">,</span> <span class="n">freeSize</span><span class="p">);</span>
    <span class="n">SystemOut</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="o">*</span><span class="nf">vcalloc</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">size</span><span class="p">,</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">valloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
    <span class="n">memset</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ptr</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="o">*</span><span class="nf">vrealloc</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// FIXME: optimize realloc()</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">vfree</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">void</span> <span class="o">*</span><span class="n">newptr</span> <span class="o">=</span> <span class="n">valloc</span><span class="p">(</span><span class="n">size</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ptr</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="n">oldsize</span> <span class="o">=</span> <span class="n">vsize</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
        <span class="n">memcpy</span><span class="p">(</span><span class="n">newptr</span><span class="p">,</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">oldsize</span> <span class="o">&lt;</span> <span class="n">size</span> <span class="o">?</span> <span class="n">oldsize</span> <span class="o">:</span> <span class="n">size</span><span class="p">);</span>
        <span class="n">vfree</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">newptr</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="o">*</span><span class="nf">vmemoryGC</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// FIXME: implement vmemoryGC()</span>
    <span class="k">return</span> <span class="n">ptr</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">vfree</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ptr</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">struct</span> <span class="n">VMhead</span> <span class="o">*</span><span class="n">node</span> <span class="o">=</span> <span class="n">TO_NODE</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">IS_FREE</span><span class="p">(</span><span class="n">node</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">SystemOut</span><span class="p">(</span><span class="s">"DOUBLE MEMORY FREE"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="n">MARK_FREE</span><span class="p">(</span><span class="n">node</span><span class="p">);</span>

        <span class="n">merge_free_nodes</span><span class="p">(</span><span class="n">node</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">__attribute</span><span class="p">((</span><span class="n">noreturn</span><span class="p">))</span> <span class="kt">void</span> <span class="nf">SystemOut</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">AdtMessageBox</span><span class="p">(</span><span class="s">"*** SYSTEM OUT ***</span><span class="se">\n\n</span><span class="s">%s"</span><span class="p">,</span> <span class="n">string</span><span class="p">);</span>
    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This crappy, singly-linked list of memory blocks that wouldn’t look out of place in the 1980s follows the design of the game’s heap allocator implementation, but we could have retrofitted a more modern one, with advanced data structures and better fragmentation characteristics if we wanted.</p>

<div class="box box-information">
  <p>With our fancy Makefile, if we put this code inside <code class="language-plaintext highlighter-rouge">src/valloc.c</code> and the original delinked code inside <code class="language-plaintext highlighter-rouge">obj/valloc.o</code> then the source code will be picked over the object file automatically.</p>
</div>

<p>There’s no video this time because there are no user-visible changes here, but we’ve just swapped out an entire source file from the original program (that we do not have the source code for by the way) with another, ran <code class="language-plaintext highlighter-rouge">make</code> to invoke a couple of standard toolchain programs and it spat out a modified executable that runs.</p>

<p>If this was happening inside a movie, I’d be rolling my eyes at that Hollywood hacking scene.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We’ve leveraged the delinking technique to produce modified versions of an executable, by slicing it into object files and then stuffing some additional source code written by us, before relinking it as a whole again.
This proof-of-concept is an interesting alternative to the typical kind of binary patching, where the reverse-engineer needs to contort its modifications within the existing layout of a program.</p>

<p>While the entry ticket is higher because it requires a curated Ghidra database for my delinking tooling to produce usable object files, it allows patching of programs at a far more intrusive scale than traditional techniques can realistically achieve.
Constraints from the original program no longer apply when it is dismantled section by section and rebuilt from the ground up.</p>

<div style="display: grid; grid-template-columns: 1fr 1fr; margin-top: 1ch; margin-bottom: 1ch;">
<div style="padding-right: 15px;">


<a class="prev" href="/tenchu1/2024/05/15/part-10.html">&laquo; Decompiling Tenchu: Stealth Assassins part 10: potpourri status update</a>

</div>
<div style="padding-left: 15px; text-align: right;">


<a href="/tenchu1/2024/06/19/part-12.html">Decompiling Tenchu: Stealth Assassins part 12: when stuffing PlayStation code inside a Linux MIPS process is no longer worth the trouble &raquo;</a>

</div>
</div>]]></content><author><name>Jean-Baptiste Boric</name></author><category term="tenchu1" /><summary type="html"><![CDATA[Previously in this series of articles, I’ve provided a potpourri-style update on this project due to a lack of demonstrable progress. Since then, the wretched demon of optimizing MIPS assemblers vacuuming up copies of instructions with HI16 relocations inside branch delay slots has been tamed, so we’re back on our irregularly-scheduled program.]]></summary></entry><entry><title type="html">Decompiling Tenchu: Stealth Assassins part 10: potpourri status update</title><link href="https://boricj.net/tenchu1/2024/05/15/part-10.html" rel="alternate" type="text/html" title="Decompiling Tenchu: Stealth Assassins part 10: potpourri status update" /><published>2024-05-15T02:00:00+02:00</published><updated>2024-05-15T02:00:00+02:00</updated><id>https://boricj.net/tenchu1/2024/05/15/part-10</id><content type="html" xml:base="https://boricj.net/tenchu1/2024/05/15/part-10.html"><![CDATA[<p><a href="/tenchu1/2024/04/15/part-9.html">Previously</a> in this <a href="/tenchu1/2024/02/05/introduction.html">series of articles</a>, we’ve completed the rescue of the debugging data that was nearly lost to the mists of time onto a more tangible vessel.
A bunch of stuff happened since then, but nothing worthy of an article by itself, so I guess I’m going for a potpourri status update now.</p>

<h2 id="the-version-tracking-trail">The version tracking trail</h2>

<p><a href="/tenchu1/2024/03/18/part-6.html">A while ago</a>, I found a SYM file containing debugging symbols for an early, lost build of the game.
That file is a treasure trove of symbol names, data types and function prototypes, far too precious to pass over, which is why I’ve spent a significant amount of time and effort to rescue it.</p>

<p>However, since I eventually want to work on the latest release of the game, this data needs to be version tracked all the way across the entire span of the game’s release timeline to use it there, a multi-step journey all by itself:</p>

<ul>
  <li><a href="/tenchu1/2024/04/01/part-8.html">From</a> <code class="language-plaintext highlighter-rouge">PSX.SYM</code> to <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code>, a placeholder program in the shape of the lost build to hold that debugging data inside Ghidra ;</li>
  <li><a href="/tenchu1/2024/04/15/part-9.html">From</a> <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code> to <code class="language-plaintext highlighter-rouge">PSX.EXE</code>, the earliest, non-working artifact we have of the game that is a close match ;</li>
  <li>From <code class="language-plaintext highlighter-rouge">PSX.EXE</code> to <em>Rittai Ninja Katsugeki Tenchu</em>’s <code class="language-plaintext highlighter-rouge">GAME.EXE</code>, the last version of the game that is similarly architectured into a single monolithic executable ;</li>
  <li>…</li>
</ul>

<p>This third step has been completed, but unlike the previous ones it was just a normal version tracking session that didn’t require any dark magic to pull off, so I didn’t bother writing a dedicated article over that.</p>

<div class="box box-information">
  <p>This debugging data doesn’t quite cover the entire <code class="language-plaintext highlighter-rouge">GAME.EXE</code> file, but I have a mostly-annotated version of the game I can work with for now.
Therefore, while the objective is still to version tracking that data all the way to <em>Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen</em>, I’m putting that goal aside for the time being.</p>
</div>

<h2 id="the-tenchu-modding-community">The Tenchu modding community</h2>

<p>While I was underway with the latest version tracking session, I’ve joined the <a href="https://discord.gg/UpAAyKk">Tenchu Speedrunning Discord server</a> that serves as the hub of the Tenchu community and announced my reverse-engineering project there.</p>

<div class="box box-warning">
  <p>The Tenchu series hasn’t seen a new release in over 10 years and is a bit of a forgotten cult classic at this point.
Also, the speedrunning activity for the original game in particular is at a low ebb compared to a couple of years ago, so that Discord server is a somewhat quiet place these days.</p>
</div>

<p>That being said, I did get in touch there with a couple of Tenchu modders that are very interested by the contents of the Ghidra database I’ve built as part of this project.
In particular, some lights have been shed on the animation and move set systems of the game (during vocal chat sessions late at night), which is key to creating fully playable versions of enemy and boss characters.</p>

<p>By sharing that data with the Tenchu community, it enables modifications more ambitious in scope than ever before by modders, at least until the decompilation project itself is completed.</p>

<div class="box box-information">
  <p>It will take a while before we’ll see the results (the modders are still digesting all that new information and experimenting with it as I’m writing this), but at some point some new videos are likely to appear on <a href="https://www.youtube.com/channel/UCyuKvZXLmtL2y6ohCv1Z5Dw">Teslafane</a>’s YouTube channel, among other places.</p>
</div>

<h2 id="the-delinking-debacle">The delinking debacle</h2>

<p>I have made several improvements to <a href="https://github.com/boricj/ghidra-delinker-extension">my Ghidra extension</a>, but I have come to realize that delinking MIPS code in an automated fashion is an absolute, unmitigated <strong>nightmare</strong> to pull off for several reasons.</p>

<p>While Ghidra has a concept of references, it does not model the relocations I require for delinking.
I’m working around this problem with custom-built analyzers that try to identify relocation spots based on the references, but Ghidra’s own automatic analyzers have a tendency to annotate references whenever and however they want, in a manner that confuses and frustrates my tooling and me.</p>

<div class="box box-warning">
  <p>Even with my latest improvements in pattern matching, a fair amount of manual cleanup of references is still required in order for my extension to successfully delink code, especially within large functions or with complex memory access patterns.</p>
</div>

<p>The <em>real</em> hair-pulling part, however, comes in with the specifics of the MIPS architecture.
In particular, a couple of quirks and features of this instruction set interact together into an absolute mess to pick apart:</p>

<ul>
  <li>HI16/LO16 relocations are split across two instructions and relocations entries, as such they can be placed quite far apart from each other ;</li>
  <li>The RISC-flavored, register-and-immediate load/store model means that complex addressing patterns requires a lot of instructions to synthesize… and the two HI16/LO16 instructions will be located somewhere in that chain ;</li>
  <li>The branch delay slot can lead to some peculiar instruction scheduling if the toolchain doesn’t put a no-op in there.</li>
</ul>

<p>I’ve accumulated some nasty test cases inside my test suite, gleaned from the game’s code, that stem from these interactions.
This is the kind of gnarly stuff I’m dealing with:</p>

<pre><code class="language-asm">#include &lt;asm/reg.h&gt;

.text
.set	noreorder
test:
	beq	$a0,$zero,1f
	lui	$v0,%hi(HELLO_WORLD)
	lui	$v0,%hi(GOODBYE_WORLD)
	j	2f
	lb	$v0,%lo(GOODBYE_WORLD)($v0)
1:
	lb	$v0,%lo(HELLO_WORLD)($v0)
2:
	jr	$ra
	nop
.data
HELLO_WORLD:
.asciiz	"Hello, world!"
GOODBYE_WORLD:
.asciiz	"Goodbye, world!"
</code></pre>

<div class="box box-warning">
  <p>It might look obvious with the relocation annotations in the assembly code, but remember that all we have to work with inside Ghidra are references on the two <code class="language-plaintext highlighter-rouge">lb</code> instructions and a bunch of immediate integer constants.</p>

  <p>For this particular case, I had to integrate code block flow analysis into my analyzers in order to untangle the usage of the <code class="language-plaintext highlighter-rouge">v0</code> register.</p>
</div>

<p>Unfortunately, that’s not the most messed up example I have.
One particularly… <em>creative</em>… instruction pattern present in the game that my Ghidra extension doesn’t handle at the moment looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                             LAB_80044468                                    XREF[1]:     8004443c(j)  
        80044468 05 00 9e 14     bne        a0,s8,LAB_80044480
        8004446c 08 80 03 3c     _lui       v1,0x8008
        80044470 01 00 09 24     li         t1,0x1
        80044474 b8 00 a9 a7     sh         t1,local_68(sp)
                             LAB_80044478                                    XREF[1]:     80044460(j)  
        80044478 74 07 80 a7     sh         zero,0x774(gp)=&gt;DAT_8008bca8
                             LAB_8004447c                                    XREF[2]:     800443e4(j), 80044458(j)  
        8004447c 08 80 03 3c     lui        v1,0x8008
                             LAB_80044480                                    XREF[1]:     80044468(j)  
        80044480 18 2e 63 24     addiu      v1,v1,0x2e18
        80044484 01 80 02 3c     lui        v0,0x8001
        80044488 02 00 42 90     lbu        v0,offset PersistentState.field0_0x0+2(v0)
        8004448c b0 00 a9 97     lhu        t1,local_70(sp)
        80044490 40 10 02 00     sll        v0,v0,0x1
        80044494 21 10 43 00     addu       v0,v0,v1
        80044498 00 00 42 94     lhu        v0,0x0(v0)=&gt;DAT_80082e18                         = 4Dh    M
</code></pre></div></div>

<p>It’s a bit hard to follow, but basically as far as I can tell the LO16 relocation located inside the <code class="language-plaintext highlighter-rouge">addiu</code> instruction at <code class="language-plaintext highlighter-rouge">80044480</code> has <em>two</em> HI16 counterparts in this snippet:</p>
<ul>
  <li>The first one is the <code class="language-plaintext highlighter-rouge">lui</code> at <code class="language-plaintext highlighter-rouge">8004446c</code>, in the delay slot of the previous <code class="language-plaintext highlighter-rouge">bne</code> branch instruction ;</li>
  <li>The second one is the <code class="language-plaintext highlighter-rouge">lui</code> at <code class="language-plaintext highlighter-rouge">8004447c</code>, right before the target of that same branch instruction.</li>
</ul>

<p>That’s right, the <code class="language-plaintext highlighter-rouge">lui</code> instruction targeted by a HI16 relocation has been <em>duplicated</em>.</p>

<div class="box box-information">
  <p>My best guess is that the assembler figured out it could shave off one instruction from the execution flow by copying the HI16 instruction inside the branch delay slots of the branch instructions targeting it, then shifting the branch targets to one instruction later to skip the original HI16 instruction, which remains for the fallthrough case.</p>
</div>

<div class="box box-addendum">
  <p><em>Sep 24, 2024</em>: when I originally wrote this article, I thought that this pattern couldn’t be represented by the <code class="language-plaintext highlighter-rouge">R_MIPS_HI16</code>/<code class="language-plaintext highlighter-rouge">R_MIPS_LO16</code> MIPS relocation pair of the ELF file format, which is the object file format I was exporting to.
It turns out that there is a <a href="https://www.mail-archive.com/gcc@gcc.gnu.org/msg94236.html">GNU extension</a> for this, as reported by mono21400 on the <a href="https://decomp.me/">decomp.me</a> Discord server.</p>

  <p>It’s not mentioned inside the <em>SYSTEM V APPLICATION BINARY INTERFACE MIPS® RISC Processor Supplement 3rd Edition</em> document that I was leaning on.
The MIPS psABI documentation in general is quite derelict, too.</p>
</div>

<p>Furthermore, my data model for synthesized relocations inside my Ghidra extension, which in theory is agnostic of any particular object file format (but in practice is heavily inspired by ELF because that’s what I’m familiar with), also can’t represent this.</p>

<div class="box box-warning">
  <p>A particularly cunning reverse-engineer might observe that patching the <code class="language-plaintext highlighter-rouge">bne</code> branch instruction to target one instruction earlier, so that the first <code class="language-plaintext highlighter-rouge">lui</code> inside the delay slot would become irrelevant because the second <code class="language-plaintext highlighter-rouge">lui</code> that would then get executed in all cases, reduces the HI16/LO16 pattern down to one manageable pair.</p>

  <p>It seems to work, but altering the code flow by hand to undo this optimization and normalize the HI16/LO16 relocation pairs is a rather fiddly, intrusive, extreme and error-prone solution…
Looks like I’ll have to improvise another crazy contraption to deal with this.</p>
</div>

<p>While each improvement of my MIPS analyzers reduces the quantity of unhandled relocation patterns, the remaining ones become harder and harder to address.
If there’s a limit to how much bullshit I’m willing to put up with in order to dial in my delinker, this latest issue is getting really, <strong>really</strong> close to it.</p>

<h2 id="the-linking-lead">The linking lead</h2>

<p>I’ve already repurposed some of its <a href="/tenchu1/2024/03/18/part-6.html">bits</a> and <a href="/tenchu1/2024/03/25/part-7.html">pieces</a> to run on Linux before, but while I can generate an ELF object file of the whole game, it doesn’t mean that it actually works.
Or that I can actually link it as a Linux program, for that matter.</p>

<p>On the Linux front, while <a href="https://github.com/OpenDriver2/PsyCross">PsyCross</a> is an obvious seemingly drop-in replacement for the original Psy-Q SDK, it is not currently suitable in practice due to multiple missing functions and even entire libraries used by Tenchu.
Even if I were to fill in the blanks and get it to link, splattering lots of PlayStation code inside a Linux MIPS process is bound to create all kinds of havoc, <em>on top of</em> the experimental and temperamental nature of my delinker tooling.</p>

<p>Therefore, a more reasonable stepping stone would be to first delink and relink the game’s code as a PlayStation program using the original Psy-Q SDK.
If the game still works with the game’s code and data shuffled around, then the delinking process will be a success and I can go back to running over ABIs like a madman…</p>

<p>But of course it’s not quite that simple.</p>

<p>I do not want to use the original Sony toolchain because it’s thoroughly obsolete and I don’t have an exporter to its proprietary object file format.
To work around this, I’m cobbling together an ELF conversion of the Psy-Q SDK out of <a href="https://gitlab.com/jype/psyq2elf">various</a> <a href="https://github.com/grumpycoders/pcsx-redux/tree/main/tools/psyq-obj-parser">tools</a>.
This introduces new sources of issues into the mix, but I’m fixing <a href="https://github.com/grumpycoders/pcsx-redux/pull/1653">the bugs</a> as I encounter them.</p>

<div class="box box-information">
  <p>At the moment, I have enough game code delinked and relinked successfully that I can play the briefing screen of a mission correctly and without crashing on a PlayStation emulator, but not much more.
Another achievement that’s not worth an article by itself, at least not until I can get the inventory selection screen running properly.</p>
</div>

<h2 id="conclusion">Conclusion</h2>

<p>So progress is being made behind the scenes on a bunch of topics, it’s just that nothing’s ready for a write-up at the moment.
Having ran out of articles in my buffer for over three weeks now, I figured I might as well write <em>something</em> to document what’s happening.</p>

<div class="box box-warning">
  <p>If it’s not obvious by now, the publication schedule of this blog is definitely not regular anymore.
Use the RSS feed if you want to be notified whenever I get around to publish something.</p>
</div>

<div style="display: grid; grid-template-columns: 1fr 1fr; margin-top: 1ch; margin-bottom: 1ch;">
<div style="padding-right: 15px;">


<a class="prev" href="/tenchu1/2024/04/15/part-9.html">&laquo; Decompiling Tenchu: Stealth Assassins part 9: rescue the debugging data</a>

</div>
<div style="padding-left: 15px; text-align: right;">


<a href="/tenchu1/2024/05/31/part-11.html">Decompiling Tenchu: Stealth Assassins part 11: a modding framework powered by the tears of CS101 teachers &raquo;</a>

</div>
</div>]]></content><author><name>Jean-Baptiste Boric</name></author><category term="tenchu1" /><summary type="html"><![CDATA[Previously in this series of articles, we’ve completed the rescue of the debugging data that was nearly lost to the mists of time onto a more tangible vessel. A bunch of stuff happened since then, but nothing worthy of an article by itself, so I guess I’m going for a potpourri status update now.]]></summary></entry><entry><title type="html">Decompiling Tenchu: Stealth Assassins part 9: rescue the debugging data</title><link href="https://boricj.net/tenchu1/2024/04/15/part-9.html" rel="alternate" type="text/html" title="Decompiling Tenchu: Stealth Assassins part 9: rescue the debugging data" /><published>2024-04-15T02:00:00+02:00</published><updated>2024-04-15T02:00:00+02:00</updated><id>https://boricj.net/tenchu1/2024/04/15/part-9</id><content type="html" xml:base="https://boricj.net/tenchu1/2024/04/15/part-9.html"><![CDATA[<p><a href="/tenchu1/2024/04/01/part-8.html">Previously</a> in this <a href="/tenchu1/2024/02/05/introduction.html">series of articles</a>, we’ve discovered that the debugging symbols file <code class="language-plaintext highlighter-rouge">PSX.SYM</code> doesn’t match the <code class="language-plaintext highlighter-rouge">PSX.EXE</code> artifact we have on hand.
Undaunted, we’ve created a placeholder program <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code> that matches the layout of <code class="language-plaintext highlighter-rouge">PSX.SYM</code> and then loaded the debug data on top of it inside Ghidra.
In this part, we’ll complete the rescue of the debugging data by migrating it to <code class="language-plaintext highlighter-rouge">PSX.EXE</code>, a tangible executable.</p>

<h2 id="just-version-track-it-right">Just version track it, right?</h2>

<p>So, let’s fire up a Version Tracking session with <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code> as the source program and <code class="language-plaintext highlighter-rouge">PSX.EXE</code> as the destination program, click on the magic wand icon to run the correlators and…</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-9/version-tracking-no-matches.png" />
                <figcaption>Figure 1: Version tracking session with no matches.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>…there are no matches.</p>

<div class="box box-warning">
  <p>We have a source program with no bytes but metadata and a destination program with bytes but no metadata.
With no overlap between the two, Ghidra’s built-in correlators (which work with program bytes, references and symbol names) can’t match anything.</p>
</div>

<p>In theory, at this point the only thing we can do with an empty Version Tracking session is to create manual matches by hand, one by one.
The source program contains thousands of individual pieces of information, so that’s not a practical option.</p>

<h2 id="improving-the-dataset">Improving the dataset</h2>

<p>First order of business is to improve the quality of the metadata inside <code class="language-plaintext highlighter-rouge">PSX.EXE.elf</code>.
While loading <code class="language-plaintext highlighter-rouge">PSX.SYM</code> on top of it did bring a lot of information, there are quite a lot of blind spots left in there.
The Psy-Q SDK does not provide any debugging information about its functions and data, but even the game code itself isn’t fully covered.</p>

<div class="box box-information">
  <p>Before that, running Ghidra’s built-in <code class="language-plaintext highlighter-rouge">CreateFunctionsFromSelection.java</code> script on the <code class="language-plaintext highlighter-rouge">.text</code> section of <code class="language-plaintext highlighter-rouge">PSX.EXE</code> will discover and create the functions that Ghidra’s analyzers missed.
After all, we can’t match functions that do not exist in the destination program.</p>
</div>

<h3 id="scavenging-functions-out-of-raw-labels">Scavenging functions out of raw labels</h3>

<p>We have about 450 functions declared inside <code class="language-plaintext highlighter-rouge">PSX.SYM</code>, but there are many more functions than that in this executable.
Normally, the built-in <code class="language-plaintext highlighter-rouge">CreateFunctionsFromSelection.java</code> script can automatically create functions from dead subroutines, but our placeholder program has no instructions for Ghidra to detect subroutines, so we need another approach.</p>

<p>What we do have for everything are typeless symbols, raw addresses with nothing but names.
If the <code class="language-plaintext highlighter-rouge">.text</code> section contains only functions and there are no overlaps or holes, then we can take a symbol and create a function that spans until the next one.</p>

<p>That can be scripted like so:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//Converts a range of symbols to functions, useful for naked symbols inside .text sections.</span>
<span class="c1">//@author Jean-Baptiste Boric</span>
<span class="c1">//@category Functions</span>
<span class="c1">//@keybinding</span>
<span class="c1">//@menupath</span>
<span class="c1">//@toolbar</span>

<span class="kn">import</span> <span class="nn">java.util.HashMap</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">ghidra.app.script.GhidraScript</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.app.services.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.app.util.bin.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.address.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.block.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.ISF.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.protorules.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.listing.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.mem.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.pcode.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.reloc.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.scalar.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.symbol.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.util.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.util.exception.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SymbolsToFunctions</span> <span class="kd">extends</span> <span class="nc">GhidraScript</span> <span class="o">{</span>
	<span class="kd">public</span> <span class="nc">AddressFactory</span> <span class="n">addressFactory</span><span class="o">;</span>
	<span class="kd">public</span> <span class="nc">FunctionManager</span> <span class="n">functionManager</span><span class="o">;</span>
	<span class="kd">public</span> <span class="nc">SymbolTable</span> <span class="n">symbolTable</span><span class="o">;</span>

	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="n">addressFactory</span> <span class="o">=</span> <span class="n">currentProgram</span><span class="o">.</span><span class="na">getAddressFactory</span><span class="o">();</span>
		<span class="n">functionManager</span> <span class="o">=</span> <span class="n">currentProgram</span><span class="o">.</span><span class="na">getFunctionManager</span><span class="o">();</span>
		<span class="n">symbolTable</span> <span class="o">=</span> <span class="n">currentProgram</span><span class="o">.</span><span class="na">getSymbolTable</span><span class="o">();</span>

		<span class="k">for</span> <span class="o">(</span><span class="nc">AddressRange</span> <span class="n">selectionRange</span> <span class="o">:</span> <span class="n">currentSelection</span><span class="o">.</span><span class="na">getAddressRanges</span><span class="o">())</span> <span class="o">{</span>
			<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">AddressSetView</span><span class="o">&gt;</span> <span class="n">symbolRanges</span> <span class="o">=</span> <span class="n">getSymbolRanges</span><span class="o">(</span><span class="n">selectionRange</span><span class="o">);</span>

			<span class="k">for</span> <span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">Entry</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">AddressSetView</span><span class="o">&gt;</span> <span class="n">symbolRange</span> <span class="o">:</span> <span class="n">symbolRanges</span><span class="o">.</span><span class="na">entrySet</span><span class="o">())</span> <span class="o">{</span>
				<span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">symbolRange</span><span class="o">.</span><span class="na">getKey</span><span class="o">();</span>
				<span class="nc">AddressSetView</span> <span class="n">body</span> <span class="o">=</span> <span class="n">symbolRange</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span>

				<span class="nc">Function</span> <span class="n">func</span> <span class="o">=</span> <span class="n">functionManager</span><span class="o">.</span><span class="na">getFunctionAt</span><span class="o">(</span><span class="n">body</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">());</span>
				<span class="k">if</span> <span class="o">(</span><span class="n">func</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
					<span class="n">writer</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"Creating function %s with body %s"</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">body</span><span class="o">));</span>
					<span class="n">functionManager</span><span class="o">.</span><span class="na">createFunction</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">body</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">(),</span> <span class="n">body</span><span class="o">,</span> <span class="nc">SourceType</span><span class="o">.</span><span class="na">IMPORTED</span><span class="o">);</span>
				<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
					<span class="n">func</span><span class="o">.</span><span class="na">setName</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="nc">SourceType</span><span class="o">.</span><span class="na">IMPORTED</span><span class="o">);</span>
					<span class="n">func</span><span class="o">.</span><span class="na">setBody</span><span class="o">(</span><span class="n">body</span><span class="o">);</span>
				<span class="o">}</span>
			<span class="o">}</span>
		<span class="o">}</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">AddressSetView</span><span class="o">&gt;</span> <span class="nf">getSymbolRanges</span><span class="o">(</span><span class="nc">AddressRange</span> <span class="n">addressRange</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">AddressSetView</span><span class="o">&gt;</span> <span class="n">symbolRanges</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>

		<span class="nc">String</span> <span class="n">previousSymbolName</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
		<span class="nc">Address</span> <span class="n">previousSymbolAddress</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>

		<span class="k">for</span> <span class="o">(</span><span class="nc">Symbol</span> <span class="n">symbol</span> <span class="o">:</span> <span class="n">symbolTable</span><span class="o">.</span><span class="na">getPrimarySymbolIterator</span><span class="o">(</span><span class="n">addressFactory</span><span class="o">.</span><span class="na">getAddressSet</span><span class="o">(</span><span class="n">addressRange</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">(),</span> <span class="n">addressRange</span><span class="o">.</span><span class="na">getMaxAddress</span><span class="o">()),</span> <span class="kc">true</span><span class="o">))</span> <span class="o">{</span>
			<span class="nc">String</span> <span class="n">symbolName</span> <span class="o">=</span> <span class="n">symbol</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span>
			<span class="nc">Address</span> <span class="n">symbolAddress</span> <span class="o">=</span> <span class="n">symbol</span><span class="o">.</span><span class="na">getAddress</span><span class="o">();</span>

			<span class="k">if</span> <span class="o">(</span><span class="n">previousSymbolAddress</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
				<span class="n">symbolRanges</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">previousSymbolName</span><span class="o">,</span> <span class="n">addressFactory</span><span class="o">.</span><span class="na">getAddressSet</span><span class="o">(</span><span class="n">previousSymbolAddress</span><span class="o">,</span> <span class="n">symbolAddress</span><span class="o">.</span><span class="na">previous</span><span class="o">()));</span>
			<span class="o">}</span>

			<span class="n">previousSymbolName</span> <span class="o">=</span> <span class="n">symbolName</span><span class="o">;</span>
			<span class="n">previousSymbolAddress</span> <span class="o">=</span> <span class="n">symbolAddress</span><span class="o">;</span>
		<span class="o">}</span>

		<span class="k">if</span> <span class="o">(</span><span class="n">previousSymbolAddress</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="n">symbolRanges</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">previousSymbolName</span><span class="o">,</span> <span class="n">addressFactory</span><span class="o">.</span><span class="na">getAddressSet</span><span class="o">(</span><span class="n">previousSymbolAddress</span><span class="o">,</span> <span class="n">addressRange</span><span class="o">.</span><span class="na">getMaxAddress</span><span class="o">()));</span>
		<span class="o">}</span>

		<span class="k">return</span> <span class="n">symbolRanges</span><span class="o">;</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="box box-warning">
  <p>A fair amount of Psy-Q SDK functions like <code class="language-plaintext highlighter-rouge">CdSearchFile</code> use a bunch of private static functions that follow the public function but do not appear in the symbol table.
Since we don’t have the data to reconstruct this, this script will create a big <code class="language-plaintext highlighter-rouge">CdSearchFile</code> function instead of one small <code class="language-plaintext highlighter-rouge">CdSearchFile</code> function and a bunch of follow-up static functions.</p>
</div>

<p>Running this script on a small selection of addresses for testing yields the following output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SymbolsToFunctions.java&gt; Running...
Creating function cd_tell with body [[8008ae30, 8008ae67] ]
Creating function cd_seek with body [[8008ad8c, 8008ae2f] ]
Creating function cd_read with body [[8008aea0, 8008b02b] ]
Creating function cd_getsize with body [[8008ae68, 8008ae9f] ]
SymbolsToFunctions.java&gt; Finished!
</code></pre></div></div>

<p>In total, over 640 functions were recovered in this manner.
Sure, they don’t have signatures, they don’t have bytes and some of them don’t even have the right size, but we can fix at least the first problem.</p>

<h3 id="we-have-the-means-to-make-you-signed">We have the means to make you signed</h3>

<p>Just because the SDK doesn’t provide debugging symbols doesn’t mean we can’t recover information through different means.
The Psy-Q headers provide symbol names and type information, at least for the public interfaces.
The <a href="https://github.com/lab313ru/ghidra_psx_ldr">PlayStation executable loader</a> includes data type archives processed from the SDK header files, which we can apply using this script:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//Applies function signatures held inside data type archives.</span>
<span class="c1">//@author Jean-Baptiste Boric</span>
<span class="c1">//@category Functions</span>
<span class="c1">//@keybinding</span>
<span class="c1">//@menupath</span>
<span class="c1">//@toolbar</span>

<span class="kn">import</span> <span class="nn">java.util.Arrays</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.ArrayList</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.HashMap</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.regex.Pattern</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">ghidra.app.script.GhidraScript</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.app.services.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.app.util.bin.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.address.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.block.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.ISF.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.protorules.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.listing.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.mem.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.pcode.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.reloc.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.scalar.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.symbol.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.util.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.util.exception.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ApplyFunctionSignaturesFromDataTypeArchive</span> <span class="kd">extends</span> <span class="nc">GhidraScript</span> <span class="o">{</span>
	<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Pattern</span> <span class="no">PATTERN</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">".+/functions/.+"</span><span class="o">);</span>

	<span class="kd">public</span> <span class="nc">AddressFactory</span> <span class="n">addressFactory</span><span class="o">;</span>
	<span class="kd">public</span> <span class="nc">FunctionManager</span> <span class="n">functionManager</span><span class="o">;</span>
	<span class="kd">public</span> <span class="nc">SymbolTable</span> <span class="n">symbolTable</span><span class="o">;</span>

	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="n">addressFactory</span> <span class="o">=</span> <span class="n">currentProgram</span><span class="o">.</span><span class="na">getAddressFactory</span><span class="o">();</span>
		<span class="n">functionManager</span> <span class="o">=</span> <span class="n">currentProgram</span><span class="o">.</span><span class="na">getFunctionManager</span><span class="o">();</span>
		<span class="n">symbolTable</span> <span class="o">=</span> <span class="n">currentProgram</span><span class="o">.</span><span class="na">getSymbolTable</span><span class="o">();</span>

		<span class="nc">DataTypeManager</span> <span class="n">dtm</span> <span class="o">=</span> <span class="n">getDataTypeManager</span><span class="o">(</span><span class="s">"psyq470"</span><span class="o">);</span>
		<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">FunctionSignature</span><span class="o">&gt;</span> <span class="n">signatures</span> <span class="o">=</span> <span class="n">getFunctionSignatures</span><span class="o">(</span><span class="n">dtm</span><span class="o">,</span> <span class="no">PATTERN</span><span class="o">);</span>
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Function</span><span class="o">&gt;</span> <span class="n">functions</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
		<span class="n">functionManager</span><span class="o">.</span><span class="na">getFunctions</span><span class="o">(</span><span class="n">currentSelection</span><span class="o">,</span> <span class="kc">true</span><span class="o">).</span><span class="na">forEachRemaining</span><span class="o">(</span><span class="nl">functions:</span><span class="o">:</span><span class="n">add</span><span class="o">);</span>
		<span class="n">applyFunctionSignatures</span><span class="o">(</span><span class="n">functions</span><span class="o">,</span> <span class="n">signatures</span><span class="o">);</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kt">void</span> <span class="nf">applyFunctionSignatures</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Function</span><span class="o">&gt;</span> <span class="n">functions</span><span class="o">,</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">FunctionSignature</span><span class="o">&gt;</span> <span class="n">signatures</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="k">for</span> <span class="o">(</span><span class="nc">Function</span> <span class="n">function</span> <span class="o">:</span> <span class="n">functions</span><span class="o">)</span> <span class="o">{</span>
			<span class="nc">FunctionSignature</span> <span class="n">signature</span> <span class="o">=</span> <span class="n">signatures</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">function</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
			<span class="k">if</span> <span class="o">(</span><span class="n">signature</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">signature</span><span class="o">.</span><span class="na">isEquivalentSignature</span><span class="o">(</span><span class="n">function</span><span class="o">.</span><span class="na">getSignature</span><span class="o">()))</span> <span class="o">{</span>
				<span class="k">continue</span><span class="o">;</span>
			<span class="o">}</span>

			<span class="n">function</span><span class="o">.</span><span class="na">setReturnType</span><span class="o">(</span><span class="n">signature</span><span class="o">.</span><span class="na">getReturnType</span><span class="o">(),</span> <span class="nc">SourceType</span><span class="o">.</span><span class="na">IMPORTED</span><span class="o">);</span>
			<span class="nc">List</span><span class="o">&lt;</span><span class="nc">ParameterImpl</span><span class="o">&gt;</span> <span class="n">parameters</span> <span class="o">=</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="n">signature</span><span class="o">.</span><span class="na">getArguments</span><span class="o">()).</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="n">parameter</span> <span class="o">-&gt;</span> <span class="o">{</span>
				<span class="k">try</span> <span class="o">{</span>
					<span class="k">return</span> <span class="k">new</span> <span class="nf">ParameterImpl</span><span class="o">(</span><span class="n">parameter</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">parameter</span><span class="o">.</span><span class="na">getDataType</span><span class="o">(),</span> <span class="n">currentProgram</span><span class="o">);</span>
				<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
					<span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="n">ex</span><span class="o">);</span>
				<span class="o">}</span>
			<span class="o">}).</span><span class="na">toList</span><span class="o">();</span>
			<span class="n">function</span><span class="o">.</span><span class="na">replaceParameters</span><span class="o">(</span><span class="n">parameters</span><span class="o">,</span> <span class="nc">Function</span><span class="o">.</span><span class="na">FunctionUpdateType</span><span class="o">.</span><span class="na">DYNAMIC_STORAGE_ALL_PARAMS</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="nc">SourceType</span><span class="o">.</span><span class="na">IMPORTED</span><span class="o">);</span>
			<span class="n">function</span><span class="o">.</span><span class="na">setVarArgs</span><span class="o">(</span><span class="n">signature</span><span class="o">.</span><span class="na">hasVarArgs</span><span class="o">());</span>

			<span class="n">writer</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%s&gt; Processed function %s"</span><span class="o">,</span> <span class="n">function</span><span class="o">.</span><span class="na">getEntryPoint</span><span class="o">(),</span> <span class="n">function</span><span class="o">.</span><span class="na">getName</span><span class="o">()));</span>
		<span class="o">}</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="nc">DataTypeManager</span> <span class="nf">getDataTypeManager</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">DataTypeArchiveService</span> <span class="n">dataTypeArchiveService</span> <span class="o">=</span> <span class="n">state</span><span class="o">.</span><span class="na">getTool</span><span class="o">().</span><span class="na">getService</span><span class="o">(</span><span class="nc">DataTypeArchiveService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
		<span class="k">for</span> <span class="o">(</span><span class="nc">DataTypeManager</span> <span class="n">dtm</span> <span class="o">:</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="n">dataTypeArchiveService</span><span class="o">.</span><span class="na">getDataTypeManagers</span><span class="o">()))</span> <span class="o">{</span>
			<span class="k">if</span> <span class="o">(</span><span class="n">dtm</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">name</span><span class="o">))</span> <span class="o">{</span>
				<span class="k">return</span> <span class="n">dtm</span><span class="o">;</span>
			<span class="o">}</span>
		<span class="o">}</span>

		<span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Couldn't find data type archive "</span> <span class="o">+</span> <span class="n">name</span><span class="o">);</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">FunctionSignature</span><span class="o">&gt;</span> <span class="nf">getFunctionSignatures</span><span class="o">(</span><span class="nc">DataTypeManager</span> <span class="n">dtm</span><span class="o">,</span> <span class="nc">Pattern</span> <span class="n">pattern</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">FunctionSignature</span><span class="o">&gt;</span> <span class="n">signatures</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">DataType</span><span class="o">&gt;</span> <span class="n">dataTypes</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
		<span class="n">dtm</span><span class="o">.</span><span class="na">getAllDataTypes</span><span class="o">(</span><span class="n">dataTypes</span><span class="o">);</span>

		<span class="k">for</span> <span class="o">(</span><span class="nc">DataType</span> <span class="n">dataType</span> <span class="o">:</span> <span class="n">dataTypes</span><span class="o">)</span> <span class="o">{</span>
			<span class="k">if</span> <span class="o">(!(</span><span class="n">dataType</span> <span class="k">instanceof</span> <span class="nc">FunctionSignature</span><span class="o">))</span> <span class="o">{</span>
				<span class="k">continue</span><span class="o">;</span>
			<span class="o">}</span>

			<span class="nc">FunctionSignature</span> <span class="n">signature</span> <span class="o">=</span> <span class="o">(</span><span class="nc">FunctionSignature</span><span class="o">)</span> <span class="n">dataType</span><span class="o">;</span>
			<span class="k">if</span> <span class="o">(</span><span class="n">pattern</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="n">dataType</span><span class="o">.</span><span class="na">getDataTypePath</span><span class="o">().</span><span class="na">toString</span><span class="o">()).</span><span class="na">matches</span><span class="o">())</span> <span class="o">{</span>
				<span class="n">signatures</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">signature</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">signature</span><span class="o">);</span>
			<span class="o">}</span>
		<span class="o">}</span>

		<span class="k">return</span> <span class="n">signatures</span><span class="o">;</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>This script matches the names of functions to the signatures held inside data type archives and applies the signature to the function if a match is found.
We recover over 350 untyped function signatures this way, from the functions we’ve reconstructed from raw labels.</p>

<h2 id="looking-for-correlations">Looking for correlations</h2>

<p>Now that we have cleaned up our placeholder executable as much as possible, it’s time to Version Track it for all its worth.
Since the built-in Ghidra correlators can’t match anything, we need to think outside the box.
Luckily for us, there are some patterns in the metadata that we can take advantage of.</p>

<p>Assuming the source and destination programs are reasonably close, it is likely that they share global variables and functions with identical sizes, at least to some extent.
What is also likely is that they share the same <em>ordering</em> of these global variables and functions, especially within the same original object file.
Therefore, if we were to identify an identical pair of spots between the two programs, it’s likely that the data before and after it is also identical.</p>

<p>So we need to identify runs of similarly-sized stuff between our two programs in order to correlate them.
I could fork Ghidra and implement new types of correlators cleanly, or I could also just lazily script my way out, which sounds far more expedient for an experimental approach.</p>

<h3 id="shaping-up-correlations">Shaping up correlations</h3>

<p>Functions are fairly obvious and sizable, so if their sizes have a lot of variation then the patterns should stand out.
Since we don’t really know where we’re going with this stuff, we can start with a prototype that merely outputs the best match found:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//Correlates the shapes of functions</span>
<span class="c1">//@author Jean-Baptiste Boric</span>
<span class="c1">//@category Version Tracking</span>
<span class="c1">//@keybinding</span>
<span class="c1">//@menupath</span>
<span class="c1">//@toolbar</span>

<span class="kn">import</span> <span class="nn">java.util.ArrayList</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.HashMap</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.stream.Collectors</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">ghidra.app.services.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.app.util.bin.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.feature.vt.GhidraVersionTrackingScript</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.address.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.block.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.ISF.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.protorules.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.listing.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.mem.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.pcode.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.reloc.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.scalar.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.symbol.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.util.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.util.exception.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MultipleFunctionShapeCorrelator</span> <span class="kd">extends</span> <span class="nc">GhidraVersionTrackingScript</span> <span class="o">{</span>
	<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">CONVOLUTION_RANGE</span> <span class="o">=</span> <span class="mi">7</span><span class="o">;</span>

	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="n">openVersionTrackingSession</span><span class="o">(</span><span class="s">"/VT_PSX.SYM.elf_PSX.EXE"</span><span class="o">);</span>
		<span class="n">matchFunctions</span><span class="o">(</span><span class="n">getFunctionsOf</span><span class="o">(</span><span class="n">sourceProgram</span><span class="o">),</span> <span class="n">getFunctionsOf</span><span class="o">(</span><span class="n">destinationProgram</span><span class="o">));</span>
	<span class="o">}</span>

	<span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Function</span><span class="o">&gt;</span> <span class="nf">getFunctionsOf</span><span class="o">(</span><span class="nc">Program</span> <span class="n">program</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Function</span><span class="o">&gt;</span> <span class="n">functions</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span>
		<span class="nc">FunctionManager</span> <span class="n">functionManager</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="na">getFunctionManager</span><span class="o">();</span>
		<span class="n">functionManager</span><span class="o">.</span><span class="na">getFunctions</span><span class="o">(</span><span class="kc">true</span><span class="o">).</span><span class="na">forEachRemaining</span><span class="o">(</span><span class="nl">functions:</span><span class="o">:</span><span class="n">add</span><span class="o">);</span>
		<span class="k">return</span> <span class="n">functions</span><span class="o">;</span>
	<span class="o">}</span>

	<span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="nf">getFunctionSizes</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Function</span><span class="o">&gt;</span> <span class="n">functions</span><span class="o">)</span> <span class="o">{</span>
		<span class="k">return</span> <span class="n">functions</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="n">f</span> <span class="o">-&gt;</span> <span class="o">(</span><span class="kt">int</span><span class="o">)</span><span class="n">f</span><span class="o">.</span><span class="na">getBody</span><span class="o">().</span><span class="na">getNumAddresses</span><span class="o">()).</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>
	<span class="o">}</span>

	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">matchFunctions</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Function</span><span class="o">&gt;</span> <span class="n">sourceFunctions</span><span class="o">,</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Function</span><span class="o">&gt;</span> <span class="n">destinationFunctions</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">sourceSizes</span> <span class="o">=</span> <span class="n">getFunctionSizes</span><span class="o">(</span><span class="n">sourceFunctions</span><span class="o">);</span>
		<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">destinationSizes</span> <span class="o">=</span> <span class="n">getFunctionSizes</span><span class="o">(</span><span class="n">destinationFunctions</span><span class="o">);</span>

		<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">sourceSizes</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
			<span class="nc">Function</span> <span class="n">sourceFunction</span> <span class="o">=</span> <span class="n">sourceFunctions</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
			<span class="kt">int</span> <span class="n">bestMatch</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
			<span class="kt">float</span> <span class="n">bestScore</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
			<span class="kt">float</span> <span class="n">secondBestScore</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>

			<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">destinationSizes</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">j</span><span class="o">++)</span> <span class="o">{</span>
				<span class="kt">float</span> <span class="n">score</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
				<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="o">-</span><span class="no">CONVOLUTION_RANGE</span><span class="o">;</span> <span class="n">k</span> <span class="o">&lt;=</span> <span class="no">CONVOLUTION_RANGE</span><span class="o">;</span> <span class="n">k</span><span class="o">++)</span> <span class="o">{</span>
					<span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="n">sourceSizes</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">floorMod</span><span class="o">(</span><span class="n">i</span><span class="o">+</span><span class="n">k</span><span class="o">,</span> <span class="n">sourceSizes</span><span class="o">.</span><span class="na">size</span><span class="o">()));</span>
					<span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">destinationSizes</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">floorMod</span><span class="o">(</span><span class="n">j</span><span class="o">+</span><span class="n">k</span><span class="o">,</span> <span class="n">destinationSizes</span><span class="o">.</span><span class="na">size</span><span class="o">()));</span>
					<span class="n">score</span> <span class="o">*=</span> <span class="mi">1</span> <span class="o">-</span> <span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">a</span> <span class="o">-</span> <span class="n">b</span><span class="o">)</span> <span class="o">/</span> <span class="o">(</span><span class="kt">float</span><span class="o">)(</span><span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="o">))</span> <span class="o">*</span> <span class="nc">Math</span><span class="o">.</span><span class="na">exp</span><span class="o">(-</span><span class="nc">Math</span><span class="o">.</span><span class="na">abs</span><span class="o">(</span><span class="n">k</span><span class="o">));</span>
				<span class="o">}</span>

				<span class="k">if</span> <span class="o">(</span><span class="n">score</span> <span class="o">&gt;</span> <span class="n">bestScore</span><span class="o">)</span> <span class="o">{</span>
					<span class="n">secondBestScore</span> <span class="o">=</span> <span class="n">bestScore</span><span class="o">;</span>
					<span class="n">bestScore</span> <span class="o">=</span> <span class="n">score</span><span class="o">;</span>
					<span class="n">bestMatch</span> <span class="o">=</span> <span class="n">j</span><span class="o">;</span>
				<span class="o">}</span>
			<span class="o">}</span>

			<span class="nc">Function</span> <span class="n">destinationFunction</span> <span class="o">=</span> <span class="n">destinationFunctions</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">bestMatch</span><span class="o">);</span>
			<span class="kt">float</span> <span class="n">confidence</span> <span class="o">=</span> <span class="o">(</span><span class="n">bestScore</span> <span class="o">-</span> <span class="n">secondBestScore</span><span class="o">)</span> <span class="o">/</span> <span class="n">bestScore</span><span class="o">;</span>
			<span class="n">writer</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"[%30s; %30s] best score %1.3f second best %1.3f confidence %1.3f"</span><span class="o">,</span> <span class="n">sourceFunction</span><span class="o">,</span> <span class="n">destinationFunction</span><span class="o">,</span> <span class="n">bestScore</span><span class="o">,</span> <span class="n">secondBestScore</span><span class="o">,</span> <span class="n">confidence</span><span class="o">));</span>
		<span class="o">}</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The script treats the function sizes of both programs as vectors, then tries to find the best pair match by scoring between 0 and 1 how closely the two functions and their neighbors fit together.
If we have a score of 1, then the match is perfect: both the functions and their neighbors have the same size.
We also compute a confidence score based on the best and second-best scores, the wider the gap between the two the better.</p>

<div class="box box-warning">
  <p>There’s probably an elegant way to modelize this properly and formally, with fancy-pants words like <em>cross-correlation</em>, <em>Gaussian</em> or whatever.
Alas, I’m an engineer, not a mathematician, so I’m just winging the scoring with a made-up formula that looks pretty.</p>

  <p>As long as the correct match often has the best score, I don’t care if I’m not leveraging this data set to the fullest extent possible.</p>
</div>

<p>The script outputs this kind of data on the console:</p>

<table>
  <thead>
    <tr>
      <th>Source function</th>
      <th>Destination function</th>
      <th>Score</th>
      <th>Confidence</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">CreateHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_800247fc</code></td>
      <td>0.770</td>
      <td>0.107</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">KillAllHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_800249b8</code></td>
      <td>0.913</td>
      <td>0.249</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">KillHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_80024a00</code></td>
      <td>0.968</td>
      <td>0.365</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ControlAllHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_80024af4</code></td>
      <td>0.988</td>
      <td>0.297</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ControlHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_80024b68</code></td>
      <td>0.996</td>
      <td>0.387</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">DefaultActionHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_80024de0</code></td>
      <td>0.998</td>
      <td>0.555</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">GetHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_800256f0</code></td>
      <td>1.000</td>
      <td>0.457</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MoveHumanoid</code></td>
      <td><code class="language-plaintext highlighter-rouge">FUN_80025764</code></td>
      <td>1.000</td>
      <td>0.291</td>
    </tr>
  </tbody>
</table>

<p>Given the lack of mathematical soundness of the formula, we can’t conclude much just by looking at the raw score and confidence numbers.
However, we can take a peek at the destination functions and judge if the source function is a match.
For this subset, after manual inspection every pair turns out to be correct, so it seems like this empirical method has promising results.</p>

<h3 id="poors-man-matches">Poor’s man matches</h3>

<p>We have the data that pairs functions from the source program to the destination program, but writing text on the output stream doesn’t put it inside Ghidra’s database.
Luckily, we can hijack the manual match set present inside any Version Tracking session for our needs.</p>

<p>It’s a bit tricky to add a match in an idempotent manner, to ensure that running the script multiple times doesn’t insert duplicate matches, but not impossible:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">addMatch</span><span class="o">(</span><span class="nc">AddressSetView</span> <span class="n">source</span><span class="o">,</span> <span class="nc">AddressSetView</span> <span class="n">destination</span><span class="o">,</span> <span class="kt">float</span> <span class="n">similarity</span><span class="o">,</span> <span class="kt">float</span> <span class="n">confidence</span><span class="o">,</span> <span class="nc">VTAssociationType</span> <span class="n">associationType</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">VTMatchSet</span> <span class="n">manualMatchSet</span> <span class="o">=</span> <span class="n">vtSession</span><span class="o">.</span><span class="na">getManualMatchSet</span><span class="o">();</span>
    <span class="nc">VTMatchInfo</span> <span class="n">matchInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VTMatchInfo</span><span class="o">(</span><span class="n">manualMatchSet</span><span class="o">);</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setSourceAddress</span><span class="o">(</span><span class="n">source</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">());</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setDestinationAddress</span><span class="o">(</span><span class="n">destination</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">());</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setSourceLength</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span> <span class="n">source</span><span class="o">.</span><span class="na">getNumAddresses</span><span class="o">());</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setDestinationLength</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span> <span class="n">destination</span><span class="o">.</span><span class="na">getNumAddresses</span><span class="o">());</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setSimilarityScore</span><span class="o">(</span><span class="k">new</span> <span class="nc">VTScore</span><span class="o">(</span><span class="n">similarity</span><span class="o">));</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setConfidenceScore</span><span class="o">(</span><span class="k">new</span> <span class="nc">VTScore</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">pow</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="n">confidence</span><span class="o">)));</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setAssociationType</span><span class="o">(</span><span class="n">associationType</span><span class="o">);</span>
    <span class="n">matchInfo</span><span class="o">.</span><span class="na">setTag</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>

    <span class="k">if</span> <span class="o">(!</span><span class="n">manualMatchSet</span><span class="o">.</span><span class="na">getMatches</span><span class="o">().</span><span class="na">stream</span><span class="o">().</span><span class="na">anyMatch</span><span class="o">(</span><span class="n">match</span> <span class="o">-&gt;</span> 
        <span class="n">match</span><span class="o">.</span><span class="na">getSourceAddress</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">matchInfo</span><span class="o">.</span><span class="na">getSourceAddress</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
        <span class="n">match</span><span class="o">.</span><span class="na">getDestinationAddress</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">matchInfo</span><span class="o">.</span><span class="na">getDestinationAddress</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
        <span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getSourceLength</span><span class="o">()</span> <span class="o">==</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getSourceLength</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
        <span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getDestinationLength</span><span class="o">()</span> <span class="o">==</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getDestinationLength</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
        <span class="nc">SystemUtilities</span><span class="o">.</span><span class="na">isEqual</span><span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getSimilarityScore</span><span class="o">(),</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getSimilarityScore</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
        <span class="nc">SystemUtilities</span><span class="o">.</span><span class="na">isEqual</span><span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getConfidenceScore</span><span class="o">(),</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getConfidenceScore</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
        <span class="nc">SystemUtilities</span><span class="o">.</span><span class="na">isEqual</span><span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getAssociation</span><span class="o">().</span><span class="na">getType</span><span class="o">(),</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getAssociationType</span><span class="o">())</span>
    <span class="o">))</span> <span class="o">{</span>
        <span class="n">manualMatchSet</span><span class="o">.</span><span class="na">addMatch</span><span class="o">(</span><span class="n">matchInfo</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>After replacing the console logging with a call to this method, we get finally what we were after: a Version Tracking session with a whole bunch of matches.</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-9/version-tracking-function-matches.png" />
                <figcaption>Figure 2: Version tracking session with function matches.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>Not bad for a source program that has no bytes.</p>

<h3 id="what-about-data">What about data?</h3>

<p>In theory, the same operation should be doable with data, but I do not think this is a viable option for now.
The function correlator is already having quite a lot of trouble matching objects that are quite distinctive: simply put, I think there’s too little overlapping information between the source and destination program to make it work.</p>

<p>That being said, it’s still possible to match up runs of identical data between <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code> and <code class="language-plaintext highlighter-rouge">PSX.EXE</code> by hand, so the debugging information can still be leveraged there.
Just make a selection in the source and destination programs, invoke the <code class="language-plaintext highlighter-rouge">Create Manual Match From Tool</code> action and…</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-9/create-manual-match-from-tool-functions-required.png" />
                <figcaption>Figure 3: "The current location must be inside of a function in both the source and destination programs".</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>…oh come on, I’m not even doing something that’s an offense to Cthulhu right now, I’m just trying to create a manual match with <em>data</em>!</p>

<div class="box box-information">
  <p>I think this wasn’t implemented before because the built-in correlators probably do a good enough job with bytes and references to not require the creation of manual matches.
Too bad my source program doesn’t have bytes or references.</p>
</div>

<p>I <em>almost</em> decided to modify Ghidra’s manual match action in anger, but relented after remembering my previous experience maintaining my own Ghidra fork.
Instead, I’ll script this one out and maybe worry about making a proper pull request to Ghidra’s source code later:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//Create a manual match for data</span>
<span class="c1">//@author Jean-Baptiste Boric</span>
<span class="c1">//@category Version Tracking</span>
<span class="c1">//@keybinding</span>
<span class="c1">//@menupath</span>
<span class="c1">//@toolbar</span>

<span class="kn">import</span> <span class="nn">java.lang.reflect.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.ArrayList</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.HashMap</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.stream.Collectors</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">ghidra.app.services.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.app.util.bin.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.feature.vt.api.main.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.feature.vt.gui.plugin.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.feature.vt.GhidraVersionTrackingScript</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.framework.plugintool.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.address.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.block.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.data.ISF.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.lang.protorules.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.listing.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.mem.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.pcode.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.reloc.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.scalar.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.symbol.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.model.util.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.program.util.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.util.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">ghidra.util.exception.*</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CreateManualMatchData</span> <span class="kd">extends</span> <span class="nc">GhidraVersionTrackingScript</span> <span class="o">{</span>
	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
		<span class="n">openVersionTrackingSession</span><span class="o">(</span><span class="s">"/Miscellanea/VT_PSX.SYM.elf_PSX.EXE (manual matches)"</span><span class="o">);</span>
		<span class="nc">PluginTool</span> <span class="n">manager</span> <span class="o">=</span> <span class="n">state</span><span class="o">.</span><span class="na">getTool</span><span class="o">();</span>
		<span class="nc">Plugin</span> <span class="n">subordinate</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="na">getManagedPlugins</span><span class="o">().</span><span class="na">stream</span><span class="o">().</span><span class="na">filter</span><span class="o">(</span><span class="n">p</span> <span class="o">-&gt;</span> <span class="n">p</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getSimpleName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="s">"VersionTrackingSubordinatePluginX"</span><span class="o">)).</span><span class="na">findFirst</span><span class="o">().</span><span class="na">get</span><span class="o">();</span>
		<span class="nc">Field</span> <span class="n">subToolManagerField</span> <span class="o">=</span> <span class="n">subordinate</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getDeclaredField</span><span class="o">(</span><span class="s">"this$0"</span><span class="o">);</span>
		<span class="n">subToolManagerField</span><span class="o">.</span><span class="na">setAccessible</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
		<span class="nc">VTSubToolManager</span> <span class="n">subToolManager</span> <span class="o">=</span> <span class="o">(</span><span class="nc">VTSubToolManager</span><span class="o">)</span> <span class="n">subToolManagerField</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">subordinate</span><span class="o">);</span>
		<span class="nc">Method</span> <span class="n">sourceMethod</span> <span class="o">=</span> <span class="n">subToolManager</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getDeclaredMethod</span><span class="o">(</span><span class="s">"getSourceLocation"</span><span class="o">);</span>
		<span class="nc">Method</span> <span class="n">destinationMethod</span> <span class="o">=</span> <span class="n">subToolManager</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getDeclaredMethod</span><span class="o">(</span><span class="s">"getDestinationLocation"</span><span class="o">);</span>
		<span class="n">sourceMethod</span><span class="o">.</span><span class="na">setAccessible</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
		<span class="n">destinationMethod</span><span class="o">.</span><span class="na">setAccessible</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
		<span class="nc">ProgramLocation</span> <span class="n">sourceLocation</span> <span class="o">=</span> <span class="o">(</span><span class="nc">ProgramLocation</span><span class="o">)</span> <span class="n">sourceMethod</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">subToolManager</span><span class="o">);</span>
		<span class="nc">ProgramLocation</span> <span class="n">destinationLocation</span> <span class="o">=</span> <span class="o">(</span><span class="nc">ProgramLocation</span><span class="o">)</span> <span class="n">destinationMethod</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="n">subToolManager</span><span class="o">);</span>
		<span class="nc">CodeUnit</span> <span class="n">sourceCodeUnit</span> <span class="o">=</span> <span class="n">sourceProgram</span><span class="o">.</span><span class="na">getListing</span><span class="o">().</span><span class="na">getCodeUnitContaining</span><span class="o">(</span><span class="n">sourceLocation</span><span class="o">.</span><span class="na">getAddress</span><span class="o">());</span>
		<span class="nc">CodeUnit</span> <span class="n">destinationCodeUnit</span> <span class="o">=</span> <span class="n">destinationProgram</span><span class="o">.</span><span class="na">getListing</span><span class="o">().</span><span class="na">getCodeUnitContaining</span><span class="o">(</span><span class="n">destinationLocation</span><span class="o">.</span><span class="na">getAddress</span><span class="o">());</span>
		<span class="nc">AddressSetView</span> <span class="n">source</span> <span class="o">=</span> <span class="n">sourceProgram</span><span class="o">.</span><span class="na">getAddressFactory</span><span class="o">().</span><span class="na">getAddressSet</span><span class="o">(</span><span class="n">sourceCodeUnit</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">(),</span> <span class="n">sourceCodeUnit</span><span class="o">.</span><span class="na">getMaxAddress</span><span class="o">());</span>
		<span class="nc">AddressSetView</span> <span class="n">destination</span> <span class="o">=</span> <span class="n">destinationProgram</span><span class="o">.</span><span class="na">getAddressFactory</span><span class="o">().</span><span class="na">getAddressSet</span><span class="o">(</span><span class="n">destinationCodeUnit</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">(),</span> <span class="n">destinationCodeUnit</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">().</span><span class="na">add</span><span class="o">(</span><span class="n">source</span><span class="o">.</span><span class="na">getNumAddresses</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">));</span>

		<span class="k">if</span> <span class="o">(</span><span class="n">sourceLocation</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">destinationLocation</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="nc">Msg</span><span class="o">.</span><span class="na">showInfo</span><span class="o">(</span><span class="n">getClass</span><span class="o">(),</span> <span class="kc">null</span><span class="o">,</span> <span class="s">"Cannot Create Match"</span><span class="o">,</span> <span class="s">"There must be a source and destination selection"</span><span class="o">);</span>
			<span class="k">return</span><span class="o">;</span>
		<span class="o">}</span>

		<span class="n">addMatch</span><span class="o">(</span><span class="n">source</span><span class="o">,</span> <span class="n">destination</span><span class="o">,</span> <span class="mf">1.0f</span><span class="o">,</span> <span class="mf">0.0f</span><span class="o">,</span> <span class="nc">VTAssociationType</span><span class="o">.</span><span class="na">DATA</span><span class="o">);</span>
	<span class="o">}</span>

	<span class="kd">private</span> <span class="kt">void</span> <span class="nf">addMatch</span><span class="o">(</span><span class="nc">AddressSetView</span> <span class="n">source</span><span class="o">,</span> <span class="nc">AddressSetView</span> <span class="n">destination</span><span class="o">,</span> <span class="kt">float</span> <span class="n">similarity</span><span class="o">,</span> <span class="kt">float</span> <span class="n">confidence</span><span class="o">,</span> <span class="nc">VTAssociationType</span> <span class="n">associationType</span><span class="o">)</span> <span class="o">{</span>
		<span class="nc">VTMatchSet</span> <span class="n">manualMatchSet</span> <span class="o">=</span> <span class="n">vtSession</span><span class="o">.</span><span class="na">getManualMatchSet</span><span class="o">();</span>
		<span class="nc">VTMatchInfo</span> <span class="n">matchInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VTMatchInfo</span><span class="o">(</span><span class="n">manualMatchSet</span><span class="o">);</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setSourceAddress</span><span class="o">(</span><span class="n">source</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">());</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setDestinationAddress</span><span class="o">(</span><span class="n">destination</span><span class="o">.</span><span class="na">getMinAddress</span><span class="o">());</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setSourceLength</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span> <span class="n">source</span><span class="o">.</span><span class="na">getNumAddresses</span><span class="o">());</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setDestinationLength</span><span class="o">((</span><span class="kt">int</span><span class="o">)</span> <span class="n">destination</span><span class="o">.</span><span class="na">getNumAddresses</span><span class="o">());</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setSimilarityScore</span><span class="o">(</span><span class="k">new</span> <span class="nc">VTScore</span><span class="o">(</span><span class="n">similarity</span><span class="o">));</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setConfidenceScore</span><span class="o">(</span><span class="k">new</span> <span class="nc">VTScore</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">pow</span><span class="o">(</span><span class="mi">10</span><span class="o">,</span> <span class="n">confidence</span><span class="o">)));</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setAssociationType</span><span class="o">(</span><span class="n">associationType</span><span class="o">);</span>
		<span class="n">matchInfo</span><span class="o">.</span><span class="na">setTag</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>

		<span class="k">if</span> <span class="o">(!</span><span class="n">manualMatchSet</span><span class="o">.</span><span class="na">getMatches</span><span class="o">().</span><span class="na">stream</span><span class="o">().</span><span class="na">anyMatch</span><span class="o">(</span><span class="n">match</span> <span class="o">-&gt;</span> 
			<span class="n">match</span><span class="o">.</span><span class="na">getSourceAddress</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">matchInfo</span><span class="o">.</span><span class="na">getSourceAddress</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
			<span class="n">match</span><span class="o">.</span><span class="na">getDestinationAddress</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">matchInfo</span><span class="o">.</span><span class="na">getDestinationAddress</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
			<span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getSourceLength</span><span class="o">()</span> <span class="o">==</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getSourceLength</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
			<span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getDestinationLength</span><span class="o">()</span> <span class="o">==</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getDestinationLength</span><span class="o">())</span> <span class="o">&amp;&amp;</span>
			<span class="nc">SystemUtilities</span><span class="o">.</span><span class="na">isEqual</span><span class="o">(</span><span class="n">match</span><span class="o">.</span><span class="na">getAssociation</span><span class="o">().</span><span class="na">getType</span><span class="o">(),</span> <span class="n">matchInfo</span><span class="o">.</span><span class="na">getAssociationType</span><span class="o">())</span>
		<span class="o">))</span> <span class="o">{</span>
			<span class="n">manualMatchSet</span><span class="o">.</span><span class="na">addMatch</span><span class="o">(</span><span class="n">matchInfo</span><span class="o">);</span>
		<span class="o">}</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="box box-warning">
  <p>By the sheer amount of Java reflection going on in this script, you can probably tell I was getting rather pissed off trying to accomplish this task.
I’m scripting the <em>crap</em> out of Ghidra here, that’s not how you’re supposed to do it.</p>
</div>

<p>Anyways, this script works similarly to the <code class="language-plaintext highlighter-rouge">Create Manual Match From Tool</code> action.
Click a spot on the source and destination programs, it will create a match from the source code unit to an equally-sized region on the destination.</p>

<h3 id="end-result">End result</h3>

<p>The home-built function correlator worked very well for game code covered by debugging symbols, but it struggles in the following situations:</p>
<ul>
  <li>Missing static functions in the source dataset, which throws off the sequence of function sizes to compare ;</li>
  <li>Long runs of identically-sized functions, which doesn’t provide any entropy within the correlation window.</li>
</ul>

<p>Creating manual matches for the misidentified functions and the bulk of the data sections took a bit of time, but at the end the destination program is fairly thoroughly annotated, at least for the game and public SDK parts.
The fact that the source and destination programs were very close made the process reasonably straightforward, but some of the matches did require a lot of inferring to figure out.</p>

<div class="box box-information">
  <p>Had I attempted to version track directly onto a more distantly related artifact, it is likely that the source program being a placeholder with no initialized bytes or references would’ve made identifying matches <strong>extremely</strong> difficult.</p>
</div>

<h2 id="conclusion">Conclusion</h2>

<p>Despite the built-in Ghidra correlators being unable to work with <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code>, our placeholder program created in the last part, we’ve managed to make matches inside Version Tracking against <code class="language-plaintext highlighter-rouge">PSX.EXE</code> through out-of-the-box thinking and generous (ab)use of scripting, rescuing the valuable debugging data from a lost artifact onto a real program.</p>

<div style="display: grid; grid-template-columns: 1fr 1fr; margin-top: 1ch; margin-bottom: 1ch;">
<div style="padding-right: 15px;">


<a class="prev" href="/tenchu1/2024/04/01/part-8.html">&laquo; Decompiling Tenchu: Stealth Assassins part 8: make-believe executable for PSX.SYM</a>

</div>
<div style="padding-left: 15px; text-align: right;">


<a href="/tenchu1/2024/05/15/part-10.html">Decompiling Tenchu: Stealth Assassins part 10: potpourri status update &raquo;</a>

</div>
</div>]]></content><author><name>Jean-Baptiste Boric</name></author><category term="tenchu1" /><summary type="html"><![CDATA[Previously in this series of articles, we’ve discovered that the debugging symbols file PSX.SYM doesn’t match the PSX.EXE artifact we have on hand. Undaunted, we’ve created a placeholder program PSX.SYM.elf that matches the layout of PSX.SYM and then loaded the debug data on top of it inside Ghidra. In this part, we’ll complete the rescue of the debugging data by migrating it to PSX.EXE, a tangible executable.]]></summary></entry><entry><title type="html">Decompiling Tenchu: Stealth Assassins part 8: make-believe executable for PSX.SYM</title><link href="https://boricj.net/tenchu1/2024/04/01/part-8.html" rel="alternate" type="text/html" title="Decompiling Tenchu: Stealth Assassins part 8: make-believe executable for PSX.SYM" /><published>2024-04-01T02:00:00+02:00</published><updated>2024-04-01T02:00:00+02:00</updated><id>https://boricj.net/tenchu1/2024/04/01/part-8</id><content type="html" xml:base="https://boricj.net/tenchu1/2024/04/01/part-8.html"><![CDATA[<p><a href="/tenchu1/2024/03/25/part-7.html">Previously</a> in this <a href="/tenchu1/2024/02/05/introduction.html">series of articles</a>, we’ve analyzed some leftovers hidden inside the AFS archive of the demo version of Tenchu.
This time, we’ll continue with the main course: <code class="language-plaintext highlighter-rouge">PSX.EXE</code> and <code class="language-plaintext highlighter-rouge">PSX.SYM</code>, an early build of Tenchu’s main executable with its accompanying debugging information file.</p>

<h2 id="an-artifact-lost-in-time">An artifact lost in time</h2>

<p>Found hidden inside the AFS archive of the demo version of <em>Rittai Ninja Katsugeki Tenchu</em>, <code class="language-plaintext highlighter-rouge">PSX.EXE</code> raises a number of questions, particularly when it was created.
For reference, the timeline looks like this:</p>
<ul>
  <li>Acquire Corp. was founded on December 6th, 1994 ;</li>
  <li>The <code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\CD.CCS</code> file inside its AFS archive contains the date September 5th, 1997 ;</li>
  <li>The demo release date for <em>Rittai Ninja Katsugeki Tenchu</em> was October 26th, 1997 ;</li>
  <li>The original release date for <em>Rittai Ninja Katsugeki Tenchu</em> was February 26th, 1998.</li>
</ul>

<div class="box box-warning">
  <p>Do not quote me on the dates, I did not rely on authoritative sources for them.</p>
</div>

<p>Looking at the artifact itself, <code class="language-plaintext highlighter-rouge">PSX.EXE</code> does contain strings for all 8 levels plus the training level as well as a <a href="https://en.wikipedia.org/wiki/Source_Code_Control_System">SCCS</a> string for <code class="language-plaintext highlighter-rouge">bios.c</code> dated March 28th, 1997, which means it could’ve been built inside a time span of approximately half a year until the demo release date.
It seems to fit the way the original JP releases were architectured, with one big executable instead of four separate executables as found on the NA release onward.</p>

<div class="box box-information">
  <p>From the NA release of <em>Tenchu: Stealth Assassins</em> onwards, a 4-byte binary-coded decimal date is stored at address <code class="language-plaintext highlighter-rouge">0x80010000</code> during initialization, but unfortunately that doesn’t apply to earlier versions.</p>
</div>

<p>It’s likely that it is the last built executable before the development team switched to the name <code class="language-plaintext highlighter-rouge">GAME.EXE</code> and it was simply left there, forgotten.
Merely substituting either <code class="language-plaintext highlighter-rouge">GAME.EXE</code> or <code class="language-plaintext highlighter-rouge">PAPX_900.29</code> on the demo version of <em>Rittai Ninja Katsugeki Tenchu</em> with <code class="language-plaintext highlighter-rouge">PSX.EXE</code> doesn’t seem to yield a working game.
At any rate, the really juicy bit isn’t <code class="language-plaintext highlighter-rouge">PSX.EXE</code> itself, but <code class="language-plaintext highlighter-rouge">PSX.SYM</code>.</p>

<h3 id="challenges-of-long-obsolete-proprietary-file-formats">Challenges of long-obsolete proprietary file formats</h3>

<p><code class="language-plaintext highlighter-rouge">PSX.SYM</code> contains debugging information in a proprietary <code class="language-plaintext highlighter-rouge">MND</code> format, specific to the Psy-Q toolchain.
There’s some <a href="https://problemkaputt.de/psxspx-cdrom-file-psyq-sym-files-debug-information.htm">reverse-engineered documentation</a>, a <a href="https://github.com/lab313ru/dumpsym_src/blob/master/main.c">decompilation for DUMPSYM.EXE</a> and <a href="https://www.beneaththewaves.net/Software/This_Dust_Remembers_What_It_Once_Was.html#Components">some tools</a> to manipulate them.</p>

<div class="box box-information">
  <p>The ghidra_psx_ldr extension I’ve been using for importing PS-EXE files used to support SYM files, but <a href="https://github.com/lab313ru/ghidra_psx_ldr/commit/3349cd12fd2d11272dee35bba4178fa3e0a9d4dc">that feature was removed in 2020</a>.</p>
</div>

<p>Anyway, since I have my own ideas about my workflow I started writing a Java script (note the space) for Ghidra to process this file and get all that sweet metadata inside the program database, but it soon became apparent that things weren’t that simple…</p>

<h3 id="no-plan-survives-first-contact-with-the-enemy">No plan survives first contact with the enemy</h3>

<p>Unfortunately, the addresses inside <code class="language-plaintext highlighter-rouge">PSX.SYM</code> do not match the contents of <code class="language-plaintext highlighter-rouge">PSX.EXE</code>.
This means that this SYM file was for another, most likely earlier version of <code class="language-plaintext highlighter-rouge">PSX.EXE</code>, which we don’t have.</p>

<p>There’s still tons of information in there, like file names, data types, function definitions and symbol names, we just can’t <em>directly</em> apply it to any artifact on hand.
However, the <em>order</em> of symbols inside <code class="language-plaintext highlighter-rouge">PSX.SYM</code> does appear to match <code class="language-plaintext highlighter-rouge">PSX.EXE</code>, at least to some extent, so it can’t be that far off.</p>

<p>That’s <em>way</em> too valuable to pass up.
Therefore, the new plan currently looks something like this:</p>
<ol>
  <li>Somehow load <code class="language-plaintext highlighter-rouge">PSX.SYM</code> and all its information as a program into Ghidra ;</li>
  <li>Version track from <code class="language-plaintext highlighter-rouge">PSX.SYM</code> to <code class="language-plaintext highlighter-rouge">PSX.EXE</code>, to get that data onto an executable we have ;</li>
  <li>Version track <em>again</em> from <code class="language-plaintext highlighter-rouge">PSX.EXE</code> to <em>Rittai Ninja Katsugeki Tenchu</em>’s <code class="language-plaintext highlighter-rouge">GAME.EXE</code> ;</li>
</ol>

<p>After this, the valuable debugging data from <code class="language-plaintext highlighter-rouge">PSX.SYM</code> will have been transferred to an executable that we can actually play with, both literally and figuratively.
From there, assuming there wasn’t too much transmission loss during all these migrations, I can use the annotated <em>Rittai Ninja Katsugeki Tenchu</em>’s <code class="language-plaintext highlighter-rouge">GAME.EXE</code> as a new starting point for my reverse-engineering effort.</p>

<p>But how do we load the debugging symbols into Ghidra in the first place if we do not have a program to begin with?</p>

<h2 id="make-believe-executables">Make-believe executables</h2>

<p>Okay, so we have a SYM file but not its PS-EXE executable.
That’s like having an egg’s shell and membrane but not its yolk and albumen.
Ghidra <a href="https://youtu.be/kx2xp7IQNSc?si=pbriv8Z1ppw0FsEP&amp;t=785">being a hungry beast</a>, it can’t feast on an egg (debugging symbols) devoid of its substance (executable)… but maybe if we pad the inside with tofu it won’t know any better?</p>

<p>Sketchy metaphors aside, without a program to load we can’t process debugging information inside Ghidra.
However, if we can scaffold a <em>placeholder</em> program, one that matches the <em>shape</em> if not the contents of the missing executable, then we can load it and then lay that debugging information on top of this fake program.</p>

<h3 id="mapping-out-sections">Mapping out sections</h3>

<p>For our placeholder executable, we’ll need a header, a section table and the section’s bytes.
We’ll pad the bytes with dummy data, but we still need the sections themselves.
Fortunately, the SYM file contains symbols that mark the start, end and size of each section:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ dumpsym ~/Games/'Rittai Ninja Katsugeki - Tenchu (Japan) (Demo)'/K:/WORK/CDIMAGE/PSX.SYM | grep -E '__[a-z]+(_org|_size)' | cut -f2,4 -d' ' | sort
$00000000 __ctors_size
$00000000 __dtors_size
$0000018e __sbss_size
$0000069c __sdata_size
$00003762 __rdata_size
$0000ca22 __data_size
$000777b0 __text_size
$0015aad8 __bss_size
$80010100 __ctors_org
$80010100 __ctors_orgend
$80010100 __dtors_org
$80010100 __dtors_orgend
$80010100 __rdata_org
$80013862 __rdata_orgend
$8001387c __text_org
$8008b02c __data_org
$8008b02c __text_orgend
$80097a4e __data_orgend
$80097a50 __sdata_org
$800980ec __sdata_orgend
$80098118 __sbss_org
$800982a6 __sbss_orgend
$800982c8 __bss_org
$801f2da0 __bss_orgend
</code></pre></div></div>

<p>With this, we can recreate the memory map for the sections of our placeholder:</p>

<table>
  <thead>
    <tr>
      <th>Section</th>
      <th>Start address</th>
      <th>End address</th>
      <th>Size</th>
      <th>Padded size</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.rdata</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x80010100</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x80013862</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x3762</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x377c</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.text</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x8001387c</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x8008b02c</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x777b0</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x777b0</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.data</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x8008b02c</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x80097a4e</code></td>
      <td><code class="language-plaintext highlighter-rouge">0xca22</code></td>
      <td><code class="language-plaintext highlighter-rouge">0xca24</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.sdata</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x80097a50</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x800980ec</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x69c</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x6c8</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.sbss</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x80098118</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x800982a6</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x18e</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x1b0</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.bss</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x800982c8</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x801f2da0</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x15aad8</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x15aad8</code></td>
    </tr>
  </tbody>
</table>

<div class="box box-information">
  <p>The size of the sections will be padded to the start of the next section because there are symbols wedged <em>in between</em> the sections, at least according to this file.</p>

  <p>Once more, the toolchains in the 90s didn’t <a href="/atari-jaguar-sdk/2024/01/01/part-4.html">give a damn</a>…</p>
</div>

<h3 id="a-whole-lot-of-nothing">A whole lot of nothing</h3>

<p>One extra bit of information we can observe is that the <code class="language-plaintext highlighter-rouge">start</code> symbol is located at address <code class="language-plaintext highlighter-rouge">0x80098098</code>, which will be our entrypoint.
After that, we can then repurpose <a href="/reverse-engineering/2023/07/24/part-8.html">some Jython code from my reverse-engineering series of articles</a> to hand-craft the placeholder executable.
Ghidra embeds a Jython standalone interpreter at <code class="language-plaintext highlighter-rouge">Ghidra/Features/Python/lib/jython-standalone-2.7.3.jar</code>, which we can use to run this script:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">struct</span>
<span class="kn">from</span> <span class="n">jarray</span> <span class="kn">import</span> <span class="n">zeros</span>

<span class="n">ELFCLASS32</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">ELFDATA2LSB</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">EV_CURRENT</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">ELFOSABI_SYSV</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">ELF_ET_REL</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">ELF_ET_EXEC</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">ELF_EM_MIPS</span> <span class="o">=</span> <span class="mi">8</span>
<span class="n">ELF_EV_CURRENT</span> <span class="o">=</span> <span class="mi">1</span>

<span class="n">ELF32LE_HDR</span> <span class="o">=</span> <span class="sh">"</span><span class="s">&lt;4c12BHHIIIIIHHHHHH</span><span class="sh">"</span>
<span class="n">ELF32LE_HDR_SIZE</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="nf">calcsize</span><span class="p">(</span><span class="n">ELF32LE_HDR</span><span class="p">)</span>

<span class="n">SHT_NULL</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">SHT_PROGBITS</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">SHT_SYMTAB</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">SHT_STRTAB</span> <span class="o">=</span> <span class="mi">3</span>
<span class="n">SHT_NOBITS</span> <span class="o">=</span> <span class="mi">8</span>

<span class="n">SHF_WRITE</span> <span class="o">=</span> <span class="mh">0x1</span>
<span class="n">SHF_ALLOC</span> <span class="o">=</span> <span class="mh">0x2</span>
<span class="n">SHF_EXECINSTR</span> <span class="o">=</span> <span class="mh">0x4</span>

<span class="n">ELF32LE_SHDR</span> <span class="o">=</span> <span class="sh">"</span><span class="s">&lt;IIIIIIIIII</span><span class="sh">"</span>
<span class="n">ELF32LE_SHDR_SIZE</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="nf">calcsize</span><span class="p">(</span><span class="n">ELF32LE_SHDR</span><span class="p">)</span>

<span class="c1"># Craft string tables
</span><span class="k">def</span> <span class="nf">craft_string_table</span><span class="p">(</span><span class="n">strings</span><span class="p">):</span>
    <span class="n">data</span> <span class="o">=</span> <span class="sh">""</span>
    <span class="n">offsets</span> <span class="o">=</span> <span class="nf">dict</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">string</span> <span class="ow">in</span> <span class="n">strings</span><span class="p">:</span>
        <span class="n">offsets</span><span class="p">[</span><span class="n">string</span><span class="p">]</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="n">data</span> <span class="o">+=</span> <span class="p">(</span><span class="n">string</span> <span class="o">+</span> <span class="sh">"</span><span class="se">\0</span><span class="sh">"</span><span class="p">).</span><span class="nf">encode</span><span class="p">(</span><span class="sh">"</span><span class="s">ascii</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">data</span><span class="p">,</span> <span class="n">offsets</span>

<span class="n">_shstrtab_bytes</span><span class="p">,</span> <span class="n">shstrtab</span> <span class="o">=</span> <span class="nf">craft_string_table</span><span class="p">([</span><span class="sh">""</span><span class="p">,</span> <span class="sh">"</span><span class="s">.shstrtab</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.rdata</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.text</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.data</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.sdata</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.sbss</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.bss</span><span class="sh">"</span><span class="p">])</span>

<span class="c1">#   Name            Type                Flags                       Address         Bytes               Link    Info    Alignment   Entry size
</span><span class="n">sections</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">(</span><span class="sh">""</span><span class="p">,</span>            <span class="n">SHT_NULL</span><span class="p">,</span>           <span class="mi">0</span><span class="p">,</span>                          <span class="mi">0</span><span class="p">,</span>              <span class="sh">""</span><span class="p">,</span>                 <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
    <span class="p">(</span><span class="sh">"</span><span class="s">.shstrtab</span><span class="sh">"</span><span class="p">,</span>   <span class="n">SHT_STRTAB</span><span class="p">,</span>         <span class="mi">0</span><span class="p">,</span>                          <span class="mi">0</span><span class="p">,</span>              <span class="n">_shstrtab_bytes</span><span class="p">,</span>    <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
    <span class="p">(</span><span class="sh">"</span><span class="s">.rdata</span><span class="sh">"</span><span class="p">,</span>	    <span class="n">SHT_NOBITS</span><span class="p">,</span>         <span class="n">SHF_ALLOC</span><span class="p">,</span>                  <span class="mh">0x80010100</span><span class="p">,</span>     <span class="mh">0x377c</span><span class="p">,</span>             <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
    <span class="p">(</span><span class="sh">"</span><span class="s">.text</span><span class="sh">"</span><span class="p">,</span>	    <span class="n">SHT_NOBITS</span><span class="p">,</span>         <span class="n">SHF_ALLOC</span><span class="o">|</span><span class="n">SHF_EXECINSTR</span><span class="p">,</span>    <span class="mh">0x8001387c</span><span class="p">,</span>     <span class="mh">0x777b0</span><span class="p">,</span>            <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
    <span class="p">(</span><span class="sh">"</span><span class="s">.data</span><span class="sh">"</span><span class="p">,</span>	    <span class="n">SHT_NOBITS</span><span class="p">,</span>         <span class="n">SHF_WRITE</span><span class="o">|</span><span class="n">SHF_ALLOC</span><span class="p">,</span>        <span class="mh">0x8008b02c</span><span class="p">,</span>     <span class="mh">0xca24</span><span class="p">,</span>             <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
    <span class="p">(</span><span class="sh">"</span><span class="s">.sdata</span><span class="sh">"</span><span class="p">,</span>	    <span class="n">SHT_NOBITS</span><span class="p">,</span>         <span class="n">SHF_WRITE</span><span class="o">|</span><span class="n">SHF_ALLOC</span><span class="p">,</span>        <span class="mh">0x80097a50</span><span class="p">,</span>     <span class="mh">0x6c8</span><span class="p">,</span>              <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
    <span class="p">(</span><span class="sh">"</span><span class="s">.sbss</span><span class="sh">"</span><span class="p">,</span>	    <span class="n">SHT_NOBITS</span><span class="p">,</span>         <span class="n">SHF_WRITE</span><span class="o">|</span><span class="n">SHF_ALLOC</span><span class="p">,</span>        <span class="mh">0x80098118</span><span class="p">,</span>     <span class="mh">0x1b0</span><span class="p">,</span>              <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
    <span class="p">(</span><span class="sh">"</span><span class="s">.bss</span><span class="sh">"</span><span class="p">,</span>	    <span class="n">SHT_NOBITS</span><span class="p">,</span>         <span class="n">SHF_WRITE</span><span class="o">|</span><span class="n">SHF_ALLOC</span><span class="p">,</span>        <span class="mh">0x800982c8</span><span class="p">,</span>     <span class="mh">0x15aad8</span><span class="p">,</span>           <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>      <span class="mi">0</span><span class="p">,</span>          <span class="mi">0</span><span class="p">),</span>
<span class="p">]</span>

<span class="n">file_offset</span> <span class="o">=</span> <span class="n">ELF32LE_HDR_SIZE</span> <span class="o">+</span> <span class="n">ELF32LE_SHDR_SIZE</span> <span class="o">*</span> <span class="nf">len</span><span class="p">(</span><span class="n">sections</span><span class="p">)</span>

<span class="n">elf_section_headers_bytes</span> <span class="o">=</span> <span class="sh">""</span>
<span class="k">for</span> <span class="n">section</span> <span class="ow">in</span> <span class="n">sections</span><span class="p">:</span>
    <span class="n">length</span> <span class="o">=</span> <span class="n">section</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="k">if</span> <span class="nf">type</span><span class="p">(</span><span class="n">section</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span> <span class="o">==</span> <span class="nb">int</span> <span class="k">else</span> <span class="nf">len</span><span class="p">(</span><span class="n">section</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span>
    <span class="n">elf_section_headers_bytes</span> <span class="o">+=</span> <span class="n">struct</span><span class="p">.</span><span class="nf">pack</span><span class="p">(</span><span class="n">ELF32LE_SHDR</span><span class="p">,</span>
        <span class="n">shstrtab</span><span class="p">[</span><span class="n">section</span><span class="p">[</span><span class="mi">0</span><span class="p">]],</span> <span class="n">section</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">section</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">section</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">file_offset</span><span class="p">,</span> <span class="n">length</span><span class="p">,</span> <span class="n">section</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span> <span class="n">section</span><span class="p">[</span><span class="mi">6</span><span class="p">],</span> <span class="n">section</span><span class="p">[</span><span class="mi">7</span><span class="p">],</span> <span class="n">section</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span>
    <span class="p">)</span>
    <span class="n">file_offset</span> <span class="o">+=</span> <span class="nf">len</span><span class="p">(</span><span class="n">section</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span> <span class="k">if</span> <span class="nf">type</span><span class="p">(</span><span class="n">section</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span> <span class="o">!=</span> <span class="nb">int</span> <span class="k">else</span> <span class="mi">0</span>

<span class="n">elf_header_bytes</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="nf">pack</span><span class="p">(</span><span class="n">ELF32LE_HDR</span><span class="p">,</span>
    <span class="sh">'</span><span class="se">\x7f</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">E</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">L</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">F</span><span class="sh">'</span><span class="p">,</span> <span class="n">ELFCLASS32</span><span class="p">,</span> <span class="n">ELFDATA2LSB</span><span class="p">,</span> <span class="n">EV_CURRENT</span><span class="p">,</span> <span class="n">ELFOSABI_SYSV</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">ELF_ET_EXEC</span><span class="p">,</span> <span class="n">ELF_EM_MIPS</span><span class="p">,</span> <span class="n">ELF_EV_CURRENT</span><span class="p">,</span> <span class="mh">0x80098098</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ELF32LE_HDR_SIZE</span><span class="p">,</span> <span class="mh">0x1000</span><span class="p">,</span> <span class="n">ELF32LE_HDR_SIZE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ELF32LE_SHDR_SIZE</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">sections</span><span class="p">),</span> <span class="mi">1</span>
<span class="p">)</span>
<span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="sh">"</span><span class="s">PSX.SYM.elf</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">w</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">fp</span><span class="p">:</span>
    <span class="n">fp</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">elf_header_bytes</span><span class="p">)</span>
    <span class="n">fp</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">elf_section_headers_bytes</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">section</span> <span class="ow">in</span> <span class="n">sections</span><span class="p">:</span>
        <span class="k">if</span> <span class="nf">type</span><span class="p">(</span><span class="n">section</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span> <span class="o">!=</span> <span class="nb">int</span><span class="p">:</span>
            <span class="n">fp</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="nf">bytearray</span><span class="p">(</span><span class="n">section</span><span class="p">[</span><span class="mi">4</span><span class="p">]))</span>
</code></pre></div></div>

<p>Since <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code> is a real ELF file that happens to be a fake executable, we can inspect it with the usual tools:</p>

<div class="tabs">
<input type="radio" name="file-psx-sym-elf" id="tab-header-readelf-psx-sym-elf" />
<label for="tab-header-readelf-psx-sym-elf">Header</label>
<div class="tab"><div class="language-plaintext highlighter-rouge">
<div class="highlight">
<pre class="highlight">
<code>$ readelf --wide --file-header PSX.SYM.elf
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           MIPS R3000
  Version:                           0x1
  Entry point address:               0x80098098
  Start of program headers:          0 (bytes into file)
  Start of section headers:          52 (bytes into file)
  Flags:                             0x1000, o32, mips1
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         8
  Section header string table index: 1</code>
</pre>
</div>
</div></div>
<input type="radio" name="file-psx-sym-elf" id="tab-sections-readelf-psx-sym-elf" checked="" />
<label for="tab-sections-readelf-psx-sym-elf">Sections</label>
<div class="tab"><div class="language-plaintext highlighter-rouge">
<div class="highlight">
<pre class="highlight">
<code>$ readelf --wide --section-headers PSX.SYM.elf
There are 8 section headers, starting at offset 0x34:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000174 000000 00      0   0  0
  [ 1] .shstrtab         STRTAB          00000000 000174 000030 00      0   0  0
  [ 2] .rdata            NOBITS          80010100 0001a4 003762 00   A  0   0  0
  [ 3] .text             NOBITS          8001387c 0001a4 0777b0 00  AX  0   0  0
  [ 4] .data             NOBITS          8008b02c 0001a4 00ca22 00  WA  0   0  0
  [ 5] .sdata            NOBITS          80097a50 0001a4 00069c 00  WA  0   0  0
  [ 6] .sbss             NOBITS          80098118 0001a4 00018e 00  WA  0   0  0
  [ 7] .bss              NOBITS          800982c8 0001a4 15aad8 00  WA  0   0  0
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)</code>
</pre>
</div>
</div></div>
</div>

<p>It’s a whole lot of nothing: this executable doesn’t have segments for the loader to process or even a single byte of initialized memory to its name.
QEMU won’t even let us try to run it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ chmod +x PSX.SYM.elf 
$ qemu-mipsel PSX.SYM.elf 
qemu-mipsel: PSX.SYM.elf: Invalid ELF image for this architecture
</code></pre></div></div>

<p>Despite all of that, Ghidra will happily import this so-called executable.
After all, all we need it to do is to act as a stand-in, so that the debugging data has a place to live.</p>

<h2 id="dressing-up-the-mannequin">Dressing up the mannequin</h2>

<p>Now that we have a placeholder executable, all we need to do is to layer that debugging data on top of this scaffolding.
It’s easier said than done because this is a deceptively tricky task:</p>
<ul>
  <li>The SYM file format is poorly documented and somewhat brain-dead by modern standards ;</li>
  <li>Mapping the data types to Ghidra’s own representation isn’t a straightforward conversion ;</li>
  <li>Types can be forward-declared or use recursion (like a linked list node for example) ;</li>
</ul>

<p>I also have some additional requirements that piles another level of difficulty on top of this:</p>
<ul>
  <li>I want the script to reuse predefined data types if available from the <a href="https://github.com/lab313ru/ghidra_psx_ldr">PlayStation executable loader</a> ;</li>
  <li>I want the script to work even on a placeholder program ;</li>
  <li>I want the script to be idempotent.</li>
</ul>

<p>After banging my head on the problem for quite a while, I’ve managed to write a battle-scarred but working Ghidra importation script for SYM files, which has since been <a href="https://github.com/lab313ru/ghidra_psx_ldr/pull/74">upstreamed</a> to the ghidra_psx_ldr extension.</p>

<p>We have <em>symbols</em>:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-8/ghidra-symbol-table.png" />
                <figcaption>Figure 1: Ghidra symbol table for PSX.SYM.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>We have <em>data</em>:</p>
<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-8/ghidra-listing-data.png" />
                <figcaption>Figure 2: Ghidra listing view of data for PSX.SYM.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>We even have <em>functions</em>:</p>
<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-8/ghidra-listing-functions.png" />
                <figcaption>Figure 3: Ghidra listing view of functions for PSX.SYM.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>Sure, we haven’t got a byte for any of this stuff: <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code> is just a placeholder after all.
However, if we can port all that metadata onto an executable we do have, then we’ll have a fully annotated artifact with real bytes…
but that is a story for another part.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We’ve discovered that the debugging symbols file <code class="language-plaintext highlighter-rouge">PSX.SYM</code> doesn’t match <code class="language-plaintext highlighter-rouge">PSX.EXE</code>, meaning that we have a debugging symbols file and no program to use it on.
Undaunted, we’ve created a fake program <code class="language-plaintext highlighter-rouge">PSX.SYM.elf</code> that matches the outline of <code class="language-plaintext highlighter-rouge">PSX.SYM</code> and then wrote a script to import all this information inside Ghidra, as if we had the real program on hand.</p>

<div style="display: grid; grid-template-columns: 1fr 1fr; margin-top: 1ch; margin-bottom: 1ch;">
<div style="padding-right: 15px;">


<a class="prev" href="/tenchu1/2024/03/25/part-7.html">&laquo; Decompiling Tenchu: Stealth Assassins part 7: the AFSMAKE.EXE side-quest</a>

</div>
<div style="padding-left: 15px; text-align: right;">


<a href="/tenchu1/2024/04/15/part-9.html">Decompiling Tenchu: Stealth Assassins part 9: rescue the debugging data &raquo;</a>

</div>
</div>]]></content><author><name>Jean-Baptiste Boric</name></author><category term="tenchu1" /><summary type="html"><![CDATA[Previously in this series of articles, we’ve analyzed some leftovers hidden inside the AFS archive of the demo version of Tenchu. This time, we’ll continue with the main course: PSX.EXE and PSX.SYM, an early build of Tenchu’s main executable with its accompanying debugging information file.]]></summary></entry><entry><title type="html">Decompiling Tenchu: Stealth Assassins part 7: the AFSMAKE.EXE side-quest</title><link href="https://boricj.net/tenchu1/2024/03/25/part-7.html" rel="alternate" type="text/html" title="Decompiling Tenchu: Stealth Assassins part 7: the AFSMAKE.EXE side-quest" /><published>2024-03-25T01:00:00+01:00</published><updated>2024-03-25T01:00:00+01:00</updated><id>https://boricj.net/tenchu1/2024/03/25/part-7</id><content type="html" xml:base="https://boricj.net/tenchu1/2024/03/25/part-7.html"><![CDATA[<p><a href="/tenchu1/2024/03/18/part-6.html">Previously</a> in this <a href="/tenchu1/2024/02/05/introduction.html">series of articles</a>, we’ve messed with the archive code of Tenchu and uncovered some juicy leftovers in the earliest versions of the game.
This time, we’ll lay off the dark magic for a bit and take a look at one of these artifacts: <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code>, presumably used to create the proprietary AFS archive files for this game.</p>

<h2 id="running-afsmakeexe">Running AFSMAKE.EXE</h2>

<p><code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code> is a 32-bit Windows PE program.
To run it on a x86_64 Debian system, Wine needs to be installed first:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo dpkg --add-architecture i386
$ sudo apt-get update
$ sudo apt-get install \
    wine \
    wine32
</code></pre></div></div>

<p>With that out of the way, it’s time to check out the loot:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wine AFSMAKE.EXE 
option:
        -p : pack
        -x : extract
        -l : list

$ wine AFSMAKE.EXE -l
Afs file list
        option: &lt;file&gt;

$ wine AFSMAKE.EXE -p
AFS Volume Maker   Version 2.00
optins:
    &lt;volume file name&gt; &lt;file&gt;
$ wine AFSMAKE.EXE -x
optins:
     &lt;vol name&gt; &lt;filename&gt; &lt;destination&gt;
</code></pre></div></div>

<p>Inconsistent formatting, capitalization and typos: telltale signs of a homemade tool.
Is the lack of veneer superficial or is this program jank through and through?
Only one way to find out.</p>

<h3 id="listings-lost-in-translation">Listings lost in translation</h3>

<p>Let’s try a simple task, listing the files of the demo archive:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wine AFSMAKE.EXE -l TENCHU/DATA.VOL 
 âtâ@âCâïé¬éPé┬éαéáéΦé▄é╣é±
</code></pre></div></div>

<p>Curses!
Obsolete 90’s character encoding strikes again and this time there aren’t any conveniently located English translations nearby.
We’ll have to figure this one out.</p>

<p>Since we are dealing with a Windows program from Japan, it’s a fair bet that this is encoded in code page 932, also known as Shift-JIS.
I’m too lazy to figure out how to tell Wine and my terminal emulator how to interpret this stuff, so we’ll use Python to do the conversion for us:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wine AFSMAKE.EXE -l TENCHU/DATA.VOL &gt; mojibake.txt
$ file mojibake.txt 
mojibake.txt: Non-ISO extended-ASCII text, with CRLF line terminators
$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt; fp=open("mojibake.txt", "rb")
&gt;&gt;&gt; original_string = fp.read()
&gt;&gt;&gt; original_string
b' \x83t\x83@\x83C\x83\x8b\x82\xaa\x82P\x82\xc2\x82\xe0\x82\xa0\x82\xe8\x82\xdc\x82\xb9\x82\xf1\r\n'
&gt;&gt;&gt; original_string.decode("shift-jis")
' ファイルが１つもありません\r\n'
&gt;&gt;&gt; 
</code></pre></div></div>

<p>I still can’t understand Japanese, but Google Translate® tells me this means “There are no files”.</p>

<p>Drilling down the listing part of <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code>, it becomes apparent that this executable contains what is essentially a fully-featured version of <code class="language-plaintext highlighter-rouge">afs.o</code> as covered before, but compiled for Windows and using the C standard library I/O functions instead of the bespoke disc file layer.
It’s close enough that the differences are probably due to macros in the original code used as a compatibility layer between the PlayStation and Windows environments.</p>

<p>Therefore, my first mistake was supplying the suffix <code class="language-plaintext highlighter-rouge">.VOL</code>, as the AFS code concatenates it to the supplied path.
Let’s try again without the suffix:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wine AFSMAKE.EXE -l TENCHU/DATA 
 âtâ@âCâïé¬éPé┬éαéáéΦé▄é╣é±
</code></pre></div></div>

<p>No dice.
What’s going on?</p>

<p>Further analysis reveals that the buffer used to hold said path is at most 16 bytes long and AFS rejects the user-supplied path if it’s more than 8 bytes long.
That’s not for the filename of the archive, but for the <em>whole path to</em> the archive.</p>

<div class="box box-information">
  <p>Even disregarding LFN (long filenames) which were introduced back in Windows 95, MS-DOS 2.0 supported directories back in <em>1983</em>.
Heck, the MIPS version of this code rejects paths longer than 75 bytes.</p>
</div>

<p><em>Fine</em>, have it your way.
Let’s try that again without that pesky directory in the way:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wine AFSMAKE.EXE -l DATA
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TITLE_J.TIM
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TIM.TPD
K:\WORK\CDIMAGE\STAGE\YAMIJOU\MAP.MAD
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TITLE_I.TIM
K:\WORK\CDIMAGE\STAGE\YAMIJOU\STAGE.CON
...
K:\WORK\CDIMAGE\TRIAL\THEME\MATO.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\THEME9.TPD
K:\WORK\CDIMAGE\TRIAL\THEME\UFO.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\NOKI.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\UFO2.TMD
1048 file(s)$ 
</code></pre></div></div>

<p><em>Finally</em>, some conclusive results.</p>

<div class="box box-information">
  <p><code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code> doesn’t output a newline character on the file count line, so the shell prompt isn’t on a new line.</p>
</div>

<h3 id="single-shot-file-extraction">Single-shot file extraction</h3>

<p>Now we’ve managed to list the contents of an archive with <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code>, how about extracting some data?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wine AFSMAKE.EXE -x DATA 'K:\WORK\CDIMAGE\DEMO\START\CARD_E.TXT' card_e.txt
$ cat card_e.txt
Memory Card not found.
Memory Card damaged.
Memory Card not formatted.\Do you want to format it?
There is no saved data\for this game.
Memory Card full.
...
Couldn't read saved data.
Memory Card not found.\Game data can't be saved.\Okay?
Memory Card damaged.\Game data can't be saved.\Okay?
Memory Card doesn't have\enough space.\Game data can't be saved.\Okay?
Saving will overwrite\the previous save data.\Save anyway?
</code></pre></div></div>

<p>It works, but <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code> can only extract one file at a time, which means that my abomination from <a href="/tenchu1/2024/03/18/part-6.html">part 6</a> is sadly not obsolete yet for extracting all the files from AFS archives.</p>

<h3 id="archive-creation">Archive creation</h3>

<p>For the last feature, we actually have a relevant artifact found during the previous part from the original developers, <code class="language-plaintext highlighter-rouge">VOLMAKE.BAT</code>.
It was most likely responsible for building the AFS archive as part of the original game’s development pipeline:</p>

<div class="language-batch highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">k</span>:
<span class="nb">cd</span> \work\cdimage

<span class="nb">del</span> ..\data.vol
<span class="nb">del</span> <span class="kd">data</span>.vol
<span class="kd">afsmake</span> <span class="na">-p </span>..\data <span class="o">*</span>
<span class="nb">copy</span> ..\data.vol .
</code></pre></div></div>

<p>… Oh, right, the game expects that everything inside the archive is under <code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE</code>.
On Linux, we can define drive letters for Wine by creating symbolic links inside <code class="language-plaintext highlighter-rouge">~/.wine/dosdevices</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ln -s ~/Documents/K: ~/.wine/dosdevices/k:
$ cd ~/Documents/K:
$ wine cmd.exe
Microsoft Windows 6.1.7601

K:\&gt;dir
Volume in drive K has no label.
Volume Serial Number is 0000-0000

Directory of K:\

 3/10/2024  11:43 AM  &lt;DIR&gt;         WORK
       0 files                        0 bytes
       1 directory               5,356,400,640 bytes free


K:\&gt;
</code></pre></div></div>

<div class="box box-warning">
  <p>That exact trick won’t work on Windows since this relies on a feature specific to Wine.
It would probably require either having a <code class="language-plaintext highlighter-rouge">K:</code> drive or using a mount point to work around this issue.</p>
</div>

<p>With the <code class="language-plaintext highlighter-rouge">K:</code> drive available, we can try and create an AFS archive to run with the game:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ WINEPATH=~/.local/bin wine cmd.exe
Microsoft Windows 6.1.7601

K:\&gt;cd work\cdimage

K:\work\CDIMAGE&gt;afsmake -p ..\DATA *
AFS Volume Maker   Version 2.00
Create New volume... ..\DATA
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE10A.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE10R.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE11A.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE11R.CAD
add: K:\work\CDIMAGE\ANIM\ENGLISH\STAGE12A.CAD
...
add: K:\work\CDIMAGE\TRIAL\THEME\YATATE.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\YKAGARI.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\YOROI.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\YUKIMORI.TMD
add: K:\work\CDIMAGE\TRIAL\THEME\ZIZOU.TMD
ok. update 1047 file(s).

K:\work\CDIMAGE&gt;
</code></pre></div></div>

<p>That’s every feature of <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code> exercised.
However, it’s not very useful unless we can get the game to use newly created archives.</p>

<h2 id="modding-tenchu">Modding Tenchu</h2>

<p>While I could try and get the <code class="language-plaintext highlighter-rouge">PCdrv</code> backend of the game’s virtual file system working once more, it doesn’t use AFS archives and therefore wouldn’t show off <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code> in action.
Therefore, let’s try repacking a modified version of the game.</p>

<h3 id="repacking-a-playstation-iso">Repacking a PlayStation ISO</h3>

<p>We have the means to create new AFS archives, but we need to create a new ISO file for DuckStation.
PlayStation CD-ROM images are special enough that conventional tools such as <code class="language-plaintext highlighter-rouge">xorriso</code> won’t cut it, so I’ll use <a href="https://github.com/Lameguy64/mkpsxiso">MKPSXISO</a>, a purpose-built tool.
Sadly, the Linux version is built for Ubuntu and won’t work on this Debian distribution, so I’ll use the Windows version instead.</p>

<p>First, we need to generate a XML specification for the CD image to that the tool knows what to do.
We can just use <code class="language-plaintext highlighter-rouge">dumpsxiso</code> to rip the game and generate it for us:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ WINEPATH=~/.local/bin wine cmd.exe
Microsoft Windows 6.1.7601

Z:\home\boricj\Documents\tenchu-hacked&gt;dumpsxiso -s spec.xml "..\..\ISOs\Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan) (Track 1).bin"
DUMPSXISO 2.04 - PlayStation ISO dumping tool
2017 Meido-Tek Productions (John "Lameguy" Wilbert Villamor/Lameguy64)
2020 Phoenix (SadNES cITy)
2021-2022 Silent, Chromaryu, G4Vi, and spicyjpeg

ISO descriptor:

   System ID      : PLAYSTATION
   Volume ID      : 
   Volume Set ID  : 
   Publisher ID   : 
   Data Prep. ID  : 
   Application ID : PLAYSTATION

   Volume Create Date : 1999010214210800+36
   Volume Modify Date : 0000000000000000+0
   Volume Expire Date : 0000000000000000+0

ISO contents:

   Extracting SYSTEM.CNF;1...
SYSTEM.CNF
   Extracting SLPS_019.01;1...
SLPS_019.01
...
TENCHU\MOVIE\ENDAYA.STR
   Extracting ENDING.STR;1...
TENCHU\MOVIE\ENDING.STR
   Extracting CD.CA;1...
TENCHU\CD.CA
WARNING: The CDDA file TENCHU\CD.CA is out of the iso file bounds.
This usually means that the game has audio tracks, and they are on separate files.
As DUMPSXISO does not support dumping from a cue file, you should use an iso file containing all tracks.

DUMPSXISO will write the file as a dummy (silent) cdda file.
This is generally fine, when the real CDDA file is also a dummy file.
If it is not dummy, you WILL lose this audio data in the rebuilt iso.

Z:\home\boricj\Documents\tenchu-hacked&gt;
</code></pre></div></div>

<p>It’s complaining about the second audio track easter egg that we found in <a href="/tenchu1/2024/02/19/part-2.html">part 2</a>, but we don’t need it for our proof-of-concept so we’ll ignore that error message.
After replacing <code class="language-plaintext highlighter-rouge">TENCHU\DATA.VOL</code> with our modified version, we can repack the game with <code class="language-plaintext highlighter-rouge">mkpsxiso</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Z:\home\boricj\Documents\tenchu-hacked&gt;mkpsxiso -y -o "..\..\ISOs\Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan) (Hacked).iso" spec.xml   
MKPSXISO 2.04 - PlayStation ISO Image Maker
2017-2022 Meido-Tek Productions (John "Lameguy" Wilbert Villamor/Lameguy64)
2021-2022 Silent, Chromaryu, G4Vi, and spicyjpeg

Building ISO Image: ..\..\ISOs\Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan) (Hacked).bin + mkpsxiso.cue
Scanning tracks...

  Track #1 data:
    Identifiers:
      System       : PLAYSTATION
      Application  : PLAYSTATION
      Creation Date : 1999010214210800+36

    License file: license_data.dat

    Parsing directory tree...
      Files Total: 24
      Directories: 3
      Total file system size: 739972128 bytes (314614 sectors)

  Track #2 audio:
    DA File CD.CA

Writing ISO
    Writing files...
      Packing SYSTEM.CNF... Done.
      Packing SLPS_019.01... Done.
      Packing TENCHU\RUN.EXE... Done.
      Packing TENCHU\MENU.EXE... Done.
      Packing TENCHU\MAIN.EXE... Done.
      Packing TENCHU\TRIAL.EXE... Done.
      Packing TENCHU\ENDING.EXE... Done.
      Packing TENCHU\DATA.VOL... Done.
      Packing XA TENCHU\MOVIE\SME.STR... Done.
      Packing XA TENCHU\MOVIE\ACQUIRE.STR... Done.
      Packing XA TENCHU\MOVIE\OPEN06.STR... Done.
      Packing XA TENCHU\MOVIE\TRAINING.STR... Done.
      Packing XA TENCHU\XA\TORA.XA... Done.
      Packing XA TENCHU\XA\MUSIC.XA... Done.
      Packing XA TENCHU\XA\STAGES.XA... Done.
      Packing XA TENCHU\XA\EVENT_F.XA... Done.
      Packing XA TENCHU\XA\EVENT_E.XA... Done.
      Packing XA TENCHU\XA\EVENT_I.XA... Done.
      Packing XA TENCHU\XA\EVENT_J.XA... Done.
      Packing XA TENCHU\XA\INTRO.XA... Done.
      Packing XA TENCHU\MOVIE\ENDRIKI.STR... Done.
      Packing XA TENCHU\MOVIE\ENDAYA.STR... Done.
      Packing XA TENCHU\MOVIE\ENDING.STR... Done.

    Writing CDDA tracks...
      Packing audio TENCHU/CD.WAV... Done.

    Writing license data...Ok.
    Writing directories... Ok.
ISO image generated successfully.
Total image size: 747785472 bytes (317936 sectors)

</code></pre></div></div>

<p>With this, we have an ISO file that’ll boot inside DuckStation.</p>

<h3 id="demonstration">Demonstration</h3>

<p>We’ve repacked our modified version of <em>Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen</em>, it’s time to try it out.
While inside a level, by pressing the Select button the player can display a map:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-7/tenchu-original-map.png" />
                <figcaption>Figure 1: Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen with its original map</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>Each level has a map, whose file is named <code class="language-plaintext highlighter-rouge">CHIZU.TIM</code>.
For the first level <em>Punish the Evil Merchant</em>, this file is located inside the AFS archive at <code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\STAGE\AKINDO\CHIZU.TIM</code>.
The original Japanese release of Tenchu has different, hand-drawn maps than the newer releases.
In this mod, I’ve replaced that file with the version from <em>Rittai Ninja Katsugeki Tenchu</em> and we can see the result in-game:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-7/tenchu-modded-map.png" />
                <figcaption>Figure 2: Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen with the map from Rittai Ninja Katsugeki Tenchu</figcaption>
            </figure>
        </div>
    </label>
</div>

<div class="box box-information">
  <p>The newer releases display a small red cross that follows the player on the map.
The original release didn’t and its maps weren’t accurate to the level’s geometry, which is why the location of the small red cross is wrong relative to the hand-drawn map.</p>
</div>

<p>Hardly the mod of the century, but the proof-of-concept is done.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We’ve tested out <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code> found inside the demo version of <em>Rittai Ninja Katsugeki Tenchu</em>, discovered it is quite janky, but nevertheless managed to use it to create a modified version of <em>Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen</em>.</p>

<div style="display: grid; grid-template-columns: 1fr 1fr; margin-top: 1ch; margin-bottom: 1ch;">
<div style="padding-right: 15px;">


<a class="prev" href="/tenchu1/2024/03/18/part-6.html">&laquo; Decompiling Tenchu: Stealth Assassins part 6: archive adventures</a>

</div>
<div style="padding-left: 15px; text-align: right;">


<a href="/tenchu1/2024/04/01/part-8.html">Decompiling Tenchu: Stealth Assassins part 8: make-believe executable for PSX.SYM &raquo;</a>

</div>
</div>]]></content><author><name>Jean-Baptiste Boric</name></author><category term="tenchu1" /><summary type="html"><![CDATA[Previously in this series of articles, we’ve messed with the archive code of Tenchu and uncovered some juicy leftovers in the earliest versions of the game. This time, we’ll lay off the dark magic for a bit and take a look at one of these artifacts: AFSMAKE.EXE, presumably used to create the proprietary AFS archive files for this game.]]></summary></entry><entry><title type="html">Decompiling Tenchu: Stealth Assassins part 6: archive adventures</title><link href="https://boricj.net/tenchu1/2024/03/18/part-6.html" rel="alternate" type="text/html" title="Decompiling Tenchu: Stealth Assassins part 6: archive adventures" /><published>2024-03-18T01:00:00+01:00</published><updated>2024-03-18T01:00:00+01:00</updated><id>https://boricj.net/tenchu1/2024/03/18/part-6</id><content type="html" xml:base="https://boricj.net/tenchu1/2024/03/18/part-6.html"><![CDATA[<p><a href="/tenchu1/2024/03/11/part-5.html">Previously</a> in this <a href="/tenchu1/2024/02/05/introduction.html">series of articles</a>, we’ve started to tear apart the game, beginning with the memory allocator.
This time, we’ll take a look at another piece of the puzzle, the code responsible for the data archive.</p>

<h2 id="wheres-all-the-data">Where’s all the data?</h2>

<p>Back in <a href="/tenchu1/2024/02/19/part-2.html">part 2</a>, we’ve identified <code class="language-plaintext highlighter-rouge">TENCHU/DATA.VOL</code> as an archive file in a proprietary format.
We could go ahead and reverse-engineer that file format the traditional way, but that’s not how this series rolls.
Browsing the strings of <code class="language-plaintext highlighter-rouge">MAIN.EXE</code> for any interesting debugging messages, we can find a bunch of them prefixed with <code class="language-plaintext highlighter-rouge">Afs</code> and the first 8 bytes of <code class="language-plaintext highlighter-rouge">TENCHU/DATA.VOL</code> are <code class="language-plaintext highlighter-rouge">AFS_VOL_200</code>.</p>

<p>Coincidence?
I think not.</p>

<h3 id="the-hunt-for-afs">The hunt for AFS</h3>

<p>The archive file of <em>Tenchu: Stealth Assassins</em> covers most of the game’s data (besides music, movies and executables), but the game doesn’t directly access it.
Instead, it goes through a surprisingly sophisticated indirection mechanism:</p>

<div class="fullwindow-zoom-click">
    <label>
        <input type="checkbox" onclick="onclick_fullWindowZoom(event)" />
        <div class="fullwindow-zoom-click-content">
            <figure>
                <img src="/tenchu1/assets/part-6/tenchu-vfs.svg" />
                <figcaption>Figure 1: Diagram of Tenchu: Stealth Assassins virtual file system.</figcaption>
            </figure>
        </div>
    </label>
</div>

<p>There are three data sources implemented in the VFS code:</p>
<ul>
  <li>PC, by using <code class="language-plaintext highlighter-rouge">libsn</code> functions such as <code class="language-plaintext highlighter-rouge">PCopen()</code> and <code class="language-plaintext highlighter-rouge">PCread()</code> that only work on a dev kit ;</li>
  <li>Memory, by accessing additional memory present on a PlayStation equipped with 8 MiB of RAM  ;</li>
  <li>AFS, by loading files from an AFS archive located on the CD-ROM.</li>
</ul>

<p>Interestingly, the AFS code doesn’t directly interface with Sony’s <code class="language-plaintext highlighter-rouge">libcd</code> library, but goes through an extra layer that provides access to files on the disc through file descriptors.</p>

<p>Retail versions are hard-coded to always initialize the VFS with AFS, since the other two would’ve been useful only during development and none of the other options will work on a standard PlayStation anyway.
The memory data source is stubbed out on all versions but the Japanese demo, however the code for the PC data source is still present on all versions.</p>

<p>Currently we only have the AFS archive to work with and we don’t have any data in the correct format to feed the other data sources.
We’ll need to extract the AFS archive first to get a hold of the assets.</p>

<h3 id="delinking-afso">Delinking afs.o</h3>

<p>After some reverse-engineering and annotating, we can identify the following address ranges of <code class="language-plaintext highlighter-rouge">MAIN.EXE</code> as part of the <code class="language-plaintext highlighter-rouge">AFS</code> archive code:</p>

<table>
  <thead>
    <tr>
      <th>Section</th>
      <th>Address range</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.rdata</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x80014870</code> - <code class="language-plaintext highlighter-rouge">0x800149f2</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.text</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x8005e950</code> - <code class="language-plaintext highlighter-rouge">0x8005f227</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.sdata</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x80098e78</code> - <code class="language-plaintext highlighter-rouge">0x80097e7f</code></td>
    </tr>
  </tbody>
</table>

<p>Just like the memory allocator, we’ll <del>steal</del> repurpose the game’s original code to run inside our own test programs by delinking it into <code class="language-plaintext highlighter-rouge">afs.o</code>, an object file:</p>

<div class="tabs">
<input type="radio" name="file-afs-o" id="tab-header-readelf-afs-o" />
<label for="tab-header-readelf-afs-o">Header</label>
<div class="tab"><div class="language-plaintext highlighter-rouge">
<div class="highlight">
<pre class="highlight">
<code>$ readelf --wide --file-header afs.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           MIPS R3000
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          52 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         9
  Section header string table index: 8</code>
</pre>
</div>
</div></div>
<input type="radio" name="file-afs-o" id="tab-sections-readelf-afs-o" />
<label for="tab-sections-readelf-afs-o">Sections</label>
<div class="tab"><div class="language-plaintext highlighter-rouge">
<div class="highlight">
<pre class="highlight">
<code>$ readelf --wide --section-headers --string-dump=.comment afs.o
There are 9 section headers, starting at offset 0x34:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .strtab           STRTAB          00000000 00019c 0003de 00      0   0  1
  [ 2] .symtab           SYMTAB          00000000 00057c 0003c0 10      1  34  4
  [ 3] .rdata            PROGBITS        00000000 000940 000183 00   A  0   0 16
  [ 4] .text             PROGBITS        00000000 000ad0 000928 00  AX  0   0 16
  [ 5] .sdata            PROGBITS        00000000 001400 000008 00  WA  0   0 16
  [ 6] .rel.text         REL             00000000 001408 0002d0 08   I  2   4  4
  [ 7] .comment          PROGBITS        00000000 0016d8 000024 01  MS  0   0  1
  [ 8] .shstrtab         STRTAB          00000000 0016fc 000042 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

String dump of section '.comment':
  [     0]  ghidra-delinker-extension v0.3.0
</code>
</pre>
</div>
</div></div>
<input type="radio" name="file-afs-o" id="tab-symbols-readelf-afs-o" checked="" />
<label for="tab-symbols-readelf-afs-o">Symbols</label>
<div class="tab"><div class="language-plaintext highlighter-rouge">
<div class="highlight">
<pre class="highlight">
<code>$ readelf --wide --symbols afs.o

Symbol table '.symtab' contains 60 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS afs.o
     2: 00000000     0 SECTION LOCAL  DEFAULT    3 
     3: 00000000     0 SECTION LOCAL  DEFAULT    4 
     4: 00000000     0 SECTION LOCAL  DEFAULT    5 
     5: 00000000    28 OBJECT  LOCAL  DEFAULT    3 s_AfsOpenVolume:_%s_open_err_80014870
     6: 0000001c    28 OBJECT  LOCAL  DEFAULT    3 s_AfsOpenVolume:_Header_error_8001488c
     7: 00000038    27 OBJECT  LOCAL  DEFAULT    3 s_AfsOpenVolume:_Entry_error_800148a8
     8: 00000054    12 OBJECT  LOCAL  DEFAULT    3 s_AFS_VOL_200_800148c4
     9: 00000064    25 OBJECT  LOCAL  DEFAULT    3 s_AfsGetEntry:_empty_index_800148d4
    10: 00000080    31 OBJECT  LOCAL  DEFAULT    3 s_AfsGetEnty:_memory_not_enough!_800148f0
    11: 000000a0    32 OBJECT  LOCAL  DEFAULT    3 s_AfsGetEntry:_memory_not_enough!_80014910
    12: 000000c0    20 OBJECT  LOCAL  DEFAULT    3 s_Illigal_index_data_80014930
    13: 000000d4    28 OBJECT  LOCAL  DEFAULT    3 s_AfsInit:_not_enough_memory!_80014944
    14: 000000f0    23 OBJECT  LOCAL  DEFAULT    3 s_AfsOpen:_%s_not_found_80014960
    15: 00000108    29 OBJECT  LOCAL  DEFAULT    3 s_AfsOpen:_no_more_handle_[%s]_80014978
    16: 00000128    25 OBJECT  LOCAL  DEFAULT    3 s_AfsClose:_invalid_handle_80014998
    17: 00000144    28 OBJECT  LOCAL  DEFAULT    3 s_AfsFileSize:_invalid_handle_800149b4
    18: 00000160    24 OBJECT  LOCAL  DEFAULT    3 s_AfsRead:_invalid_handle_800149d0
    19: 00000178    11 OBJECT  LOCAL  DEFAULT    3 s_@%d_%.195s_800149e8
    20: 000000b8     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005ea08
    21: 0000020c     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005eb5c
    22: 00000348     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005ec98
    23: 00000388     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005ecd8
    24: 00000410     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005ed60
    25: 00000438     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005ed88
    26: 0000051c     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005ee6c
    27: 00000584     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005eed4
    28: 00000660     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005efb0
    29: 0000066c     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005efbc
    30: 000006a8     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005eff8
    31: 000006e8     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005f038
    32: 000007a0     4 OBJECT  LOCAL  DEFAULT    4 LAB_8005f0f0
    33: 00000000     5 OBJECT  LOCAL  DEFAULT    5 s_.VOL_80097e78
    34: 00000000   564 FUNC    GLOBAL DEFAULT    4 AfsGetEntry
    35: 00000234   560 FUNC    GLOBAL DEFAULT    4 AfsFind
    36: 00000464   204 FUNC    GLOBAL DEFAULT    4 AfsOpenVolume
    37: 00000530   100 FUNC    GLOBAL DEFAULT    4 AfsInit
    38: 00000594    80 FUNC    GLOBAL DEFAULT    4 FUN_8005eee4
    39: 000005e4   156 FUNC    GLOBAL DEFAULT    4 AfsOpen
    40: 00000680    56 FUNC    GLOBAL DEFAULT    4 AfsClose
    41: 000006b8    64 FUNC    GLOBAL DEFAULT    4 AfsFileSize
    42: 000006f8   196 FUNC    GLOBAL DEFAULT    4 AfsRead
    43: 000007bc   180 FUNC    GLOBAL DEFAULT    4 FUN_8005f10c
    44: 00000870   184 FUNC    GLOBAL DEFAULT    4 AfsCheckMagic
    45: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND AdtMessageBox
    46: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND DiscOpenFile
    47: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND DiscReadFile
    48: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND DiscSeekFile
    49: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND MemoryAllocate
    50: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND MemoryFree
    51: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND memset
    52: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND sprintf
    53: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND strcat
    54: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND strcmp
    55: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND strcpy
    56: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND strlen
    57: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND strncmp
    58: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND strncpy
    59: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND toupper</code>
</pre>
</div>
</div></div>
<input type="radio" name="file-afs-o" id="tab-relocations-readelf-afs-o" />
<label for="tab-relocations-readelf-afs-o">Relocations</label>
<div class="tab"><div class="language-plaintext highlighter-rouge">
<div class="highlight">
<pre class="highlight">
<code>$ readelf --wide --relocs afs.o

Relocation section '.rel.text' at offset 0x1408 contains 90 entries:
 Offset     Info    Type                Sym. Value  Symbol's Name
00000038  00000905 R_MIPS_HI16            00000064   s_AfsGetEntry:_empty_index_800148d4
0000003c  00002d04 R_MIPS_26              00000000   AdtMessageBox
00000040  00000906 R_MIPS_LO16            00000064   s_AfsGetEntry:_empty_index_800148d4
00000044  00001504 R_MIPS_26              0000020c   LAB_8005eb5c
00000050  00003104 R_MIPS_26              00000000   MemoryAllocate
00000064  00000a05 R_MIPS_HI16            00000080   s_AfsGetEnty:_memory_not_enough!_800148f0
00000068  00002d04 R_MIPS_26              00000000   AdtMessageBox
0000006c  00000a06 R_MIPS_LO16            00000080   s_AfsGetEnty:_memory_not_enough!_800148f0
00000070  00003204 R_MIPS_26              00000000   MemoryFree
00000078  00001504 R_MIPS_26              0000020c   LAB_8005eb5c
00000090  00003104 R_MIPS_26              00000000   MemoryAllocate
000000a4  00000b05 R_MIPS_HI16            000000a0   s_AfsGetEntry:_memory_not_enough!_80014910
000000a8  00001404 R_MIPS_26              000000b8   LAB_8005ea08
000000ac  00000b06 R_MIPS_LO16            000000a0   s_AfsGetEntry:_memory_not_enough!_80014910
000000b0  00000c05 R_MIPS_HI16            000000c0   s_Illigal_index_data_80014930
000000b4  00000c06 R_MIPS_LO16            000000c0   s_Illigal_index_data_80014930
000000b8  00002d04 R_MIPS_26              00000000   AdtMessageBox
000000c0  00001504 R_MIPS_26              0000020c   LAB_8005eb5c
000000d0  00003004 R_MIPS_26              00000000   DiscSeekFile
000000ec  00002f04 R_MIPS_26              00000000   DiscReadFile
000001ac  00003a04 R_MIPS_26              00000000   strncpy
00000200  00003204 R_MIPS_26              00000000   MemoryFree
00000268  00003a04 R_MIPS_26              00000000   strncpy
00000288  00003b04 R_MIPS_26              00000000   toupper
00000300  00003904 R_MIPS_26              00000000   strncmp
00000354  00003704 R_MIPS_26              00000000   strcpy
00000360  00001305 R_MIPS_HI16            00000178   s_@%d_%.195s_800149e8
00000364  00001306 R_MIPS_LO16            00000178   s_@%d_%.195s_800149e8
0000036c  00003404 R_MIPS_26              00000000   sprintf
00000374  00001704 R_MIPS_26              00000388   LAB_8005ecd8
0000037c  00001604 R_MIPS_26              00000348   LAB_8005ec98
000003c8  00003904 R_MIPS_26              00000000   strncmp
00000418  00001904 R_MIPS_26              00000438   LAB_8005ed88
00000420  00001804 R_MIPS_26              00000410   LAB_8005ed60
00000478  00002504 R_MIPS_26              00000530   AfsInit
00000480  00003804 R_MIPS_26              00000000   strlen
00000498  00003704 R_MIPS_26              00000000   strcpy
000004a4  00002105 R_MIPS_HI16            00000000   s_.VOL_80097e78
000004a8  00003504 R_MIPS_26              00000000   strcat
000004ac  00002106 R_MIPS_LO16            00000000   s_.VOL_80097e78
000004b4  00002e04 R_MIPS_26              00000000   DiscOpenFile
000004c4  00000505 R_MIPS_HI16            00000000   s_AfsOpenVolume:_%s_open_err_80014870
000004c8  00000506 R_MIPS_LO16            00000000   s_AfsOpenVolume:_%s_open_err_80014870
000004cc  00002d04 R_MIPS_26              00000000   AdtMessageBox
000004d4  00001a04 R_MIPS_26              0000051c   LAB_8005ee6c
000004dc  00002c04 R_MIPS_26              00000870   AfsCheckMagic
000004e8  00000605 R_MIPS_HI16            0000001c   s_AfsOpenVolume:_Header_error_8001488c
000004ec  00002d04 R_MIPS_26              00000000   AdtMessageBox
000004f0  00000606 R_MIPS_LO16            0000001c   s_AfsOpenVolume:_Header_error_8001488c
000004f4  00001a04 R_MIPS_26              0000051c   LAB_8005ee6c
000004fc  00002204 R_MIPS_26              00000000   AfsGetEntry
0000050c  00000705 R_MIPS_HI16            00000038   s_AfsOpenVolume:_Entry_error_800148a8
00000510  00002d04 R_MIPS_26              00000000   AdtMessageBox
00000514  00000706 R_MIPS_LO16            00000038   s_AfsOpenVolume:_Entry_error_800148a8
00000550  00003104 R_MIPS_26              00000000   MemoryAllocate
00000560  00000d05 R_MIPS_HI16            000000d4   s_AfsInit:_not_enough_memory!_80014944
00000564  00002d04 R_MIPS_26              00000000   AdtMessageBox
00000568  00000d06 R_MIPS_LO16            000000d4   s_AfsInit:_not_enough_memory!_80014944
0000056c  00001b04 R_MIPS_26              00000584   LAB_8005eed4
0000057c  00003304 R_MIPS_26              00000000   memset
000005b4  00003b04 R_MIPS_26              00000000   toupper
000005fc  00002304 R_MIPS_26              00000234   AfsFind
00000610  00000e05 R_MIPS_HI16            000000f0   s_AfsOpen:_%s_not_found_80014960
00000614  00001c04 R_MIPS_26              00000660   LAB_8005efb0
00000618  00000e06 R_MIPS_LO16            000000f0   s_AfsOpen:_%s_not_found_80014960
0000062c  00001d04 R_MIPS_26              0000066c   LAB_8005efbc
00000658  00000f05 R_MIPS_HI16            00000108   s_AfsOpen:_no_more_handle_[%s]_80014978
0000065c  00000f06 R_MIPS_LO16            00000108   s_AfsOpen:_no_more_handle_[%s]_80014978
00000660  00002d04 R_MIPS_26              00000000   AdtMessageBox
00000690  00001e04 R_MIPS_26              000006a8   LAB_8005eff8
00000698  00001005 R_MIPS_HI16            00000128   s_AfsClose:_invalid_handle_80014998
0000069c  00002d04 R_MIPS_26              00000000   AdtMessageBox
000006a0  00001006 R_MIPS_LO16            00000128   s_AfsClose:_invalid_handle_80014998
000006d0  00001f04 R_MIPS_26              000006e8   LAB_8005f038
000006d8  00001105 R_MIPS_HI16            00000144   s_AfsFileSize:_invalid_handle_800149b4
000006dc  00002d04 R_MIPS_26              00000000   AdtMessageBox
000006e0  00001106 R_MIPS_LO16            00000144   s_AfsFileSize:_invalid_handle_800149b4
00000724  00001205 R_MIPS_HI16            00000160   s_AfsRead:_invalid_handle_800149d0
00000728  00002d04 R_MIPS_26              00000000   AdtMessageBox
0000072c  00001206 R_MIPS_LO16            00000160   s_AfsRead:_invalid_handle_800149d0
00000730  00002004 R_MIPS_26              000007a0   LAB_8005f0f0
0000074c  00003004 R_MIPS_26              00000000   DiscSeekFile
0000077c  00002004 R_MIPS_26              000007a0   LAB_8005f0f0
00000788  00002f04 R_MIPS_26              00000000   DiscReadFile
00000808  00003904 R_MIPS_26              00000000   strncmp
00000888  00003004 R_MIPS_26              00000000   DiscSeekFile
00000898  00002f04 R_MIPS_26              00000000   DiscReadFile
000008a4  00000805 R_MIPS_HI16            00000054   s_AFS_VOL_200_800148c4
000008a8  00003604 R_MIPS_26              00000000   strcmp
000008ac  00000806 R_MIPS_LO16            00000054   s_AFS_VOL_200_800148c4</code>
</pre>
</div>
</div></div>
</div>

<p>This time, we have more undefined symbols besides the usual standard C library functions:</p>
<ul>
  <li>Memory allocation (<code class="language-plaintext highlighter-rouge">MemoryAllocate</code>, <code class="language-plaintext highlighter-rouge">MemoryFree</code>) ;</li>
  <li>Message boxes (<code class="language-plaintext highlighter-rouge">AdtMessageBox</code>) ;</li>
  <li>Disc file functions (<code class="language-plaintext highlighter-rouge">DiscOpenFile</code>, <code class="language-plaintext highlighter-rouge">DiscReadFile</code>, <code class="language-plaintext highlighter-rouge">DiscSeekFile</code>).</li>
</ul>

<p>We have a couple more functions to shim, but it’s still manageable.
We also need the corresponding header file for the C compiler to parse:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#pragma once
</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">uint16_t</span> <span class="n">signature</span><span class="p">;</span>
    <span class="kt">uint16_t</span> <span class="n">type</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">location</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field1_0xc</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field2_0xd</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field3_0xe</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field4_0xf</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">name</span><span class="p">[</span><span class="mi">20</span><span class="p">];</span>
<span class="p">}</span> <span class="n">afs_entry_t</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="kt">int32_t</span> <span class="n">used</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">cursor</span><span class="p">;</span>
    <span class="n">afs_entry_t</span><span class="o">*</span> <span class="n">entry</span><span class="p">;</span>
<span class="p">}</span> <span class="n">afs_file_descriptor_t</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">disc_file_descriptor_t</span> <span class="o">*</span><span class="n">discDescriptor</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field1_0x4</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field2_0x5</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field3_0x6</span><span class="p">;</span>
    <span class="kt">char</span> <span class="n">field4_0x7</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">entriesOffset</span><span class="p">;</span>
    <span class="n">afs_entry_t</span><span class="o">*</span> <span class="n">entries</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">numberEntries</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">field8_0x14</span><span class="p">;</span>
    <span class="n">afs_file_descriptor_t</span><span class="o">*</span> <span class="n">fileDescriptors</span><span class="p">;</span>
<span class="p">}</span> <span class="n">afs_volume_t</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">AfsGetEntry</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">);</span>
<span class="n">afs_entry_t</span><span class="o">*</span> <span class="nf">AfsFind</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">short</span> <span class="n">flags</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">AfsOpenVolume</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">AfsInit</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">);</span>
<span class="n">afs_file_descriptor_t</span><span class="o">*</span> <span class="nf">AfsOpen</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">AfsClose</span><span class="p">(</span><span class="n">afs_file_descriptor_t</span><span class="o">*</span> <span class="n">fd</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">AfsRead</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">volume</span><span class="p">,</span> <span class="n">afs_file_descriptor_t</span><span class="o">*</span> <span class="n">fd</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">buffer</span><span class="p">,</span> <span class="kt">int</span> <span class="n">length</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">AfsCheckMagic</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">);</span>
</code></pre></div></div>

<div class="box box-information">
  <p>We do not have a definition for <code class="language-plaintext highlighter-rouge">disc_file_descriptor_t</code> inside <code class="language-plaintext highlighter-rouge">afs.h</code>, as it is part of the disc file subsystem.
We will shim it anyways during our investigations in this part.</p>
</div>

<h2 id="puppeteering-the-games-code">Puppeteering the game’s code</h2>

<p>We have our object file, so we might as well do something interesting with it.
But first, we need to provide alternative implementations for the missing symbols before we can start using <code class="language-plaintext highlighter-rouge">afs.o</code>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">disc_file_descriptor_t</span> <span class="n">disc_file_descriptor_t</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">AdtMessageBox</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">fmt</span><span class="p">,</span> <span class="p">...)</span> <span class="p">{</span>
	<span class="kt">va_list</span> <span class="n">args</span><span class="p">;</span>

	<span class="n">va_start</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">fmt</span><span class="p">);</span>
	<span class="n">vprintf</span><span class="p">(</span><span class="n">fmt</span><span class="p">,</span> <span class="n">args</span><span class="p">);</span>
	<span class="n">va_end</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">disc_file_descriptor_t</span><span class="o">*</span> <span class="nf">DiscOpenFile</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">return</span> <span class="p">(</span><span class="n">disc_file_descriptor_t</span><span class="o">*</span><span class="p">)</span> <span class="n">fopen</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">DiscReadFile</span><span class="p">(</span><span class="n">disc_file_descriptor_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">buffer</span><span class="p">,</span> <span class="kt">int</span> <span class="n">length</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">fread</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">length</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="kt">FILE</span><span class="o">*</span><span class="p">)</span> <span class="n">handle</span><span class="p">);</span>
	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">DiscSeekFile</span><span class="p">(</span><span class="n">disc_file_descriptor_t</span><span class="o">*</span> <span class="n">handle</span><span class="p">,</span> <span class="kt">int</span> <span class="n">offset</span><span class="p">,</span> <span class="kt">int</span> <span class="n">whence</span><span class="p">)</span> <span class="p">{</span>
	<span class="kt">int</span> <span class="n">origin</span><span class="p">;</span>
	<span class="k">switch</span> <span class="p">(</span><span class="n">whence</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">case</span> <span class="mi">0</span><span class="p">:</span>
			<span class="n">origin</span> <span class="o">=</span> <span class="n">SEEK_SET</span><span class="p">;</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="k">case</span> <span class="mi">1</span><span class="p">:</span>
			<span class="n">origin</span> <span class="o">=</span> <span class="n">SEEK_CUR</span><span class="p">;</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="k">case</span> <span class="mi">2</span><span class="p">:</span>
			<span class="n">origin</span> <span class="o">=</span> <span class="n">SEEK_END</span><span class="p">;</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="nl">default:</span>
			<span class="n">abort</span><span class="p">();</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="n">fseek</span><span class="p">((</span><span class="kt">FILE</span><span class="o">*</span><span class="p">)</span> <span class="n">handle</span><span class="p">,</span> <span class="n">offset</span><span class="p">,</span> <span class="n">origin</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With that out of the way, it’s time for everyone’s favorite activity: code necromancy.</p>

<h3 id="proof-of-concept">Proof-of-concept</h3>

<p>Let’s start with a single-shot file extractor as a first step, similar to what the game internally does but dumping out the contents to the standard output:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">afs_volume_t</span> <span class="n">vol</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">AfsOpenVolume</span><span class="p">(</span><span class="o">&amp;</span><span class="n">vol</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="n">afs_file_descriptor_t</span><span class="o">*</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">AfsOpen</span><span class="p">(</span><span class="o">&amp;</span><span class="n">vol</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="kt">int</span> <span class="n">length</span> <span class="o">=</span> <span class="n">AfsFileSize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">vol</span><span class="p">,</span> <span class="n">fd</span><span class="p">);</span>
	<span class="kt">void</span><span class="o">*</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">MemoryAllocate</span><span class="p">(</span><span class="n">length</span><span class="p">);</span>
	<span class="n">AfsRead</span><span class="p">(</span><span class="o">&amp;</span><span class="n">vol</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">length</span><span class="p">);</span>
	<span class="n">AfsClose</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>

	<span class="n">write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">length</span><span class="p">);</span>
	<span class="n">MemoryFree</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span>

	<span class="k">return</span> <span class="n">EXIT_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Let’s build it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mipsel-linux-gnu-gcc -g -static -fno-pic -no-pie -o test-afs.elf test-afs.c afs.o memory.o panic.o
/usr/lib/gcc-cross/mipsel-linux-gnu/10/../../../../mipsel-linux-gnu/bin/ld: afs.o: warning: linking abicalls files with non-abicalls files
/usr/lib/gcc-cross/mipsel-linux-gnu/10/../../../../mipsel-linux-gnu/bin/ld: memory.o: warning: linking abicalls files with non-abicalls files
/usr/lib/gcc-cross/mipsel-linux-gnu/10/../../../../mipsel-linux-gnu/bin/ld: panic.o: warning: linking abicalls files with non-abicalls files
</code></pre></div></div>

<p>As usual, the toolchain isn’t happy, but it does produce an executable.
Looking at <code class="language-plaintext highlighter-rouge">MAIN.EXE</code>, we can find the string <code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\DEMO\start\card_j.txt</code> embedded within it.
Let’s try with it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ qemu-mipsel ./test-afs.elf TENCHU/DATA 'K:\WORK\CDIMAGE\DEMO\start\card_j.txt'
[J[hĂ܂.
[J[hĂ܂.
tH[}bgĂ܂\tH[}bg܂?
̃Q[̃f[^\Z[uĂ܂.
[J[h\󂫗eʂ܂.
f[^ǂݍݒł\J[h𔲂Ȃŉ!
f[^ݒł\J[h𔲂Ȃŉ!
ǂݍ݊.
݊.
tH[}bgł\J[h𔲂Ȃŉ!
tH[}bg.
tH[}bgł܂ł.
f[^ǂݍ݂ł܂ł.
f[^݂ł܂ł.
ȑÕf[^Z[uĂ܂\㏑܂?
[J[h`FbNł!
tH[}bgĂ܂.
[J[hĂ܂\J[hĂȂ\f[^Z[uł܂.
[J[h\󂫗eʂ܂\̂܂܂ł̓Z[uł܂.
Z[uł܂񂪂낵ł?
Z[uf[^ǂݍݒł\J[h𔲂Ȃŉ!
Z[uf[^ݒł\J[h𔲂Ȃŉ!
Z[uf[^\ǂݍ݂ł܂ł.
[J[hĂ܂\Z[uł܂񂪂낵ł?
[J[hĂ܂\Z[uł܂񂪂낵ł?
[J[h\󂫗eʂ܂\Z[uł܂񂪂낵ł?
łɃf[^Z[uĂ܂\㏑\Õf[^͖Ȃ܂\Z[u܂?
VɃZ[u܂?
[J[hɃZ[u܂?
㏑܂?
f[^ǂݍ݂ł܂ł\x݂܂?
f[^݂ł܂ł\x݂܂?
̔Cf[^폜܂?
{ɂ낵ł?
폜ł\J[h𔲂Ȃŉ!
폜.
폜ł܂ł.
[J[hĂ܂\ҏWł܂񂪂낵ł?
[hł܂񂪂낵ł?
[J[h\tH[}bgĂ܂\ҏWł܂񂪂낵ł?
̃Q[̖{҃f[^\Z[uĂ܂.
̔Cf[^\Z[uĂ܂.
t@C܂ł.
ׂẴQ[f[^\iō쐬Cf[^ȊOj\㏑܂?
</code></pre></div></div>

<p>Well, that’s a whole lot of mojibake, but we can discern some structure with the dots and question marks.
I could try and find in what code page this is, but rather than wrestling with obsolete character encodings in a foreign language I’ll hazard a guess and try changing the file name to <code class="language-plaintext highlighter-rouge">card_e.txt</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ qemu-mipsel ./test-afs.elf TENCHU/DATA 'K:\WORK\CDIMAGE\DEMO\start\card_e.txt'
Memory Card not found.
Memory Card damaged.
Memory Card not formatted.\Do you want to format it?
There is no saved data\for this game.
Memory Card full.
Reading data.\Don't remove Memory Card!
Writing data.\Don't remove Memory Card!
Finished reading data.
Finished writing data.
Formatting.\Don't remove Memory Card!
Finished formatting.
Formatting failed.
Couldn't read data.
Couldn't write data.
Overwrite\previously saved data?
Checking Memory Card!
Memory Card not formatted.
Memory Card not found.\Insert a Memory Card\for saving data.
Memory Card does not have\enough space for saving data.
Game data can't be saved.\Okay?
Reading data.\Don't remove Memory Card!
Writing data.\Don't remove Memory Card!
Couldn't read saved data.
Memory Card not found.\Game data can't be saved.\Okay?
Memory Card damaged.\Game data can't be saved.\Okay?
Memory Card doesn't have\enough space.\Game data can't be saved.\Okay?
Saving will overwrite\the previous save data.\Save anyway?
</code></pre></div></div>

<p>Ah, a plain ASCII file that my terminal emulator can render and that I can read.
I guess that the first file was also extracted correctly but my terminal couldn’t render whatever 90’s nonsense this was.</p>

<h3 id="the-jankiest-archive-extractor-ever">The jankiest archive extractor ever</h3>

<p>Unfortunately, none of the AFS functions from the game can enumerate the contents of the archive, so we can only extract files this way if we know their full path ahead of time.
Therefore, we do need to take a peek at the underlying code and file format to figure this part out.</p>

<p>AFS archives contain a flat index table of files and directories.
The first entry is a directory with a straightforward name, but the next ones encode the parent’s entry index <em>in the name</em>.</p>

<p>For example, the following directory tree layout:</p>
<ul>
  <li>K:
    <ul>
      <li>WORK
        <ul>
          <li>CDIMAGE
            <ul>
              <li>STAGE
                <ul>
                  <li>YAMIJO
                    <ul>
                      <li>TITLE_J.TIM</li>
                      <li>TIM.TPD</li>
                      <li>…</li>
                      <li>IE_3</li>
                      <li>IE_2</li>
                    </ul>
                  </li>
                  <li>TEMPLE
                    <ul>
                      <li>TITLE_J.TIM</li>
                      <li>TITLE_I.TIM</li>
                      <li>…</li>
                    </ul>
                  </li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>Would be encoded like so in the index table:</p>

<table>
  <thead>
    <tr>
      <th>Index</th>
      <th>Name</th>
      <th>Real name</th>
      <th>Parent index</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td><code class="language-plaintext highlighter-rouge">K:</code></td>
      <td><code class="language-plaintext highlighter-rouge">K:</code></td>
      <td>N/A</td>
    </tr>
    <tr>
      <td>1</td>
      <td><code class="language-plaintext highlighter-rouge">@0_WORK</code></td>
      <td><code class="language-plaintext highlighter-rouge">WORK</code></td>
      <td>0</td>
    </tr>
    <tr>
      <td>2</td>
      <td><code class="language-plaintext highlighter-rouge">@1_CDIMAGE</code></td>
      <td><code class="language-plaintext highlighter-rouge">CDIMAGE</code></td>
      <td>1</td>
    </tr>
    <tr>
      <td>3</td>
      <td><code class="language-plaintext highlighter-rouge">@2_STAGE</code></td>
      <td><code class="language-plaintext highlighter-rouge">STAGE</code></td>
      <td>2</td>
    </tr>
    <tr>
      <td>4</td>
      <td><code class="language-plaintext highlighter-rouge">@3_YAMIJO</code></td>
      <td><code class="language-plaintext highlighter-rouge">YAMIJO</code></td>
      <td>3</td>
    </tr>
    <tr>
      <td>5</td>
      <td><code class="language-plaintext highlighter-rouge">@4_TITLE_J.TIM</code></td>
      <td><code class="language-plaintext highlighter-rouge">TITLE_J.TIM</code></td>
      <td>4</td>
    </tr>
    <tr>
      <td>6</td>
      <td><code class="language-plaintext highlighter-rouge">@4_TIM.TPD</code></td>
      <td><code class="language-plaintext highlighter-rouge">TIM.TPD</code></td>
      <td>4</td>
    </tr>
    <tr>
      <td>…</td>
      <td>…</td>
      <td>…</td>
      <td>…</td>
    </tr>
    <tr>
      <td>19</td>
      <td><code class="language-plaintext highlighter-rouge">@4_IE_3</code></td>
      <td><code class="language-plaintext highlighter-rouge">IE_3</code></td>
      <td>4</td>
    </tr>
    <tr>
      <td>18</td>
      <td><code class="language-plaintext highlighter-rouge">@4_IE_2</code></td>
      <td><code class="language-plaintext highlighter-rouge">IE_2</code></td>
      <td>4</td>
    </tr>
    <tr>
      <td>20</td>
      <td><code class="language-plaintext highlighter-rouge">@3_TEMPLE</code></td>
      <td><code class="language-plaintext highlighter-rouge">TEMPLE</code></td>
      <td>3</td>
    </tr>
    <tr>
      <td>21</td>
      <td><code class="language-plaintext highlighter-rouge">@20_TITLE_J.TIM</code></td>
      <td><code class="language-plaintext highlighter-rouge">TITLE_J.TIM</code></td>
      <td>20</td>
    </tr>
    <tr>
      <td>22</td>
      <td><code class="language-plaintext highlighter-rouge">@20_TITLE_I.TIM</code></td>
      <td><code class="language-plaintext highlighter-rouge">TITLE_I.TIM</code></td>
      <td>20</td>
    </tr>
    <tr>
      <td>…</td>
      <td>…</td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>

<div class="box box-information">
  <p>Needless to say, encoding the parent directory’s index number as part of the file name is <em>super</em> weird.
I haven’t studied the <code class="language-plaintext highlighter-rouge">AFS</code> code in depth, but the mere fact it’s calling <code class="language-plaintext highlighter-rouge">sprintf</code> as part of searching for an entry should’ve been a code smell.</p>
</div>

<p>Fortunately, we can hack our way around this mess by recomputing the depth of the entries and storing it in an unused field of <code class="language-plaintext highlighter-rouge">afs_entry_t</code> and overwriting the buffer for the path as we iterate through the index:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">walk_afs_volume</span><span class="p">(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">vol</span><span class="p">,</span> <span class="kt">void</span><span class="p">(</span><span class="o">*</span><span class="n">callback</span><span class="p">)(</span><span class="n">afs_volume_t</span><span class="o">*</span> <span class="n">vol</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">,</span> <span class="kt">int</span> <span class="n">isfile</span><span class="p">))</span> <span class="p">{</span>
	<span class="kt">int</span> <span class="n">depth</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="kt">char</span> <span class="n">path</span><span class="p">[</span><span class="mi">128</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>

	<span class="n">strcpy</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">vol</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">name</span><span class="p">);</span>
	<span class="n">strcat</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">PATH_SEPARATOR</span><span class="p">);</span>
	<span class="n">vol</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">field1_0xc</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">index</span> <span class="o">&lt;</span> <span class="n">vol</span><span class="o">-&gt;</span><span class="n">numberEntries</span><span class="p">;</span> <span class="n">index</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">afs_entry_t</span><span class="o">*</span> <span class="n">entry</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">vol</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
		<span class="n">afs_entry_t</span><span class="o">*</span> <span class="n">parent</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">vol</span><span class="o">-&gt;</span><span class="n">entries</span><span class="p">[</span><span class="n">atoi</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">name</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)];</span>
		<span class="kt">char</span><span class="o">*</span> <span class="n">component</span> <span class="o">=</span> <span class="n">strchr</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">,</span> <span class="sc">'_'</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>

		<span class="kt">char</span><span class="o">*</span> <span class="n">target</span> <span class="o">=</span> <span class="n">path</span><span class="p">;</span>
		<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">parent</span><span class="o">-&gt;</span><span class="n">field1_0xc</span><span class="p">;</span> <span class="n">count</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">count</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">target</span> <span class="o">=</span> <span class="n">strstr</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">PATH_SEPARATOR</span><span class="p">)</span> <span class="o">+</span> <span class="n">strlen</span><span class="p">(</span><span class="n">PATH_SEPARATOR</span><span class="p">);</span>
		<span class="p">}</span>

		<span class="n">strcpy</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">component</span><span class="p">);</span>

		<span class="k">if</span> <span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">location</span> <span class="o">==</span> <span class="mh">0xCDCDCDCD</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">entry</span><span class="o">-&gt;</span><span class="n">field1_0xc</span> <span class="o">=</span> <span class="n">parent</span><span class="o">-&gt;</span><span class="n">field1_0xc</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
			<span class="n">callback</span><span class="p">(</span><span class="n">vol</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
			<span class="n">strcat</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">PATH_SEPARATOR</span><span class="p">);</span>
		<span class="p">}</span>
		<span class="k">else</span> <span class="p">{</span>
			<span class="n">callback</span><span class="p">(</span><span class="n">vol</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="box box-warning">
  <p>At this point, writing a brand-new extractor from scratch would’ve probably been a better idea than insisting on piggy-backing the original game code.
At any rate, it makes for a decent shakedown of <code class="language-plaintext highlighter-rouge">afs.o</code>.</p>
</div>

<p>Regardless, with a means to enumerate complete paths we can call <code class="language-plaintext highlighter-rouge">AfsOpen</code> with those and extract them like in the previous section.
After writing some more code we end up with the <code class="language-plaintext highlighter-rouge">unafs.elf</code> utility, with command-line options inspired from <code class="language-plaintext highlighter-rouge">tar</code>.
It can list the files in a volume:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ qemu-mipsel ./unafs.elf -tvf TENCHU/DATA
K:
K:\WORK
K:\WORK\CDIMAGE
K:\WORK\CDIMAGE\STAGE
K:\WORK\CDIMAGE\STAGE\YAMIJOU
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TITLE_J.TIM
K:\WORK\CDIMAGE\STAGE\YAMIJOU\TIM.TPD
...
K:\WORK\CDIMAGE\STAGE\CAVE\IE_1
K:\WORK\CDIMAGE\STAGE\CAVE\&lt;BA&gt;&lt;CB&gt;߰ &lt;81&gt;` STAGE.
K:\WORK\CDIMAGE\STAGE\CAVE\TITLE_E.TIM
...
K:\WORK\CDIMAGE\TRIAL\THEME\MATO.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\THEME9.TPD
K:\WORK\CDIMAGE\TRIAL\THEME\UFO.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\NOKI.TMD
K:\WORK\CDIMAGE\TRIAL\THEME\UFO2.TMD
</code></pre></div></div>

<div class="box box-warning">
  <p>It looks like there’s some data corruption inside the <em>Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen</em> AFS archive…</p>
</div>

<p>But more interestingly, it can also <em>extract</em> the whole archive:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ qemu-mipsel ./unafs.elf -s 1 -xvf TENCHU/DATA
WORK
WORK\CDIMAGE
WORK\CDIMAGE\STAGE
WORK\CDIMAGE\STAGE\YAMIJOU
WORK\CDIMAGE\STAGE\YAMIJOU\TITLE_J.TIM
WORK\CDIMAGE\STAGE\YAMIJOU\TIM.TPD
...
WORK\CDIMAGE\TRIAL\THEME\MATO.TMD
WORK\CDIMAGE\TRIAL\THEME\THEME9.TPD
WORK\CDIMAGE\TRIAL\THEME\UFO.TMD
WORK\CDIMAGE\TRIAL\THEME\NOKI.TMD
WORK\CDIMAGE\TRIAL\THEME\UFO2.TMD
</code></pre></div></div>

<p>That has to be one of the most convoluted ways to write an asset extractor for a game, but it works.</p>

<div class="box box-warning">
  <p>Interestingly enough, there are files in the archive that are bigger than the available heap memory inside <code class="language-plaintext highlighter-rouge">MAIN.EXE</code>.
That means we can’t use <code class="language-plaintext highlighter-rouge">memory.o</code> delinked from <code class="language-plaintext highlighter-rouge">MAIN.EXE</code> and must shim <code class="language-plaintext highlighter-rouge">MemoryAllocate</code> and <code class="language-plaintext highlighter-rouge">MemoryFree</code> with the C standard memory heap allocator instead.</p>

  <p>What’s even more strange is that there’s apparently only one call to <code class="language-plaintext highlighter-rouge">AfsOpen</code> (coming from the VFS), which loads files into a buffer allocated dynamically from the memory heap allocated.
Either these files aren’t actually used by the game or something else’s going on.</p>
</div>

<div class="box box-information">
  <p>The files for this part can be found here: <a href="/tenchu1/assets/part-6/tenchu1.tar.gz" download="">tenchu1.tar.gz</a></p>
</div>

<h2 id="what-about-the-pc-backend">What about the PC backend?</h2>

<p>Going back to the VFS layer, now that we have extracted the assets from the AFS archive we could theoretically use the PC backend…</p>

<p>Patching <code class="language-plaintext highlighter-rouge">MAIN.EXE</code> to initialize the VFS layer with the PC backend (while still calling <code class="language-plaintext highlighter-rouge">CdInit</code> as the game still expects a CD) is fairly straightforward and DuckStation even supports PCdrv (open <code class="language-plaintext highlighter-rouge">Settings &gt; Advanced</code>, check <code class="language-plaintext highlighter-rouge">Enable PCDrv</code> under <code class="language-plaintext highlighter-rouge">Tweaks/Hacks</code> and set up the path for the PCDrv root directory).</p>

<p>Unfortunately, Tenchu uses backslashes as path separators and relies on case-insensitive paths, owning to the fact it was probably developed on Windows systems.
DuckStation doesn’t transform the path in any way and since I’m using Linux, a case-sensitive system with forward slashes as path separators, the game can’t find its files:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls '/home/boricj/Games/Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan)/K:/WORK/CDIMAGE/IMAGE/IMAGES.ARC' 
'/home/boricj/Games/Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan)/K:/WORK/CDIMAGE/IMAGE/IMAGES.ARC'
$ duckstation-qt
...
[ 2688.2700] E(HandleSyscall): PCopen: Failed to open '/home/boricj/Games/Rittai Ninja Katsugeki - Tenchu - Shinobi Gaisen (Japan)/K:\WORK\CDIMAGE\IMAGE\images.arc'
</code></pre></div></div>

<p>Even on Windows it won’t work as-is, since the game prefixes any paths with <code class="language-plaintext highlighter-rouge">K:\</code>.
It’s likely that the PC backend could be made to work again with additional efforts to fix all these issues, but I can’t be bothered to do it right now.</p>

<h2 id="wait-a-minute-whats-that-doing-here">Wait a minute, what’s that doing here?</h2>

<p>If we use <code class="language-plaintext highlighter-rouge">unafs</code> on the demo version of <em>Rittai Ninja Katsugeki Tenchu</em>, things get really interesting.
Backups of files that were probably being modified at the time the demo was finalized, lots of files that the demo probably didn’t use and things that definitively shouldn’t be there:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\$RES_UP.BAT</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\AFSMAKE.EXE</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\CD.CCS</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\CD.CTI</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\ENDING.EXE</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\GAME.EXE</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\LICENSEJ.DAT</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\PSX.EXE</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\PSX.SYM</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\RESTART.EXE</code></li>
  <li><code class="language-plaintext highlighter-rouge">K:\WORK\CDIMAGE\VOLMAKE.BAT</code></li>
</ul>

<div class="box box-information">
  <p>These files (except for <code class="language-plaintext highlighter-rouge">LICENSEJ.DAT</code>) can be found here: <a href="/tenchu1/assets/part-6/bonus.tar.gz" download="">bonus.tar.gz</a></p>
</div>

<p>We have batch files, CD-ROM generation logs, a bunch of PlayStation executables, debugging symbols for <code class="language-plaintext highlighter-rouge">PSX.EXE</code>, the Sony PlayStation license file for Japan (that can be found inside the Psy-Q SDK) and finally <code class="language-plaintext highlighter-rouge">AFSMAKE.EXE</code>, a PE i386 console executable for Windows.
The mastering of the gold disc for the demo back left some unexpected surprises.</p>

<div class="box box-information">
  <p>Turns out the Tenchu modding community already knew about these (and also had a working AFS extractor that isn’t scavenged from the corpse of an executable), but it appears this wasn’t publicly documented online before.
Still, it’s good I’ve stumbled upon these things <em>before</em> I got any deeper inside my reverse-engineering effort.</p>
</div>

<h2 id="conclusion">Conclusion</h2>

<p>We have twisted the original archive code of <em>Rittai Ninja Katsugeki Tenchu: Shinobi Gaisen</em> into a working asset extractor for the AFS file format.
We’ve also found some noteworthy artifacts inside the demo version of <em>Rittai Ninja Katsugeki Tenchu</em> that warrant further examination.</p>

<div style="display: grid; grid-template-columns: 1fr 1fr; margin-top: 1ch; margin-bottom: 1ch;">
<div style="padding-right: 15px;">


<a class="prev" href="/tenchu1/2024/03/11/part-5.html">&laquo; Decompiling Tenchu: Stealth Assassins part 5: memory mismanagement</a>

</div>
<div style="padding-left: 15px; text-align: right;">


<a href="/tenchu1/2024/03/25/part-7.html">Decompiling Tenchu: Stealth Assassins part 7: the AFSMAKE.EXE side-quest &raquo;</a>

</div>
</div>]]></content><author><name>Jean-Baptiste Boric</name></author><category term="tenchu1" /><summary type="html"><![CDATA[Previously in this series of articles, we’ve started to tear apart the game, beginning with the memory allocator. This time, we’ll take a look at another piece of the puzzle, the code responsible for the data archive.]]></summary></entry></feed>