Skip to content

Commit

Permalink
Merge pull request #67 from twevs/libgpu
Browse files Browse the repository at this point in the history
Added libgpu macro detector.
  • Loading branch information
lab313ru authored Jun 27, 2023
2 parents ddb22bd + 6095608 commit 2ef1002
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 2 deletions.
8 changes: 6 additions & 2 deletions src/main/java/psx/PsxAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
import psyq.LibgpuMacroDetector;
import psyq.SigApplier;


Expand Down Expand Up @@ -113,8 +114,11 @@ public boolean accept(File dir, String name) {

while (i.hasNext()) {
AddressRange next = i.next();

applyPsyqSignaturesByVersion(files, patchesFilePath, program, next.getMinAddress(), next.getMaxAddress(), monitor, log);
Address startAddr = next.getMinAddress();
Address endAddr = next.getMaxAddress();

applyPsyqSignaturesByVersion(files, patchesFilePath, program, startAddr, endAddr, monitor, log);
LibgpuMacroDetector.detectLibgpuMacros(program, startAddr, endAddr, monitor);
}

monitor.setMessage("Applying PsyQ functions and data types...");
Expand Down
258 changes: 258 additions & 0 deletions src/main/java/psyq/LibgpuMacroDetector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
package psyq;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileException;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.DecompileResults;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.code.CodeManager;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionManager;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.PcodeOpAST;
import ghidra.util.task.TaskMonitor;

public class LibgpuMacroDetector {

static private void SetComment(PcodeOpAST value, String comment, CodeManager codeManager) {
Address address = value.getSeqnum().getTarget();
codeManager.setComment(address, CodeUnit.PRE_COMMENT, comment);
}

static public void detectLibgpuMacros(Program program, final Address startAddr, final Address endAddr,
TaskMonitor monitor) throws Exception {
Map<Integer, String> primCodeMap = new HashMap<Integer, String>() {
{
put(0x20, "setPolyF3()");
put(0x22, "setPolyF3() + setSemiTrans(polyF3, 1)");

put(0x24, "setPolyFT3()");
put(0x26, "setPolyFT3() + setSemiTrans(polyFT3, 1)");
put(0x25, "setPolyFT3() + setShadeTex(polyFT3, 1)");
put(0x27, "setPolyFT3() + setSemiTrans(polyFT3, 1) + setShadeTex(polyFT3, 1");

put(0x30, "setPolyG3()");
put(0x32, "setPolyG3() + setSemiTrans(polyG3, 1)");

put(0x34, "setPolyGT3()");
put(0x36, "setPolyGT3() + setSemiTrans(polyGT3, 1)");

put(0x28, "setPolyF4()");
put(0x2a, "setPolyF4() + setSemiTrans(polyF4, 1)");

put(0x2c, "setPolyFT4()");
put(0x2e, "setPolyFT4() + setSemiTrans(polyFT4, 1)");
put(0x2d, "setPolyFT4() + setShadeTex(polyFT4, 1)");
put(0x2f, "setPolyFT4() + setSemiTrans(polyFT4, 1) + setShadeTex(polyFT4, 1)");

put(0x38, "setPolyG4()");
put(0x3a, "setPolyG4() + setSemiTrans(polyG4, 1)");

put(0x3c, "setPolyGT4()");
put(0x3e, "setPolyGT4() + setSemiTrans(polyGT4, 1)");

put(0x40, "setLineF2()");
put(0x42, "setLineF2() + setSemiTrans(lineF2, 1)");

put(0x50, "setLineG2()");
put(0x52, "setLineG2() + setSemiTrans(lineG2, 1)");

put(0x48, "setLineF3()");
put(0x4a, "setLineF3() + setSemiTrans(lineF3, 1)");

put(0x58, "setLineG3()");
put(0x5a, "setLineG3() + setSemiTrans(lineG3, 1)");

put(0x4c, "setLineF4()");
put(0x4e, "setLineF4() + setSemiTrans(lineF4, 1)");

put(0x5c, "setLineG4()");
put(0x5e, "setLineG4() + setSemiTrans(lineG4, 1)");

put(0x64, "setSprt()");
put(0x66, "setSprt() + setSemiTrans(sprt, 1)");
put(0x65, "setSprt() + setShadeTex(sprt, 1)");
put(0x67, "setSprt() + setSemiTrans(sprt, 1) + setShadeTex(sprt, 1)");

put(0x7c, "setSprt16()");
put(0x7e, "setSprt16() + setSemiTrans(sprt16, 1)");
put(0x7d, "setSprt16() + setShadeTex(sprt16, 1)");
put(0x7f, "setSprt16() + setSemiTrans(sprt16, 1) + setShadeTex(sprt16, 1)");

put(0x74, "setSprt8()");
put(0x76, "setSprt8() + setSemiTrans(sprt8, 1)");
put(0x75, "setSprt8() + setShadeTex(sprt8, 1)");
put(0x77, "setSprt8() + setSemiTrans(sprt8, 1) + setShadeTex(sprt8, 1)");

put(0x68, "setTile1()");
put(0x6a, "setTile1() + setSemiTrans(tile1, 1)");

put(0x78, "setTile8()");
put(0x7a, "setTile8() + setSemiTrans(tile8, 1)");

put(0x70, "setTile16()");
put(0x72, "setTile16() + setSemiTrans(tile16, 1)");

put(0x60, "setTile()");
put(0x62, "setTile() + setSemiTrans(tile, 1)");
}
};

CodeManager codeManager = ((ProgramDB) program).getCodeManager();
FunctionManager functionManager = program.getFunctionManager();
DecompileOptions options = new DecompileOptions();
DecompInterface ifc = new DecompInterface();
ifc.setOptions(options);
if (!ifc.openProgram(program)) {
throw new DecompileException("Fatal error", "The decompiler was unable to open the program.");
}

ArrayList<String> logLines = new ArrayList<String>();

for (Function function : functionManager.getFunctions(startAddr, true)) {
if (monitor.isCancelled() || function.getEntryPoint().getOffset() > endAddr.getOffset()) {
break;
}

// sprintf() triggers a huge amount of false positives for setPolyG3().
String functionName = function.getName();
if (functionName.contains("sprintf") || functionName.contains("SPRINTF")) {
continue;
}

DecompileResults decompResults = ifc.decompileFunction(function, 30, null);
HighFunction highFunction = decompResults.getHighFunction();
Iterator<PcodeOpAST> ast = highFunction.getPcodeOps();
ArrayList<PcodeOpAST> PcodeOps = new ArrayList<PcodeOpAST>();
while (ast.hasNext()) {
PcodeOps.add(ast.next());
}

for (int i = 0; i < PcodeOps.size(); i++) {

PcodeOpAST value = PcodeOps.get(i);
int opCode = value.getOpcode();

/*
* Detect primitive initializers.
*/
if (opCode == PcodeOp.INT_ADD) {

long offset = value.getInput(1).getOffset();
// The primitive code is at a 7-byte offset but compiler optimization of
// primitive initialization in loops leads to a variety of odd offsets being
// used.
if (offset % 2 == 1) {
// Sometimes the ADD and STORE used to initialize a primitive are several
// instructions apart.
// Note that the iteration count is based on testing on the Metal Gear Solid
// binary and may not be optimal for other games.
for (int j = i + 1; j < i + 8; j++) {

if (j < PcodeOps.size()) {

value = PcodeOps.get(j);
if (value.getOpcode() == PcodeOp.STORE
&& value.getInput(2).isConstant()) {

long primCode = value.getInput(2).getOffset();
if (primCodeMap.containsKey((int) primCode)) {

String confidence = (offset == 7) ? "Probable" : "Possible";
String macro = primCodeMap.get((int) primCode);
SetComment(value, confidence + " PsyQ macro: " + macro, codeManager);
logLines.add(value.getSeqnum().getTarget().toString() + ": " + macro);
}
break;
}
}
}
}

/*
* Detect addPrim() and nextPrim().
*/
} else if (opCode == PcodeOp.INT_AND
&& value.getInput(0).isRegister()) {

long offset = value.getInput(1).getOffset();
long register1 = value.getInput(0).getOffset();
if (i + 1 < PcodeOps.size()) {

value = PcodeOps.get(i + 1);
if (offset == 0xff000000L
&& value.getOpcode() == PcodeOp.INT_AND
&& value.getInput(0).isRegister()
&& value.getInput(1).getOffset() == 0xffffff) {

long register2 = value.getInput(0).getOffset();
value = PcodeOps.get(i + 2);
if (value.getOpcode() == PcodeOp.INT_OR
&& value.getInput(0).getOffset() == register1
&& value.getInput(1).getOffset() == register2) {

SetComment(value, "Probable PsyQ macro: addPrim().", codeManager);
logLines.add(value.getSeqnum().getTarget().toString() + ": addPrim()");
}
} else if (offset == 0xffffffL
&& value.getOpcode() == PcodeOp.INT_OR
&& value.getInput(0).isRegister()
&& value.getInput(0).getOffset() == register1
&& value.getInput(1).getOffset() == 0x80000000L) {

SetComment(value,
"Probable PsyQ macro: nextPrim(), with pattern: *pPrim & 0xffffff | 0x80000000.",
codeManager);
logLines.add(value.getSeqnum().getTarget().toString() + ": nextPrim()");
}
}

/*
* Detect get/setClut().
*/
} else if (opCode == PcodeOp.INT_RIGHT
&& value.getInput(1).getOffset() == 0x4) {

// Sometimes there is a CAST between the right shift and the bitwise and.
value = (PcodeOps.get(i + 1).getOpcode() == PcodeOp.CAST) ? PcodeOps.get(i + 2)
: PcodeOps.get(i + 1);

if (value.getOpcode() == PcodeOp.INT_AND
&& value.getInput(1).getOffset() == 0x3f) {

SetComment(value, "Probable PsyQ macro: get/setClut(), with pattern: y << 6 | x >> 4 & 0x3f.",
codeManager);
logLines.add(value.getSeqnum().getTarget().toString() + ": get/setClut()");
}

/*
* Detect setDrawTPage().
*/
} else if ((opCode == PcodeOp.STORE && (value.getInput(2).getOffset() & 0xfffff000) == 0xe1000000L)
|| (opCode == PcodeOp.INT_OR && (value.getInput(1).getOffset() & 0xfffff000) == 0xe1000000L)) {

SetComment(value,
"Probable PsyQ macro: setDrawTPage() if setlen(p, 1), setDrawMode() if setlen(p, 2).",
codeManager);
logLines.add(value.getSeqnum().getTarget().toString() + ": setDrawTPage() or setDrawMode()");
}
}
}

// Path log = Paths.get("log.txt");
// Files.write(log, logLines, StandardCharsets.UTF_8);
}
}

0 comments on commit 2ef1002

Please sign in to comment.