/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import jpcsp.HLE.Modules;
import jpcsp.HLE.kernel.types.SceModule;
public class NIDMapper {
private static Logger log = Modules.log;
private static NIDMapper instance;
private final Map<Integer, NIDInfo> syscallMap;
private final Map<String, Map<Integer, NIDInfo>> moduleNidMap;
private final Map<Integer, NIDInfo> nidMap;
private final Map<Integer, NIDInfo> addressMap;
private final Map<String, NIDInfo> nameMap;
private int freeSyscallNumber;
protected static class NIDInfo {
private final int nid;
private final int syscall;
private int address;
private final String name;
private final String moduleName;
private int firmwareVersion;
private boolean overwritten;
private boolean loaded;
private boolean validModuleName;
/**
* New NIDInfo for a NID from a loaded module.
*
* @param nid
* @param address
* @param moduleName
*/
public NIDInfo(int nid, int address, String moduleName) {
this.nid = nid;
this.address = address;
this.moduleName = moduleName;
name = null;
syscall = -1;
firmwareVersion = 999;
overwritten = false;
loaded = true;
validModuleName = true;
}
/**
* New NIDInfo for a NID from an HLE syscall.
*
* @param nid
* @param syscall
* @param name
* @param moduleName
* @param firmwareVersion
*/
public NIDInfo(int nid, int syscall, String name, String moduleName, int firmwareVersion) {
this.nid = nid;
this.syscall = syscall;
this.name = name;
this.moduleName = moduleName;
this.firmwareVersion = firmwareVersion;
address = 0;
overwritten = false;
loaded = true;
validModuleName = false; // the given moduleName is probably not the correct one...
}
public int getNid() {
return nid;
}
public int getSyscall() {
return syscall;
}
public boolean hasSyscall() {
return syscall >= 0;
}
public int getAddress() {
return address;
}
private void setAddress(int address) {
this.address = address;
}
public boolean hasAddress() {
return address != 0;
}
public String getName() {
return name;
}
public boolean hasName() {
return name != null && name.length() > 0;
}
public String getModuleName() {
return moduleName;
}
public boolean isOverwritten() {
return overwritten;
}
private void setOverwritten(boolean overwritten) {
this.overwritten = overwritten;
}
public void overwrite(int address) {
setOverwritten(true);
setAddress(address);
}
public void undoOverwrite() {
setOverwritten(false);
setAddress(0);
}
public int getFirmwareVersion() {
return firmwareVersion;
}
public void setFirmwareVersion(int firmwareVersion) {
this.firmwareVersion = firmwareVersion;
}
public boolean isFromModule(String moduleName) {
return moduleName.equals(this.moduleName);
}
public boolean isLoaded() {
return loaded;
}
public void setLoaded(boolean loaded) {
this.loaded = loaded;
}
public boolean isValidModuleName() {
return validModuleName;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
if (name != null) {
s.append(String.format("%s(nid=0x%08X)", name, nid));
} else {
s.append(String.format("nid=0x%08X", nid));
}
s.append(String.format(", moduleName='%s'", moduleName));
if (!isValidModuleName()) {
s.append("(probably invalid)");
}
s.append(String.format(", firmwareVersion=%d", firmwareVersion));
if (isOverwritten()) {
s.append(", overwritten");
}
if (hasAddress()) {
s.append(String.format(", address=0x%08X", address));
}
if (hasSyscall()) {
s.append(String.format(", syscall=0x%X", syscall));
}
return s.toString();
}
}
public static NIDMapper getInstance() {
if (instance == null) {
instance = new NIDMapper();
}
return instance;
}
private NIDMapper() {
moduleNidMap = new HashMap<>();
nidMap = new HashMap<>();
syscallMap = new HashMap<>();
addressMap = new HashMap<>();
nameMap = new HashMap<>();
// Official syscalls start at 0x2000,
// so we'll put the HLE syscalls far away at 0x4000.
freeSyscallNumber = 0x4000;
}
private void addNIDInfo(NIDInfo info) {
Map<Integer, NIDInfo> moduleMap = moduleNidMap.get(info.getModuleName());
if (moduleMap == null) {
moduleMap = new HashMap<Integer, NIDInfo>();
moduleNidMap.put(info.getModuleName(), moduleMap);
}
moduleMap.put(info.getNid(), info);
// For HLE NID's, do not trust the module names defined in Jpcsp, use only the NID.
if (!info.isValidModuleName()) {
nidMap.put(info.getNid(), info);
}
if (info.hasAddress()) {
addressMap.put(info.getAddress(), info);
}
if (info.hasSyscall()) {
syscallMap.put(info.getSyscall(), info);
}
if (info.hasName()) {
nameMap.put(info.getName(), info);
}
}
private void removeNIDInfo(NIDInfo info) {
Map<Integer, NIDInfo> moduleMap = moduleNidMap.get(info.getModuleName());
if (moduleMap != null) {
moduleMap.remove(info.getNid());
if (moduleMap.isEmpty()) {
moduleMap.remove(info.getModuleName());
}
}
// For HLE NID's, do not trust the module names defined in Jpcsp, use only the NID.
if (!info.isValidModuleName()) {
nidMap.remove(info.getNid());
}
if (info.hasAddress()) {
addressMap.remove(info.getAddress());
}
if (info.hasSyscall()) {
syscallMap.remove(info.getSyscall());
}
if (info.hasName()) {
syscallMap.remove(info.getName());
}
}
private NIDInfo getNIDInfoByNid(String moduleName, int nid) {
NIDInfo info = null;
Map<Integer, NIDInfo> moduleMap = moduleNidMap.get(moduleName);
if (moduleMap != null) {
info = moduleMap.get(nid);
}
// For HLE NID's, do not trust the module names defined in Jpcsp, use only the NID.
if (info == null) {
info = nidMap.get(nid);
}
return info;
}
private NIDInfo getNIDInfoBySyscall(int syscall) {
return syscallMap.get(syscall);
}
private NIDInfo getNIDInfoByAddress(int address) {
return addressMap.get(address);
}
private NIDInfo getNIDInfoByName(String name) {
return nameMap.get(name);
}
public int getNewSyscallNumber() {
return freeSyscallNumber++;
}
/**
* Add a NID from an HLE syscall.
*
* @param nid the nid
* @param name the function name
* @param moduleName the module name
* @param firmwareVersion the firmware version defining this nid
* @return true if the NID has been added
* false if the NID was already added
*/
public boolean addHLENid(int nid, String name, String moduleName, int firmwareVersion) {
NIDInfo info = getNIDInfoByNid(moduleName, nid);
if (info != null) {
// This NID is already added, verify that we are trying to use the same data
if (!name.equals(info.getName()) || !moduleName.equals(info.getModuleName()) || firmwareVersion != info.getFirmwareVersion()) {
return false;
}
return true;
}
int syscall = getNewSyscallNumber();
info = new NIDInfo(nid, syscall, name, moduleName, firmwareVersion);
addNIDInfo(info);
return true;
}
/**
* Add a NID loaded from a module.
*
* @param module the loaded module
* @param moduleName the module name
* @param nid the nid
* @param address the address of the nid
*/
public void addModuleNid(SceModule module, String moduleName, int nid, int address) {
NIDInfo info = getNIDInfoByNid(moduleName, nid);
if (info != null) {
// Only modules from flash0 are allowed to overwrite NIDs from syscalls
if (module.pspfilename == null || !module.pspfilename.startsWith("flash0:")) {
return;
}
if (log.isInfoEnabled()) {
log.info(String.format("NID %s[0x%08X] at address 0x%08X from module '%s' overwriting an HLE syscall", info.getName(), nid, address, moduleName));
}
info.overwrite(address);
addressMap.put(address, info);
} else {
info = new NIDInfo(nid, address, moduleName);
addNIDInfo(info);
}
}
/**
* Remove all the NIDs that have been loaded from a module.
*
* @param moduleName the module name
*/
public void removeModuleNids(String moduleName) {
List<NIDInfo> nidsToBeRemoved = new LinkedList<NIDInfo>();
List<Integer> addressesToBeRemoved = new LinkedList<Integer>();
for (NIDInfo info : addressMap.values()) {
if (info.isFromModule(moduleName)) {
if (info.isOverwritten()) {
addressesToBeRemoved.add(info.getAddress());
info.undoOverwrite();
} else {
nidsToBeRemoved.add(info);
}
}
}
for (NIDInfo info : nidsToBeRemoved) {
removeNIDInfo(info);
}
for (Integer address : addressesToBeRemoved) {
addressMap.remove(address);
}
}
public int getAddressByNid(int nid, String moduleName) {
NIDInfo info = getNIDInfoByNid(moduleName, nid);
if (info == null || !info.hasAddress()) {
return 0;
}
if (moduleName != null && !info.isFromModule(moduleName)) {
log.debug(String.format("Trying to resolve %s from module '%s'", info, moduleName));
}
return info.getAddress();
}
public int getAddressByNid(int nid) {
return getAddressByNid(nid, null);
}
public int getAddressBySyscall(int syscall) {
NIDInfo info = getNIDInfoBySyscall(syscall);
if (info == null || !info.hasAddress()) {
return 0;
}
return info.getAddress();
}
public int getAddressByName(String name) {
NIDInfo info = getNIDInfoByName(name);
if (info == null || !info.hasAddress()) {
return 0;
}
return info.getAddress();
}
public int getSyscallByNid(int nid, String moduleName) {
NIDInfo info = getNIDInfoByNid(moduleName, nid);
if (info == null || !info.hasSyscall()) {
return -1;
}
if (moduleName != null && !info.isFromModule(moduleName)) {
log.debug(String.format("Trying to resolve %s from module '%s'", info, moduleName));
}
return info.getSyscall();
}
public int getSyscallByNid(int nid) {
return getSyscallByNid(nid, null);
}
public String getNameBySyscall(int syscall) {
NIDInfo info = getNIDInfoBySyscall(syscall);
if (info == null) {
return null;
}
return info.getName();
}
public int getNidBySyscall(int syscall) {
NIDInfo info = getNIDInfoBySyscall(syscall);
if (info == null) {
return 0;
}
return info.getNid();
}
public int getNidByAddress(int address) {
NIDInfo info = getNIDInfoByAddress(address);
if (info == null) {
return 0;
}
return info.getNid();
}
public void unloadNid(int nid) {
// Search for the NID in all the modules
for (String moduleName : moduleNidMap.keySet()) {
NIDInfo info = getNIDInfoByNid(moduleName, nid);
if (info != null) {
info.setLoaded(false);
}
}
}
public void unloadAll() {
for (Map<Integer, NIDInfo> moduleMap : moduleNidMap.values()) {
for (NIDInfo info : moduleMap.values()) {
if (info.isOverwritten()) {
info.undoOverwrite();
}
info.setLoaded(false);
}
}
}
}