Compare commits
2 Commits
027829659c
...
4dc6fb2176
Author | SHA1 | Date |
---|---|---|
|
4dc6fb2176 | |
|
836c856258 |
|
@ -17,7 +17,6 @@ public class DumpCurrentFunction extends GhidraScript {
|
||||||
|
|
||||||
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
||||||
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
||||||
globalDumper.loadGlobalManifest();
|
|
||||||
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
||||||
|
|
||||||
Function currentFunction = getFunctionContaining(currentAddress);
|
Function currentFunction = getFunctionContaining(currentAddress);
|
||||||
|
@ -31,7 +30,6 @@ public class DumpCurrentFunction extends GhidraScript {
|
||||||
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
||||||
|
|
||||||
globalDumper.dumpGlobals();
|
globalDumper.dumpGlobals();
|
||||||
globalDumper.saveGlobalManifest();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ public class DumpCurrentFunctionFix extends GhidraScript {
|
||||||
|
|
||||||
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
||||||
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
||||||
globalDumper.loadGlobalManifest();
|
|
||||||
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
||||||
|
|
||||||
// Force Fix type instead of Auto
|
// Force Fix type instead of Auto
|
||||||
|
@ -34,7 +33,6 @@ public class DumpCurrentFunctionFix extends GhidraScript {
|
||||||
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
||||||
|
|
||||||
globalDumper.dumpGlobals();
|
globalDumper.dumpGlobals();
|
||||||
globalDumper.saveGlobalManifest();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
// Decompile selected function recursively (until a given number of new functions is reached)
|
// Decompile selected function recursively (until a given number of new functions is reached)
|
||||||
// @category _Reman3
|
// @category _Reman3
|
||||||
// @menupath Reman3.Dump N Functions
|
// @menupath Reman3.Dump N Functions
|
||||||
|
// @importpackage org.sqlite
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -50,7 +51,6 @@ public class DumpCurrentFunctionN extends GhidraScript {
|
||||||
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
||||||
|
|
||||||
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
||||||
globalDumper.loadGlobalManifest();
|
|
||||||
|
|
||||||
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Decompile selected function recursively
|
// Decompile selected function recursively
|
||||||
// @category _Reman3
|
// @category _Reman3
|
||||||
// @menupath Reman3.Dump Current Function (recursive)
|
// @menupath Reman3.Dump Current Function (recursive)
|
||||||
|
// @importpackage org.sqlite
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -22,7 +23,6 @@ public class DumpCurrentFunctionRecursive extends GhidraScript {
|
||||||
|
|
||||||
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
||||||
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
||||||
globalDumper.loadGlobalManifest();
|
|
||||||
|
|
||||||
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
FunctionDumper functionDumper = new FunctionDumper(this, functionDatabase, globalDumper);
|
||||||
|
|
||||||
|
@ -33,13 +33,24 @@ public class DumpCurrentFunctionRecursive extends GhidraScript {
|
||||||
List<Function> functionsToDump = new ArrayList<>();
|
List<Function> functionsToDump = new ArrayList<>();
|
||||||
List<Function> functionsToDumpNew = new ArrayList<>();
|
List<Function> functionsToDumpNew = new ArrayList<>();
|
||||||
for (Function func : tracer.out) {
|
for (Function func : tracer.out) {
|
||||||
if (FunctionDumper.isDumpedFix(func))
|
List<FunctionDatabase.FunctionEntry> entries = functionDatabase.findEntriesByAddress(func.getEntryPoint());
|
||||||
|
boolean shouldDump = true;
|
||||||
|
boolean isNew = true;
|
||||||
|
for (FunctionDatabase.FunctionEntry entry : entries) {
|
||||||
|
if (entry.type == FunctionDatabase.Type.Fix) {
|
||||||
|
shouldDump = false;
|
||||||
|
}
|
||||||
|
if (entry.type == FunctionDatabase.Type.Fix || entry.type == FunctionDatabase.Type.Auto) {
|
||||||
|
isNew = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!shouldDump)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
println("Dump: " + func.getName());
|
println("Dump: " + func.getName());
|
||||||
functionsToDump.add(func);
|
functionsToDump.add(func);
|
||||||
|
|
||||||
if (!FunctionDumper.isDumpedAuto(func))
|
if (isNew)
|
||||||
functionsToDumpNew.add(func);
|
functionsToDumpNew.add(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +81,6 @@ public class DumpCurrentFunctionRecursive extends GhidraScript {
|
||||||
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
||||||
|
|
||||||
globalDumper.dumpGlobals();
|
globalDumper.dumpGlobals();
|
||||||
globalDumper.saveGlobalManifest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump types
|
// Dump types
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Script to refresh all custom globals & types from Ghidra
|
// Script to refresh all custom globals & types from Ghidra
|
||||||
// @category _Reman3
|
// @category _Reman3
|
||||||
// @menupath Reman3.Redump Globals and Types
|
// @menupath Reman3.Redump Globals and Types
|
||||||
|
// @importpackage org.sqlite
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import re3lib.GlobalDumper;
|
import re3lib.GlobalDumper;
|
||||||
|
@ -16,10 +17,8 @@ public class DumpGlobals extends GhidraScript {
|
||||||
|
|
||||||
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
||||||
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
||||||
globalDumper.loadGlobalManifest();
|
|
||||||
|
|
||||||
globalDumper.dumpGlobals();
|
globalDumper.dumpGlobals();
|
||||||
globalDumper.saveGlobalManifest();
|
|
||||||
|
|
||||||
TypeDumper dumper = new TypeDumper(this);
|
TypeDumper dumper = new TypeDumper(this);
|
||||||
dumper.run();
|
dumper.run();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Script to dump all custom types from Ghidra
|
// Script to dump all custom types from Ghidra
|
||||||
// @category _Reman3
|
// @category _Reman3
|
||||||
// @menupath Reman3.Dump Types
|
// @menupath Reman3.Dump Types
|
||||||
|
// @importpackage org.sqlite
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.DataType;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Script to find hardcoded addresses in the binary that need to be relocated
|
// Script to find hardcoded addresses in the binary that need to be relocated
|
||||||
// @category _Reman3
|
// @category _Reman3
|
||||||
// @menupath Reman3.Find and dump Relocations
|
// @menupath Reman3.Find and dump Relocations
|
||||||
|
// @importpackage org.sqlite
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Script to regenerate all dumped stub functions
|
// Script to regenerate all dumped stub functions
|
||||||
// @category _Reman3
|
// @category _Reman3
|
||||||
// @menupath Reman3.Redump Stub Functions
|
// @menupath Reman3.Redump Stub Functions
|
||||||
|
// @importpackage org.sqlite
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// Script to sanitize global symbols in Ghidra
|
// Script to sanitize global symbols in Ghidra
|
||||||
// @category _Reman3
|
// @category _Reman3
|
||||||
// @menupath Reman3.Sanitize Global Symbols
|
// @menupath Reman3.Sanitize Global Symbols
|
||||||
|
// @importpackage org.sqlite
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import re3lib.GlobalDumper;
|
import re3lib.GlobalDumper;
|
||||||
import re3lib.RemanConfig;
|
import re3lib.RemanConfig;
|
||||||
|
import re3lib.FunctionDatabase;
|
||||||
|
|
||||||
public class SanitizeGlobalSymbols extends GhidraScript {
|
public class SanitizeGlobalSymbols extends GhidraScript {
|
||||||
@Override
|
@Override
|
||||||
|
@ -12,12 +14,12 @@ public class SanitizeGlobalSymbols extends GhidraScript {
|
||||||
RemanConfig.INSTANCE = new RemanConfig(this);
|
RemanConfig.INSTANCE = new RemanConfig(this);
|
||||||
RemanConfig.INSTANCE.createDirectories();
|
RemanConfig.INSTANCE.createDirectories();
|
||||||
|
|
||||||
GlobalDumper globalDumper = new GlobalDumper(this);
|
try (FunctionDatabase functionDatabase = new FunctionDatabase(this)) {
|
||||||
globalDumper.loadGlobalManifest();
|
GlobalDumper globalDumper = new GlobalDumper(this, functionDatabase);
|
||||||
|
|
||||||
globalDumper.sanitizeGlobalSymbols();
|
globalDumper.sanitizeGlobalSymbols();
|
||||||
|
|
||||||
globalDumper.dumpGlobals();
|
globalDumper.dumpGlobals();
|
||||||
globalDumper.saveGlobalManifest();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -673,7 +673,6 @@ public class FunctionDatabase implements AutoCloseable {
|
||||||
// Update CMake timestamp
|
// Update CMake timestamp
|
||||||
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
RemanConfig.INSTANCE.touchCMakeTimestamp();
|
||||||
globalDumper.dumpGlobals();
|
globalDumper.dumpGlobals();
|
||||||
globalDumper.saveGlobalManifest();
|
|
||||||
|
|
||||||
TypeDumper typeDumper = new TypeDumper(script);
|
TypeDumper typeDumper = new TypeDumper(script);
|
||||||
typeDumper.run();
|
typeDumper.run();
|
||||||
|
|
|
@ -48,7 +48,6 @@ public class GlobalDumper {
|
||||||
|
|
||||||
GhidraScript script;
|
GhidraScript script;
|
||||||
FunctionDatabase functionDatabase;
|
FunctionDatabase functionDatabase;
|
||||||
HashMap<Address, GlobalRec> globalAddrs = new HashMap<>();
|
|
||||||
|
|
||||||
public GlobalDumper(GhidraScript script, FunctionDatabase functionDatabase) {
|
public GlobalDumper(GhidraScript script, FunctionDatabase functionDatabase) {
|
||||||
this.script = script;
|
this.script = script;
|
||||||
|
@ -56,57 +55,14 @@ public class GlobalDumper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeGlobalManifest() {
|
public void removeGlobalManifest() {
|
||||||
// Remove globals from database instead of file
|
// Remove globals from database
|
||||||
try {
|
try {
|
||||||
functionDatabase.removeGlobalsByFilepath(RemanConfig.INSTANCE.GLOBAL_H_FILE);
|
functionDatabase.removeGlobalsByFilepath(RemanConfig.INSTANCE.GLOBAL_H_FILE);
|
||||||
globalAddrs.clear();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
script.println("Error removing global manifest: " + e.getMessage());
|
script.println("Error removing global manifest: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean loadGlobalManifest() throws Exception {
|
|
||||||
globalAddrs.clear();
|
|
||||||
|
|
||||||
// Load globals from database
|
|
||||||
List<FunctionDatabase.GlobalEntry> dbGlobals = functionDatabase.loadAllGlobals();
|
|
||||||
|
|
||||||
// Get the dataTypeManagerService for parsing types
|
|
||||||
DataTypeManagerService dataTypeManagerService = (DataTypeManagerService) script.getState().getTool()
|
|
||||||
.getService(DataTypeManagerService.class);
|
|
||||||
DataTypeManager dtm = script.getCurrentProgram().getDataTypeManager();
|
|
||||||
DataTypeParser dtp = new DataTypeParser(dataTypeManagerService, AllowedDataTypes.ALL);
|
|
||||||
|
|
||||||
for (FunctionDatabase.GlobalEntry entry : dbGlobals) {
|
|
||||||
// Note: The database stores type as string, need to reconstruct DataType
|
|
||||||
// For now, we'll parse it back from the type string stored in database
|
|
||||||
// This is a limitation of moving from the manifest format
|
|
||||||
DataType type = null;
|
|
||||||
|
|
||||||
// Try to get from existing data at address
|
|
||||||
Data data = script.getDataAt(entry.address);
|
|
||||||
if (data != null) {
|
|
||||||
type = data.getDataType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == null) {
|
|
||||||
script.println("WARNING: Could not reconstruct type for global: " + entry.name + " at " + entry.address);
|
|
||||||
type = dtp.parse("undefined4");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the name again
|
|
||||||
Symbol sym = script.getSymbolAt(entry.address);
|
|
||||||
if (sym != null) {
|
|
||||||
entry.name = sym.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
globalAddrs.put(entry.address, new GlobalRec(entry.address, entry.name, type));
|
|
||||||
}
|
|
||||||
|
|
||||||
script.println("Loaded " + globalAddrs.size() + " globals from database");
|
|
||||||
return !globalAddrs.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
String escapeCString(String str) {
|
String escapeCString(String str) {
|
||||||
str = str.replace("\\", "\\\\");
|
str = str.replace("\\", "\\\\");
|
||||||
str = str.replace("\"", "\\\"");
|
str = str.replace("\"", "\\\"");
|
||||||
|
@ -149,7 +105,39 @@ public class GlobalDumper {
|
||||||
cwriter.println("#include <r3/binders/global.h>");
|
cwriter.println("#include <r3/binders/global.h>");
|
||||||
hwriter.println();
|
hwriter.println();
|
||||||
|
|
||||||
List<GlobalRec> globals = new ArrayList<>(globalAddrs.values());
|
// Load globals directly from database
|
||||||
|
List<FunctionDatabase.GlobalEntry> dbGlobals = functionDatabase.loadAllGlobals();
|
||||||
|
List<GlobalRec> globals = new ArrayList<>();
|
||||||
|
|
||||||
|
// Convert database entries to GlobalRec objects
|
||||||
|
for (FunctionDatabase.GlobalEntry entry : dbGlobals) {
|
||||||
|
DataType type = null;
|
||||||
|
|
||||||
|
// Try to get from existing data at address
|
||||||
|
Data data = script.getDataAt(entry.address);
|
||||||
|
if (data != null) {
|
||||||
|
type = data.getDataType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
script.println("WARNING: Could not determine type for global: " + entry.name + " at " + entry.address);
|
||||||
|
// Get the dataTypeManagerService for parsing types
|
||||||
|
DataTypeManagerService dataTypeManagerService = (DataTypeManagerService) script.getState().getTool()
|
||||||
|
.getService(DataTypeManagerService.class);
|
||||||
|
DataTypeParser dtp = new DataTypeParser(dataTypeManagerService, AllowedDataTypes.ALL);
|
||||||
|
type = dtp.parse("undefined4");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the current symbol name (may have changed since stored)
|
||||||
|
Symbol sym = script.getSymbolAt(entry.address);
|
||||||
|
String currentName = entry.name;
|
||||||
|
if (sym != null) {
|
||||||
|
currentName = sym.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
globals.add(new GlobalRec(entry.address, currentName, type));
|
||||||
|
}
|
||||||
|
|
||||||
globals.sort((o1, o2) -> o1.address.compareTo(o2.address));
|
globals.sort((o1, o2) -> o1.address.compareTo(o2.address));
|
||||||
|
|
||||||
for (GlobalRec global : globals) {
|
for (GlobalRec global : globals) {
|
||||||
|
@ -217,19 +205,6 @@ public class GlobalDumper {
|
||||||
cwriter.close();
|
cwriter.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveGlobalManifest() throws Exception {
|
|
||||||
// Save globals to database instead of file
|
|
||||||
script.println("Saving globals to database");
|
|
||||||
|
|
||||||
// Clear existing globals for the default filepath
|
|
||||||
functionDatabase.removeGlobalsByFilepath(RemanConfig.GLOBAL_H_FILE);
|
|
||||||
|
|
||||||
// Add all current globals to database
|
|
||||||
for (GlobalRec global : globalAddrs.values()) {
|
|
||||||
functionDatabase.addGlobal(global.address, global.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addGlobal(Address addr, HighSymbol sym) throws Exception {
|
public void addGlobal(Address addr, HighSymbol sym) throws Exception {
|
||||||
if (sym.getName().equals("ExceptionList")) {
|
if (sym.getName().equals("ExceptionList")) {
|
||||||
return;
|
return;
|
||||||
|
@ -257,23 +232,30 @@ public class GlobalDumper {
|
||||||
}
|
}
|
||||||
// script.println("Global: " + addr + " - " + sym.getName() + " - " +
|
// script.println("Global: " + addr + " - " + sym.getName() + " - " +
|
||||||
// dt.getDisplayName());
|
// dt.getDisplayName());
|
||||||
globalAddrs.put(addr, new GlobalRec(addr, sym.getName(), dt));
|
|
||||||
|
// Add directly to database instead of storing in memory
|
||||||
|
functionDatabase.addGlobal(addr, sym.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sanitizeGlobalSymbols() {
|
public void sanitizeGlobalSymbols() throws Exception {
|
||||||
for (GlobalRec global : globalAddrs.values()) {
|
// Load globals from database, sanitize symbol names, and update both Ghidra symbols and database
|
||||||
String sanitizedName = Utils.sanitizeIdentifier(global.name);
|
List<FunctionDatabase.GlobalEntry> dbGlobals = functionDatabase.loadAllGlobals();
|
||||||
if (!sanitizedName.equals(global.name)) {
|
|
||||||
Symbol symbol = script.getSymbolAt(global.address);
|
for (FunctionDatabase.GlobalEntry entry : dbGlobals) {
|
||||||
|
String sanitizedName = Utils.sanitizeIdentifier(entry.name);
|
||||||
|
if (!sanitizedName.equals(entry.name)) {
|
||||||
|
Symbol symbol = script.getSymbolAt(entry.address);
|
||||||
if (symbol != null) {
|
if (symbol != null) {
|
||||||
script.println("Renaming global symbol: " + global.name + " -> " + sanitizedName);
|
script.println("Renaming global symbol: " + entry.name + " -> " + sanitizedName);
|
||||||
AddLabelCmd cmd = new AddLabelCmd(global.address, sanitizedName,
|
AddLabelCmd cmd = new AddLabelCmd(entry.address, sanitizedName,
|
||||||
symbol.getParentNamespace(),
|
symbol.getParentNamespace(),
|
||||||
SourceType.USER_DEFINED);
|
SourceType.USER_DEFINED);
|
||||||
if (!cmd.applyTo(script.getCurrentProgram())) {
|
if (!cmd.applyTo(script.getCurrentProgram())) {
|
||||||
script.println("Error renaming symbol: " + cmd.getStatusMsg());
|
script.println("Error renaming symbol: " + cmd.getStatusMsg());
|
||||||
|
} else {
|
||||||
|
// Update the database with the new name
|
||||||
|
functionDatabase.addGlobal(entry.address, sanitizedName);
|
||||||
}
|
}
|
||||||
global.name = sanitizedName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class RemanConfig {
|
||||||
|
|
||||||
this.script = script;
|
this.script = script;
|
||||||
|
|
||||||
rootDir = new File(script.getSourceFile().getAbsolutePath()).getParentFile().getParentFile().getParentFile().toString();
|
rootDir = new File(script.getSourceFile().getAbsolutePath()).getParentFile().getParentFile().toString();
|
||||||
outputDir = new File(rootDir, RECOMPILE_PREFIX).toString();
|
outputDir = new File(rootDir, RECOMPILE_PREFIX).toString();
|
||||||
script.println("Output path: " + outputDir);
|
script.println("Output path: " + outputDir);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue