Example #1
0
@HLELogging
public class sceMeCore_driver extends HLEModule {
  public static Logger log = Modules.getLogger("sceMeCore_driver");

  @Override
  public String getName() {
    return "sceMeCore_driver";
  }

  @HLEUnimplemented
  @HLEFunction(nid = 0x051C1601, version = 500)
  public int sceMeBootStart500(int unknown) {
    return 0;
  }
}
Example #2
0
public class sceNp extends HLEModule {
  public static Logger log = Modules.getLogger("sceNp");

  protected boolean initialized;

  @Override
  public void start() {
    initialized = false;
    super.start();
  }

  /**
   * Initialization.
   *
   * @return
   */
  @HLEUnimplemented
  @HLEFunction(nid = 0x857B47D3, version = 150, checkInsideInterrupt = true)
  public int sceNp_857B47D3() {
    // No parameters
    initialized = true;

    return 0;
  }

  /**
   * Termination.
   *
   * @return
   */
  @HLEUnimplemented
  @HLEFunction(nid = 0x37E1E274, version = 150, checkInsideInterrupt = true)
  public int sceNp_37E1E274() {
    // No parameters
    initialized = false;

    return 0;
  }
}
Example #3
0
public class sceGe_user extends HLEModule {
  public static Logger log = Modules.getLogger("sceGe_user");

  public volatile boolean waitingForSync;
  public volatile boolean syncDone;
  private HashMap<Integer, SceKernelCallbackInfo> signalCallbacks;
  private HashMap<Integer, SceKernelCallbackInfo> finishCallbacks;
  private static final String geCallbackPurpose = "sceGeCallback";

  // PSP has an array of 64 GE lists
  private static final int NUMBER_GE_LISTS = 64;
  private PspGeList[] allGeLists;
  private ConcurrentLinkedQueue<PspGeList> listFreeQueue;

  private ConcurrentLinkedQueue<Integer> deferredThreadWakeupQueue;

  public static final int PSP_GE_LIST_DONE = 0;
  public static final int PSP_GE_LIST_QUEUED = 1;
  public static final int PSP_GE_LIST_DRAWING = 2;
  public static final int PSP_GE_LIST_STALL_REACHED = 3;
  public static final int PSP_GE_LIST_END_REACHED = 4;
  public static final int PSP_GE_LIST_CANCEL_DONE = 5;
  public static final String[] PSP_GE_LIST_STRINGS = {
    "PSP_GE_LIST_DONE",
    "PSP_GE_LIST_QUEUED",
    "PSP_GE_LIST_DRAWING",
    "PSP_GE_LIST_STALL_REACHED",
    "PSP_GE_LIST_END_REACHED",
    "PSP_GE_LIST_CANCEL_DONE"
  };

  public static final int PSP_GE_SIGNAL_HANDLER_SUSPEND = 0x01;
  public static final int PSP_GE_SIGNAL_HANDLER_CONTINUE = 0x02;
  public static final int PSP_GE_SIGNAL_HANDLER_PAUSE = 0x03;
  public static final int PSP_GE_SIGNAL_SYNC = 0x08;
  public static final int PSP_GE_SIGNAL_JUMP = 0x10;
  public static final int PSP_GE_SIGNAL_CALL = 0x11;
  public static final int PSP_GE_SIGNAL_RETURN = 0x12;
  public static final int PSP_GE_SIGNAL_TBP0_REL = 0x20;
  public static final int PSP_GE_SIGNAL_TBP1_REL = 0x21;
  public static final int PSP_GE_SIGNAL_TBP2_REL = 0x22;
  public static final int PSP_GE_SIGNAL_TBP3_REL = 0x23;
  public static final int PSP_GE_SIGNAL_TBP4_REL = 0x24;
  public static final int PSP_GE_SIGNAL_TBP5_REL = 0x25;
  public static final int PSP_GE_SIGNAL_TBP6_REL = 0x26;
  public static final int PSP_GE_SIGNAL_TBP7_REL = 0x27;
  public static final int PSP_GE_SIGNAL_TBP0_REL_OFFSET = 0x28;
  public static final int PSP_GE_SIGNAL_TBP1_REL_OFFSET = 0x29;
  public static final int PSP_GE_SIGNAL_TBP2_REL_OFFSET = 0x2A;
  public static final int PSP_GE_SIGNAL_TBP3_REL_OFFSET = 0x2B;
  public static final int PSP_GE_SIGNAL_TBP4_REL_OFFSET = 0x2C;
  public static final int PSP_GE_SIGNAL_TBP5_REL_OFFSET = 0x2D;
  public static final int PSP_GE_SIGNAL_TBP6_REL_OFFSET = 0x2E;
  public static final int PSP_GE_SIGNAL_TBP7_REL_OFFSET = 0x2F;
  public static final int PSP_GE_SIGNAL_BREAK = 0xFF;

  public static final int PSP_GE_MATRIX_BONE0 = 0;
  public static final int PSP_GE_MATRIX_BONE1 = 1;
  public static final int PSP_GE_MATRIX_BONE2 = 2;
  public static final int PSP_GE_MATRIX_BONE3 = 3;
  public static final int PSP_GE_MATRIX_BONE4 = 4;
  public static final int PSP_GE_MATRIX_BONE5 = 5;
  public static final int PSP_GE_MATRIX_BONE6 = 6;
  public static final int PSP_GE_MATRIX_BONE7 = 7;
  public static final int PSP_GE_MATRIX_WORLD = 8;
  public static final int PSP_GE_MATRIX_VIEW = 9;
  public static final int PSP_GE_MATRIX_PROJECTION = 10;
  public static final int PSP_GE_MATRIX_TEXGEN = 11;

  public int eDRAMMemoryWidth;

  @Override
  public void start() {
    log.debug(String.format("Starting %s", getName()));

    waitingForSync = false;
    syncDone = false;

    signalCallbacks = new HashMap<Integer, SceKernelCallbackInfo>();
    finishCallbacks = new HashMap<Integer, SceKernelCallbackInfo>();

    listFreeQueue = new ConcurrentLinkedQueue<PspGeList>();
    allGeLists = new PspGeList[NUMBER_GE_LISTS];
    for (int i = 0; i < NUMBER_GE_LISTS; i++) {
      allGeLists[i] = new PspGeList(i);
      listFreeQueue.add(allGeLists[i]);
    }

    deferredThreadWakeupQueue = new ConcurrentLinkedQueue<Integer>();

    eDRAMMemoryWidth = 1024;

    super.start();
  }

  @Override
  public void stop() {
    log.debug(String.format("Stopping %s", getName()));

    if (ExternalGE.isActive()) {
      ExternalGE.onGeUserStop();
    }
  }

  public void step() {
    ThreadManForUser threadMan = Modules.ThreadManForUserModule;

    for (Integer thid = deferredThreadWakeupQueue.poll();
        thid != null;
        thid = deferredThreadWakeupQueue.poll()) {
      if (log.isDebugEnabled()) {
        log.debug(
            "really waking thread "
                + Integer.toHexString(thid)
                + "("
                + threadMan.getThreadName(thid)
                + ")");
      }
      threadMan.hleUnblockThread(thid);

      ExternalGE.onGeStopWaitList();
    }
  }

  private void triggerAsyncCallback(
      int cbid,
      int listId,
      int listPc,
      int behavior,
      int signalId,
      HashMap<Integer, SceKernelCallbackInfo> callbacks) {
    SceKernelCallbackInfo callback = callbacks.get(cbid);
    if (callback != null && callback.callback_addr != 0) {
      if (log.isDebugEnabled()) {
        log.debug(
            String.format(
                "Scheduling Async Callback %s, listId=0x%X, listPc=0x%08X, behavior=%d, signalId=0x%X",
                callback.toString(), listId, listPc, behavior, signalId));
      }
      GeCallbackInterruptHandler geCallbackInterruptHandler =
          new GeCallbackInterruptHandler(
              callback.callback_addr, callback.callback_arg_addr, listPc);
      GeInterruptHandler geInterruptHandler =
          new GeInterruptHandler(geCallbackInterruptHandler, listId, behavior, signalId);
      Emulator.getScheduler().addAction(geInterruptHandler);
    } else {
      hleGeOnAfterCallback(listId, behavior, false);
    }
  }

  private void blockCurrentThreadOnList(PspGeList list, IAction action) {
    ThreadManForUser threadMan = Modules.ThreadManForUserModule;

    boolean blockCurrentThread = false;
    boolean executeAction = false;

    synchronized (this) {
      int currentThreadId = threadMan.getCurrentThreadID();
      if (list.isDone()) {
        // There has been some race condition: the list has just completed
        // do not block the thread
        if (log.isDebugEnabled()) {
          log.debug(
              "blockCurrentThreadOnList not blocking thread "
                  + Integer.toHexString(currentThreadId)
                  + ", list completed "
                  + list);
        }
        executeAction = true;
      } else {
        if (log.isDebugEnabled()) {
          log.debug(
              "blockCurrentThreadOnList blocking thread "
                  + Integer.toHexString(currentThreadId)
                  + " on list "
                  + list);
        }
        list.blockedThreadIds.add(currentThreadId);
        blockCurrentThread = true;
      }
    }

    // Execute the action outside of the synchronized block
    if (executeAction && action != null) {
      action.execute();
    }

    // Block the thread outside of the synchronized block
    if (blockCurrentThread) {
      // Block the thread, but do not execute callbacks.
      threadMan.hleBlockCurrentThread(
          SceKernelThreadInfo.JPCSP_WAIT_GE_LIST,
          list.id,
          false,
          action,
          new ListSyncWaitStateChecker(list));

      ExternalGE.onGeStartWaitList();
    }
  }

  // sceGeDrawSync is resetting all the lists having status PSP_GE_LIST_DONE
  private void hleGeAfterDrawSyncAction() {
    synchronized (this) {
      for (int i = 0; i < NUMBER_GE_LISTS; i++) {
        if (allGeLists[i].status == PSP_GE_LIST_DONE) {
          allGeLists[i].reset();
        }
      }
    }
  }

  /** Called from VideoEngine */
  public void hleGeListSyncDone(PspGeList list) {
    if (log.isDebugEnabled()) {
      String msg = "hleGeListSyncDone list " + list;

      if (list.isDone()) {
        msg += ", done";
      } else {
        msg += ", NOT done";
      }

      if (list.blockedThreadIds.size() > 0 && list.status != PSP_GE_LIST_END_REACHED) {
        msg += ", waking thread";
        for (int threadId : list.blockedThreadIds) {
          msg += " " + Integer.toHexString(threadId);
        }
      }

      log.debug(msg);
    }

    synchronized (this) {
      if (list.blockedThreadIds.size() > 0 && list.status != PSP_GE_LIST_END_REACHED) {
        // things might go wrong if the thread already exists in the queue
        deferredThreadWakeupQueue.addAll(list.blockedThreadIds);
      }

      if (list.isDone()) {
        listFreeQueue.add(list);
      }
    }
  }

  public void hleGeOnAfterCallback(int listId, int behavior, boolean hasCallback) {
    // (gid15) I could not make any difference between
    //    PSP_GE_BEHAVIOR_CONTINUE and PSP_GE_BEHAVIOR_SUSPEND
    // Both wait for the completion of the callback before continuing
    // the list processing...
    if (behavior == PSP_GE_SIGNAL_HANDLER_CONTINUE
        || behavior == PSP_GE_SIGNAL_HANDLER_SUSPEND
        || !hasCallback) {
      if (listId >= 0 && listId < NUMBER_GE_LISTS) {
        PspGeList list = allGeLists[listId];
        if (log.isDebugEnabled()) {
          log.debug("hleGeOnAfterCallback restarting list " + list);
        }

        list.restartList();
      }
    }
  }

  /** safe to call from the Async display thread */
  public void triggerFinishCallback(int cbid, int listId, int listPc, int callbackNotifyArg1) {
    triggerAsyncCallback(
        cbid, listId, listPc, PSP_GE_SIGNAL_HANDLER_SUSPEND, callbackNotifyArg1, finishCallbacks);
  }

  /** safe to call from the Async display thread */
  public void triggerSignalCallback(
      int cbid, int listId, int listPc, int behavior, int callbackNotifyArg1) {
    triggerAsyncCallback(cbid, listId, listPc, behavior, callbackNotifyArg1, signalCallbacks);
  }

  public PspGeList getGeList(int id) {
    if (id < 0 || id >= NUMBER_GE_LISTS) {
      return null;
    }
    return allGeLists[id];
  }

  static class DeferredCallbackInfo {
    public final int cbid;
    public final int callbackIndex;
    public final int listId;
    public final int behavior;
    public final int callbackNotifyArg1;

    public DeferredCallbackInfo(int cbid, int callbackIndex, int callbackNotifyArg1) {
      this.cbid = cbid;
      this.callbackIndex = callbackIndex;
      this.listId = -1;
      this.behavior = PSP_GE_SIGNAL_HANDLER_SUSPEND;
      this.callbackNotifyArg1 = callbackNotifyArg1;
    }

    public DeferredCallbackInfo(
        int cbid, int callbackIndex, int listId, int behavior, int callbackNotifyArg1) {
      this.cbid = cbid;
      this.callbackIndex = callbackIndex;
      this.listId = listId;
      this.behavior = behavior;
      this.callbackNotifyArg1 = callbackNotifyArg1;
    }
  }

  private class HLEAfterDrawSyncAction implements IAction {
    @Override
    public void execute() {
      hleGeAfterDrawSyncAction();
    }
  }

  private static class ListSyncWaitStateChecker implements IWaitStateChecker {
    private PspGeList list;

    public ListSyncWaitStateChecker(PspGeList list) {
      this.list = list;
    }

    @Override
    public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
      // Continue the wait state until the list is done
      boolean contineWait = !list.isDone();

      if (!contineWait) {
        ExternalGE.onGeStopWaitList();
      }

      return contineWait;
    }
  }

  public int checkListId(int id) {
    if (id < 0 || id >= NUMBER_GE_LISTS) {
      throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_ID);
    }

    return id;
  }

  public int checkMode(int mode) {
    if (mode < 0 || mode > 1) {
      throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_MODE);
    }

    return mode;
  }

  public int hleGeListEnQueue(
      TPointer listAddr,
      @CanBeNull TPointer stallAddr,
      int cbid,
      @CanBeNull TPointer argAddr,
      int saveContextAddr,
      boolean enqueueHead) {
    pspGeListOptParam optParams = null;
    int stackAddr = 0;
    if (argAddr.isNotNull()) {
      optParams = new pspGeListOptParam();
      optParams.read(argAddr);
      stackAddr = optParams.stackAddr;
      if (log.isDebugEnabled()) {
        log.debug(String.format("hleGeListEnQueue optParams=%s", optParams));
      }
    }

    if (Modules.SysMemUserForUserModule.hleKernelGetCompiledSdkVersion() >= 0x02000000) {
      boolean isBusy;
      if (ExternalGE.isActive()) {
        isBusy = ExternalGE.hasDrawList(listAddr.getAddress(), stackAddr);
      } else {
        isBusy = VideoEngine.getInstance().hasDrawList(listAddr.getAddress(), stackAddr);
      }
      if (isBusy) {
        log.warn(
            String.format(
                "hleGeListEnQueue can't enqueue duplicate list address %s, stack 0x%08X",
                listAddr, stackAddr));
        return SceKernelErrors.ERROR_BUSY;
      }
    }

    int result;
    synchronized (this) {
      PspGeList list = listFreeQueue.poll();
      if (list == null) {
        log.warn("hleGeListEnQueue no more free list available!");
        if (log.isDebugEnabled()) {
          for (int i = 0; i < NUMBER_GE_LISTS; i++) {
            log.debug(String.format("List#%d: %s", i, allGeLists[i]));
          }
        }
        return SceKernelErrors.ERROR_OUT_OF_MEMORY;
      }

      list.init(listAddr.getAddress(), stallAddr.getAddress(), cbid, optParams);
      list.setSaveContextAddr(saveContextAddr);
      if (enqueueHead) {
        // Send the list to the VideoEngine at the head of the queue.
        list.startListHead();
      } else {
        // Send the list to the VideoEngine before triggering the display (setting GE dirty)
        list.startList();
      }
      Modules.sceDisplayModule.setGeDirty(true);
      result = list.id;
    }

    if (log.isDebugEnabled()) {
      log.debug(String.format("hleGeListEnQueue returning 0x%X", result));
    }

    return result;
  }

  public int hleGeListSync(int id) {
    if (id < 0 || id >= NUMBER_GE_LISTS) {
      return -1;
    }

    PspGeList list = null;
    int result;
    synchronized (this) {
      list = allGeLists[id];
      result = list.status;
    }

    return result;
  }

  @HLEFunction(nid = 0x1F6752AD, version = 150)
  public int sceGeEdramGetSize() {
    return MemoryMap.SIZE_VRAM;
  }

  @HLEFunction(nid = 0xE47E40E4, version = 150)
  public int sceGeEdramGetAddr() {
    return MemoryMap.START_VRAM;
  }

  @HLEFunction(nid = 0xB77905EA, version = 150)
  public int sceGeEdramSetAddrTranslation(int size) {
    // Faking. There's no need for real memory width conversion.
    int previousWidth = eDRAMMemoryWidth;
    eDRAMMemoryWidth = size;

    return previousWidth;
  }

  @HLEFunction(nid = 0xDC93CFEF, version = 150)
  public int sceGeGetCmd(int cmd) {
    VideoEngine ve = VideoEngine.getInstance();
    int value;
    if (ExternalGE.isActive()) {
      value = ExternalGE.getCmd(cmd);
    } else {
      value = ve.getCommandValue(cmd);
    }

    if (log.isInfoEnabled()) {
      log.info(
          String.format(
              "sceGeGetCmd %s: cmd=0x%X, value=0x%06X",
              ve.commandToString(cmd).toUpperCase(), cmd, value));
    }

    return value;
  }

  @HLEFunction(nid = 0x57C8945B, version = 150)
  public int sceGeGetMtx(int mtxType, TPointer mtxAddr) {
    if (mtxType < 0 || mtxType > PSP_GE_MATRIX_TEXGEN) {
      log.warn(String.format("sceGeGetMtx invalid type mtxType=%d", mtxType));
      return SceKernelErrors.ERROR_INVALID_INDEX;
    }

    float[] mtx;
    if (ExternalGE.isActive()) {
      mtx = ExternalGE.getMatrix(mtxType);
    } else {
      mtx = VideoEngine.getInstance().getMatrix(mtxType);
    }

    for (int i = 0; i < mtx.length; i++) {
      // Float value is returned in lower 24 bits.
      mtxAddr.setValue32(i << 2, Float.floatToRawIntBits(mtx[i]) >>> 8);
    }

    if (log.isInfoEnabled()) {
      log.info(String.format("sceGeGetMtx mtxType=%d, mtxAddr=%s, mtx=%s", mtxType, mtxAddr, mtx));
    }

    return 0;
  }

  @HLEFunction(nid = 0x438A385A, version = 150)
  public int sceGeSaveContext(TPointer contextAddr) {
    if (ExternalGE.isActive()) {
      return ExternalGE.saveContext(contextAddr.getAddress());
    }

    VideoEngine.getInstance().hleSaveContext(contextAddr.getAddress());

    return 0;
  }

  @HLEFunction(nid = 0x0BF608FB, version = 150)
  public int sceGeRestoreContext(TPointer contextAddr) {
    if (ExternalGE.isActive()) {
      return ExternalGE.restoreContext(contextAddr.getAddress());
    }

    VideoEngine.getInstance().hleRestoreContext(contextAddr.getAddress());

    return 0;
  }

  @HLEFunction(nid = 0xAB49E76A, version = 150)
  public int sceGeListEnQueue(
      TPointer listAddr, @CanBeNull TPointer stallAddr, int cbid, @CanBeNull TPointer argAddr) {
    return hleGeListEnQueue(listAddr, stallAddr, cbid, argAddr, 0, false);
  }

  @HLEFunction(nid = 0x1C0D95A6, version = 150)
  public int sceGeListEnQueueHead(
      TPointer listAddr, @CanBeNull TPointer stallAddr, int cbid, @CanBeNull TPointer argAddr) {
    return hleGeListEnQueue(listAddr, stallAddr, cbid, argAddr, 0, true);
  }

  @HLEFunction(nid = 0x5FB86AB0, version = 150)
  public int sceGeListDeQueue(@CheckArgument("checkListId") int id) {
    synchronized (this) {
      PspGeList list = allGeLists[id];
      list.reset();
      if (!listFreeQueue.contains(list)) {
        listFreeQueue.add(list);
      }
    }

    return 0;
  }

  @HLEFunction(nid = 0xE0D68148, version = 150)
  public int sceGeListUpdateStallAddr(
      @CheckArgument("checkListId") int id, @CanBeNull TPointer stallAddr) {
    synchronized (this) {
      PspGeList list = allGeLists[id];
      if (list.getStallAddr() != stallAddr.getAddress()) {
        list.setStallAddr(stallAddr.getAddress());
        Modules.sceDisplayModule.setGeDirty(true);
      }
    }

    return 0;
  }

  @HLEFunction(nid = 0x03444EB4, version = 150)
  public int sceGeListSync(
      @CheckArgument("checkListId") int id, @CheckArgument("checkMode") int mode) {
    if (mode == 0 && IntrManager.getInstance().isInsideInterrupt()) {
      log.debug("sceGeListSync (mode==0) cannot be called inside an interrupt handler!");
      return SceKernelErrors.ERROR_KERNEL_CANNOT_BE_CALLED_FROM_INTERRUPT;
    }

    PspGeList list = null;
    boolean blockCurrentThread = false;
    int result;
    synchronized (this) {
      list = allGeLists[id];
      if (log.isDebugEnabled()) {
        log.debug(String.format("sceGeListSync on list: %s", list));
      }

      if (list.isReset()) {
        throw new SceKernelErrorException(SceKernelErrors.ERROR_INVALID_ID);
      }

      if (mode == 0 && !list.isDone()) {
        result = 0;
        blockCurrentThread = true;
      } else {
        result = list.status;
      }
    }

    // Block the current thread outside of the synchronized block
    if (blockCurrentThread) {
      blockCurrentThreadOnList(list, null);
    }

    return result;
  }

  @HLEFunction(nid = 0xB287BD61, version = 150)
  public int sceGeDrawSync(@CheckArgument("checkMode") int mode) {
    if (mode == 0 && IntrManager.getInstance().isInsideInterrupt()) {
      log.debug("sceGeDrawSync (mode==0) cannot be called inside an interrupt handler!");
      return SceKernelErrors.ERROR_KERNEL_CANNOT_BE_CALLED_FROM_INTERRUPT;
    }

    // no synchronization on "this" required because we are not accessing
    // local data, only list information from the VideoEngine.
    int result = 0;
    if (mode == 0) {
      PspGeList lastList;
      if (ExternalGE.isActive()) {
        lastList = ExternalGE.getLastDrawList();
      } else {
        lastList = VideoEngine.getInstance().getLastDrawList();
      }

      if (lastList != null) {
        blockCurrentThreadOnList(lastList, new HLEAfterDrawSyncAction());
      } else {
        if (log.isDebugEnabled()) {
          log.debug("sceGeDrawSync all lists completed, not waiting");
        }
        hleGeAfterDrawSyncAction();
        Modules.ThreadManForUserModule.hleRescheduleCurrentThread();
      }
    } else if (mode == 1) {
      PspGeList currentList;
      if (ExternalGE.isActive()) {
        currentList = ExternalGE.getFirstDrawList();
      } else {
        currentList = VideoEngine.getInstance().getFirstDrawList();
      }
      if (currentList != null) {
        result = currentList.status;
      }
      if (log.isDebugEnabled()) {
        log.debug(String.format("sceGeDrawSync mode=%d, returning %d", mode, result));
      }
    }

    return result;
  }

  @HLEFunction(nid = 0xB448EC0D, version = 150)
  public int sceGeBreak(@CheckArgument("checkMode") int mode, TPointer brk_addr) {
    int result = 0;

    PspGeList list;
    if (ExternalGE.isActive()) {
      list = ExternalGE.getCurrentList();
    } else {
      list = VideoEngine.getInstance().getCurrentList();
    }

    if (mode == 0) { // Pause the current list only.
      if (list != null) {
        list.pauseList();
        result = list.id;
      }
    } else if (mode == 1) { // Pause the current list and cancel the rest of the queue.
      if (list != null) {
        list.pauseList();
        for (int i = 0; i < NUMBER_GE_LISTS; i++) {
          allGeLists[i].status = PSP_GE_LIST_CANCEL_DONE;
        }
        result = list.id;
      }
    }

    return result;
  }

  @HLEFunction(nid = 0x4C06E472, version = 150)
  public int sceGeContinue() {
    PspGeList list;
    if (ExternalGE.isActive()) {
      list = ExternalGE.getCurrentList();
    } else {
      list = VideoEngine.getInstance().getCurrentList();
    }

    if (list != null) {
      synchronized (this) {
        if (list.status == PSP_GE_LIST_END_REACHED) {
          Memory mem = Memory.getInstance();
          if (mem.read32(list.getPc()) == (GeCommands.FINISH << 24)
              && mem.read32(list.getPc() + 4) == (GeCommands.END << 24)) {
            list.readNextInstruction();
            list.readNextInstruction();
          }
        }
        list.restartList();
      }
    }

    return 0;
  }

  @HLEFunction(nid = 0xA4FC06A4, version = 150, checkInsideInterrupt = true)
  public int sceGeSetCallback(TPointer cbdata_addr) {
    pspGeCallbackData cbdata = new pspGeCallbackData();
    cbdata.read(cbdata_addr);

    // The cbid returned has a value in the range [0..15].
    int cbid = SceUidManager.getNewId(geCallbackPurpose, 0, 15);
    if (cbid == SceUidManager.INVALID_ID) {
      log.warn(String.format("sceGeSetCallback no more callback ID available"));
      return SceKernelErrors.ERROR_OUT_OF_MEMORY;
    }

    if (log.isDebugEnabled()) {
      log.debug(
          String.format(
              "sceGeSetCallback signalFunc=0x%08X, signalArg=0x%08X, finishFunc=0x%08X, finishArg=0x%08X, result cbid=0x%X",
              cbdata.signalFunction,
              cbdata.signalArgument,
              cbdata.finishFunction,
              cbdata.finishArgument,
              cbid));
    }

    ThreadManForUser threadMan = Modules.ThreadManForUserModule;
    SceKernelCallbackInfo callbackSignal =
        threadMan.hleKernelCreateCallback(
            "GeCallbackSignal", cbdata.signalFunction, cbdata.signalArgument);
    SceKernelCallbackInfo callbackFinish =
        threadMan.hleKernelCreateCallback(
            "GeCallbackFinish", cbdata.finishFunction, cbdata.finishArgument);
    signalCallbacks.put(cbid, callbackSignal);
    finishCallbacks.put(cbid, callbackFinish);

    return cbid;
  }

  @HLEFunction(nid = 0x05DB22CE, version = 150, checkInsideInterrupt = true)
  public int sceGeUnsetCallback(int cbid) {
    ThreadManForUser threadMan = Modules.ThreadManForUserModule;
    SceKernelCallbackInfo callbackSignal = signalCallbacks.remove(cbid);
    SceKernelCallbackInfo callbackFinish = finishCallbacks.remove(cbid);
    if (callbackSignal != null) {
      threadMan.hleKernelDeleteCallback(callbackSignal.uid);
    }
    if (callbackFinish != null) {
      threadMan.hleKernelDeleteCallback(callbackFinish.uid);
    }
    SceUidManager.releaseId(cbid, geCallbackPurpose);

    return 0;
  }
}
public class LwMutexManager {

  protected static Logger log = Modules.getLogger("ThreadManForUser");

  private HashMap<Integer, SceKernelLwMutexInfo> lwMutexMap;
  private LwMutexWaitStateChecker lwMutexWaitStateChecker;

  private static final int PSP_LWMUTEX_ATTR_FIFO = 0;
  private static final int PSP_LWMUTEX_ATTR_PRIORITY = 0x100;
  private static final int PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE = 0x200;

  public void reset() {
    lwMutexMap = new HashMap<Integer, SceKernelLwMutexInfo>();
    lwMutexWaitStateChecker = new LwMutexWaitStateChecker();
  }

  private boolean removeWaitingThread(SceKernelThreadInfo thread) {
    // Update numWaitThreads
    SceKernelLwMutexInfo info = lwMutexMap.get(thread.wait.LwMutex_id);
    if (info != null) {
      info.numWaitThreads--;
      if (info.numWaitThreads < 0) {
        log.warn(
            "removing waiting thread "
                + Integer.toHexString(thread.uid)
                + ", lwmutex "
                + Integer.toHexString(info.uid)
                + " numWaitThreads underflowed");
        info.numWaitThreads = 0;
      }
      return true;
    }
    return false;
  }

  public void onThreadWaitTimeout(SceKernelThreadInfo thread) {
    // Untrack
    if (removeWaitingThread(thread)) {
      // Return WAIT_TIMEOUT
      thread.cpuContext.gpr[2] = ERROR_KERNEL_WAIT_TIMEOUT;
    } else {
      log.warn("LwMutex deleted while we were waiting for it! (timeout expired)");
      // Return WAIT_DELETE
      thread.cpuContext.gpr[2] = ERROR_KERNEL_WAIT_DELETE;
    }
  }

  public void onThreadWaitReleased(SceKernelThreadInfo thread) {
    // Untrack
    if (removeWaitingThread(thread)) {
      // Return ERROR_WAIT_STATUS_RELEASED
      thread.cpuContext.gpr[2] = ERROR_KERNEL_WAIT_STATUS_RELEASED;
    } else {
      log.warn("EventFlag deleted while we were waiting for it!");
      // Return WAIT_DELETE
      thread.cpuContext.gpr[2] = ERROR_KERNEL_WAIT_DELETE;
    }
  }

  public void onThreadDeleted(SceKernelThreadInfo thread) {
    if (thread.isWaitingForType(PSP_WAIT_LWMUTEX)) {
      // decrement numWaitThreads
      removeWaitingThread(thread);
    }
  }

  private void onLwMutexDeleted(int lwmid) {
    ThreadManForUser threadMan = Modules.ThreadManForUserModule;
    boolean reschedule = false;

    for (Iterator<SceKernelThreadInfo> it = threadMan.iterator(); it.hasNext(); ) {
      SceKernelThreadInfo thread = it.next();
      if (thread.isWaitingForType(PSP_WAIT_LWMUTEX) && thread.wait.LwMutex_id == lwmid) {
        thread.cpuContext.gpr[2] = ERROR_KERNEL_WAIT_DELETE;
        threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
        reschedule = true;
      }
    }
    // Reschedule only if threads waked up.
    if (reschedule) {
      threadMan.hleRescheduleCurrentThread();
    }
  }

  private void onLwMutexModified(SceKernelLwMutexInfo info) {
    ThreadManForUser threadMan = Modules.ThreadManForUserModule;
    boolean reschedule = false;

    if ((info.attr & PSP_LWMUTEX_ATTR_PRIORITY) == PSP_LWMUTEX_ATTR_FIFO) {
      for (Iterator<SceKernelThreadInfo> it = Modules.ThreadManForUserModule.iterator();
          it.hasNext(); ) {
        SceKernelThreadInfo thread = it.next();
        if (thread.isWaitingForType(PSP_WAIT_LWMUTEX)
            && thread.wait.LwMutex_id == info.uid
            && tryLockLwMutex(info, thread.wait.LwMutex_count, thread)) {
          // New thread is taking control of LwMutex.
          info.threadid = thread.uid;
          // Update numWaitThreads
          info.numWaitThreads--;
          // Return success or failure
          thread.cpuContext.gpr[2] = 0;
          // Wakeup
          threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
          reschedule = true;
        }
      }
    } else if ((info.attr & PSP_LWMUTEX_ATTR_PRIORITY) == PSP_LWMUTEX_ATTR_PRIORITY) {
      for (Iterator<SceKernelThreadInfo> it = Modules.ThreadManForUserModule.iteratorByPriority();
          it.hasNext(); ) {
        SceKernelThreadInfo thread = it.next();
        if (thread.isWaitingForType(PSP_WAIT_LWMUTEX)
            && thread.wait.LwMutex_id == info.uid
            && tryLockLwMutex(info, thread.wait.LwMutex_count, thread)) {
          // New thread is taking control of LwMutex.
          info.threadid = thread.uid;
          // Update numWaitThreads
          info.numWaitThreads--;
          // Return success or failure
          thread.cpuContext.gpr[2] = 0;
          // Wakeup
          threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
          reschedule = true;
        }
      }
    }
    // Reschedule only if threads waked up.
    if (reschedule) {
      Modules.ThreadManForUserModule.hleRescheduleCurrentThread();
    }
  }

  private boolean tryLockLwMutex(SceKernelLwMutexInfo info, int count, SceKernelThreadInfo thread) {
    if (info.lockedCount == 0) {
      // If the lwmutex is not locked, allow this thread to lock it.
      info.threadid = thread.uid;
      info.lockedCount += count;
      return true;
    } else if (info.threadid == thread.uid) {
      // If the lwmutex is already locked, but it's trying to be locked by the same thread
      // that acquired it initially, check if recursive locking is allowed.
      // If not, return an error.
      if (((info.attr & PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE) == PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE)) {
        info.lockedCount += count;
        return true;
      }
    }
    return false;
  }

  private int hleKernelLockLwMutex(
      int uid, int count, int timeout_addr, boolean wait, boolean doCallbacks) {
    SceKernelLwMutexInfo info = lwMutexMap.get(uid);
    if (info == null) {
      log.warn(
          String.format(
              "hleKernelLockLwMutex uid=%d, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b -  - unknown UID",
              uid, count, timeout_addr, wait, doCallbacks));
      return ERROR_KERNEL_LWMUTEX_NOT_FOUND;
    }

    ThreadManForUser threadMan = Modules.ThreadManForUserModule;
    SceKernelThreadInfo currentThread = threadMan.getCurrentThread();
    if (!tryLockLwMutex(info, count, currentThread)) {
      if (log.isDebugEnabled()) {
        log.debug(
            String.format(
                "hleKernelLockLwMutex %s, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b - fast check failed",
                info.toString(), count, timeout_addr, wait, doCallbacks));
      }
      if (wait && info.threadid != currentThread.uid) {
        // Failed, but it's ok, just wait a little
        info.numWaitThreads++;
        // Wait on a specific lwmutex
        currentThread.wait.LwMutex_id = uid;
        currentThread.wait.LwMutex_count = count;
        threadMan.hleKernelThreadEnterWaitState(
            PSP_WAIT_LWMUTEX, uid, lwMutexWaitStateChecker, timeout_addr, doCallbacks);
      } else {
        if ((info.attr & PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE) != PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE) {
          return ERROR_KERNEL_LWMUTEX_RECURSIVE_NOT_ALLOWED;
        }
        return ERROR_KERNEL_LWMUTEX_LOCKED;
      }
    } else {
      // Success, do not reschedule the current thread.
      if (log.isDebugEnabled()) {
        log.debug(
            String.format(
                "hleKernelLockLwMutex %s, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b - fast check succeeded",
                info.toString(), count, timeout_addr, wait, doCallbacks));
      }
    }

    return 0;
  }

  public int sceKernelCreateLwMutex(
      int workAreaAddr, int name_addr, int attr, int count, int option_addr) {
    Memory mem = Processor.memory;

    String name = Utilities.readStringNZ(mem, name_addr, 32);

    if (log.isDebugEnabled()) {
      log.debug(
          "sceKernelCreateLwMutex (workAreaAddr='"
              + Integer.toHexString(workAreaAddr)
              + "', name='"
              + name
              + "', attr=0x"
              + Integer.toHexString(attr)
              + ", count=0x"
              + Integer.toHexString(count)
              + ", option_addr=0x"
              + Integer.toHexString(option_addr)
              + ")");
    }

    SceKernelLwMutexInfo info = new SceKernelLwMutexInfo(workAreaAddr, name, count, attr);
    lwMutexMap.put(info.uid, info);

    // If the initial count is 0, the lwmutex is not acquired.
    if (count > 0) {
      info.threadid = Modules.ThreadManForUserModule.getCurrentThreadID();
    }

    // Return 0 in case of no error, do not return the UID of the created mutex
    return 0;
  }

  public int sceKernelDeleteLwMutex(int workAreaAddr) {
    Memory mem = Processor.memory;

    int uid = mem.read32(workAreaAddr);

    if (log.isDebugEnabled()) {
      log.debug("sceKernelDeleteLwMutex (workAreaAddr='" + Integer.toHexString(workAreaAddr) + ")");
    }

    SceKernelLwMutexInfo info = lwMutexMap.remove(uid);
    if (info == null) {
      log.warn("sceKernelDeleteLwMutex unknown UID " + Integer.toHexString(uid));
      return ERROR_KERNEL_LWMUTEX_NOT_FOUND;
    }

    mem.write32(workAreaAddr, 0); // Clear uid.
    onLwMutexDeleted(uid);

    return 0;
  }

  public int sceKernelLockLwMutex(int workAreaAddr, int count, int timeout_addr) {
    Memory mem = Processor.memory;

    int uid = mem.read32(workAreaAddr);

    if (log.isDebugEnabled()) {
      log.debug("sceKernelLockLwMutex redirecting to hleKernelLockLwMutex");
    }
    return hleKernelLockLwMutex(uid, count, timeout_addr, true, false);
  }

  public int sceKernelLockLwMutexCB(int workAreaAddr, int count, int timeout_addr) {
    Memory mem = Processor.memory;

    int uid = mem.read32(workAreaAddr);

    if (log.isDebugEnabled()) {
      log.debug("sceKernelLockLwMutexCB redirecting to hleKernelLockLwMutex");
    }
    return hleKernelLockLwMutex(uid, count, timeout_addr, true, true);
  }

  public int sceKernelTryLockLwMutex(int workAreaAddr, int count) {
    Memory mem = Processor.memory;

    int uid = mem.read32(workAreaAddr);

    if (log.isDebugEnabled()) {
      log.debug("sceKernelTryLockLwMutex redirecting to hleKernelLockLwMutex");
    }
    return hleKernelLockLwMutex(uid, count, 0, false, false);
  }

  public int sceKernelUnlockLwMutex(int workAreaAddr, int count) {
    Memory mem = Processor.memory;

    int uid = mem.read32(workAreaAddr);

    if (log.isDebugEnabled()) {
      log.debug(
          "sceKernelUnlockLwMutex (workAreaAddr=0x"
              + Integer.toHexString(workAreaAddr)
              + ", count="
              + count
              + ")");
    }

    SceKernelLwMutexInfo info = lwMutexMap.get(uid);
    if (info == null) {
      log.warn("sceKernelUnlockLwMutex unknown uid");
      return ERROR_KERNEL_LWMUTEX_NOT_FOUND;
    }
    if (info.lockedCount == 0) {
      log.debug("sceKernelUnlockLwMutex not locked");
      return ERROR_KERNEL_LWMUTEX_UNLOCKED;
    }
    if (info.lockedCount < 0) {
      log.warn("sceKernelUnlockLwMutex underflow");
      return ERROR_KERNEL_LWMUTEX_UNLOCK_UNDERFLOW;
    }

    info.lockedCount -= count;
    if (info.lockedCount == 0) {
      onLwMutexModified(info);
    }

    return 0;
  }

  public int sceKernelReferLwMutexStatus(int workAreaAddr, int addr) {
    Memory mem = Processor.memory;

    int uid = mem.read32(workAreaAddr);

    if (log.isDebugEnabled()) {
      log.debug(
          "sceKernelReferLwMutexStatus (workAreaAddr=0x"
              + Integer.toHexString(workAreaAddr)
              + ", addr=0x"
              + addr
              + ")");
    }

    SceKernelLwMutexInfo info = lwMutexMap.get(uid);
    if (info == null) {
      log.warn("sceKernelReferLwMutexStatus unknown UID " + Integer.toHexString(uid));
      return ERROR_KERNEL_LWMUTEX_NOT_FOUND;
    }
    if (!Memory.isAddressGood(addr)) {
      log.warn("sceKernelReferLwMutexStatus bad address 0x" + Integer.toHexString(addr));
      return -1;
    }

    info.write(mem, addr);

    return 0;
  }

  public int sceKernelReferLwMutexStatusByID(int uid, int addr) {
    Memory mem = Processor.memory;

    if (log.isDebugEnabled()) {
      log.debug(
          "sceKernelReferLwMutexStatus (uid=0x"
              + Integer.toHexString(uid)
              + ", addr=0x"
              + addr
              + ")");
    }

    SceKernelLwMutexInfo info = lwMutexMap.get(uid);
    if (info == null) {
      log.warn("sceKernelReferLwMutexStatus unknown UID " + Integer.toHexString(uid));
      return ERROR_KERNEL_LWMUTEX_NOT_FOUND;
    }
    if (!Memory.isAddressGood(addr)) {
      log.warn("sceKernelReferLwMutexStatus bad address 0x" + Integer.toHexString(addr));
      return -1;
    }

    info.write(mem, addr);

    return 0;
  }

  private class LwMutexWaitStateChecker implements IWaitStateChecker {

    @Override
    public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
      // Check if the thread has to continue its wait state or if the lwmutex
      // has been unlocked during the callback execution.
      SceKernelLwMutexInfo info = lwMutexMap.get(wait.LwMutex_id);
      if (info == null) {
        thread.cpuContext.gpr[2] = ERROR_KERNEL_LWMUTEX_NOT_FOUND;
        return false;
      }

      // Check the lwmutex.
      if (tryLockLwMutex(info, wait.LwMutex_count, thread)) {
        info.numWaitThreads--;
        thread.cpuContext.gpr[2] = 0;
        return false;
      }

      return true;
    }
  }

  public static final LwMutexManager singleton = new LwMutexManager();

  private LwMutexManager() {}
}
Example #5
0
/*
 * TODO list:
 * 1. Use the partitionid in functions that use it as a parameter.
 *  -> Info:
 *      1 = kernel, 2 = user, 3 = me, 4 = kernel mirror (from potemkin/dash)
 *      http://forums.ps2dev.org/viewtopic.php?p=75341#75341
 *      8 = slim, topaddr = 0x8A000000, size = 0x1C00000 (28 MB), attr = 0x0C
 *      8 = slim, topaddr = 0x8BC00000, size = 0x400000 (4 MB), attr = 0x0C
 *
 * 2. Implement format string parsing and reading variable number of parameters
 * in sceKernelPrintf.
 */
public class SysMemUserForUser extends HLEModule {
  public static Logger log = Modules.getLogger("SysMemUserForUser");
  protected static Logger stdout = Logger.getLogger("stdout");
  protected static HashMap<Integer, SysMemInfo> blockList;
  protected static MemoryChunkList[] freeMemoryChunks;
  protected int firmwareVersion = 150;
  public static final int defaultSizeAlignment = 256;

  // PspSysMemBlockTypes
  public static final int PSP_SMEM_Low = 0;
  public static final int PSP_SMEM_High = 1;
  public static final int PSP_SMEM_Addr = 2;
  public static final int PSP_SMEM_LowAligned = 3;
  public static final int PSP_SMEM_HighAligned = 4;

  public static final int KERNEL_PARTITION_ID = 1;
  public static final int USER_PARTITION_ID = 2;

  protected boolean started = false;
  private int compiledSdkVersion;
  protected int compilerVersion;

  @Override
  public void load() {
    reset();

    super.load();
  }

  @Override
  public void start() {
    if (!started) {
      reset();
      started = true;
    }

    compiledSdkVersion = 0;

    super.start();
  }

  @Override
  public void stop() {
    started = false;

    super.stop();
  }

  private MemoryChunkList createMemoryChunkList(int startAddr, int endAddr) {
    startAddr &= Memory.addressMask;
    endAddr &= Memory.addressMask;

    MemoryChunk initialMemory = new MemoryChunk(startAddr, endAddr - startAddr + 1);

    return new MemoryChunkList(initialMemory);
  }

  public void reset() {
    blockList = new HashMap<Integer, SysMemInfo>();

    // free memory chunks for each partition
    freeMemoryChunks = new MemoryChunkList[3];
    freeMemoryChunks[USER_PARTITION_ID] =
        createMemoryChunkList(MemoryMap.START_USERSPACE, MemoryMap.END_USERSPACE);
    freeMemoryChunks[KERNEL_PARTITION_ID] =
        createMemoryChunkList(MemoryMap.START_KERNEL, MemoryMap.END_KERNEL);
  }

  public void setMemory64MB(boolean isMemory64MB) {
    if (isMemory64MB) {
      setMemorySize(MemoryMap.END_RAM_64MB - MemoryMap.START_RAM + 1); // 60 MB
    } else {
      setMemorySize(MemoryMap.END_RAM_32MB - MemoryMap.START_RAM + 1); // 32 MB
    }
  }

  public void setMemorySize(int memorySize) {
    if (MemoryMap.SIZE_RAM != memorySize) {
      int previousMemorySize = MemoryMap.SIZE_RAM;
      MemoryMap.END_RAM = MemoryMap.START_RAM + memorySize - 1;
      MemoryMap.END_USERSPACE = MemoryMap.END_RAM;
      MemoryMap.SIZE_RAM = MemoryMap.END_RAM - MemoryMap.START_RAM + 1;

      if (!Memory.getInstance().allocate()) {
        log.error(
            String.format(
                "Failed to resize the PSP memory from 0x%X to 0x%X",
                previousMemorySize, memorySize));
        Emulator.PauseEmuWithStatus(Emulator.EMU_STATUS_MEM_ANY);
      }

      reset();
    }
  }

  public static class SysMemInfo implements Comparable<SysMemInfo> {

    public final int uid;
    public final int partitionid;
    public final String name;
    public final int type;
    public final int size;
    public final int allocatedSize;
    public final int addr;

    public SysMemInfo(
        int partitionid, String name, int type, int size, int allocatedSize, int addr) {
      this.partitionid = partitionid;
      this.name = name;
      this.type = type;
      this.size = size;
      this.allocatedSize = allocatedSize;
      this.addr = addr;

      uid = SceUidManager.getNewUid("SysMem");
      blockList.put(uid, this);
    }

    @Override
    public String toString() {
      return String.format(
          "SysMemInfo[addr=0x%08X-0x%08X, uid=%x, partition=%d, name='%s', type=%s, size=0x%X (allocated=0x%X)]",
          addr,
          addr + allocatedSize,
          uid,
          partitionid,
          name,
          getTypeName(type),
          size,
          allocatedSize);
    }

    public void free() {
      blockList.remove(uid);
    }

    @Override
    public int compareTo(SysMemInfo o) {
      if (addr == o.addr) {
        log.warn("Set invariant broken for SysMemInfo " + this);
        return 0;
      }
      return addr < o.addr ? -1 : 1;
    }
  }

  protected static String getTypeName(int type) {
    String typeName;

    switch (type) {
      case PSP_SMEM_Low:
        typeName = "PSP_SMEM_Low";
        break;
      case PSP_SMEM_High:
        typeName = "PSP_SMEM_High";
        break;
      case PSP_SMEM_Addr:
        typeName = "PSP_SMEM_Addr";
        break;
      case PSP_SMEM_LowAligned:
        typeName = "PSP_SMEM_LowAligned";
        break;
      case PSP_SMEM_HighAligned:
        typeName = "PSP_SMEM_HighAligned";
        break;
      default:
        typeName = "UNHANDLED " + type;
        break;
    }

    return typeName;
  }

  // Allocates to 256-byte alignment
  public SysMemInfo malloc(int partitionid, String name, int type, int size, int addr) {
    int allocatedAddress = 0;
    int allocatedSize = 0;

    if (partitionid >= 0
        && partitionid < freeMemoryChunks.length
        && freeMemoryChunks[partitionid] != null) {
      MemoryChunkList freeMemoryChunk = freeMemoryChunks[partitionid];
      int alignment = defaultSizeAlignment - 1;

      // The allocated size has not to be aligned to the requested alignment
      // (for PSP_SMEM_LowAligned or PSP_SMEM_HighAligned),
      // it is only aligned to the default size alignment.
      allocatedSize = Utilities.alignUp(size, alignment);

      if (type == PSP_SMEM_LowAligned || type == PSP_SMEM_HighAligned) {
        // Use the alignment provided in the addr parameter
        alignment = addr - 1;
      }

      switch (type) {
        case PSP_SMEM_Low:
        case PSP_SMEM_LowAligned:
          allocatedAddress = freeMemoryChunk.allocLow(allocatedSize, alignment);
          break;
        case PSP_SMEM_High:
        case PSP_SMEM_HighAligned:
          allocatedAddress = freeMemoryChunk.allocHigh(allocatedSize, alignment);
          break;
        case PSP_SMEM_Addr:
          allocatedAddress = freeMemoryChunk.alloc(addr, allocatedSize);
          break;
        default:
          log.warn(String.format("malloc: unknown type %s", getTypeName(type)));
      }
    }

    SysMemInfo sysMemInfo;
    if (allocatedAddress == 0) {
      log.warn(
          String.format(
              "malloc cannot allocate partition=%d, name='%s', type=%s, size=0x%X, addr=0x%08X, maxFreeMem=0x%X, totalFreeMem=0x%X",
              partitionid,
              name,
              getTypeName(type),
              size,
              addr,
              maxFreeMemSize(),
              totalFreeMemSize()));
      if (log.isTraceEnabled()) {
        log.trace("Free list: " + getDebugFreeMem());
        log.trace("Allocated blocks:\n" + getDebugAllocatedMem() + "\n");
      }
      sysMemInfo = null;
    } else {
      sysMemInfo = new SysMemInfo(partitionid, name, type, size, allocatedSize, allocatedAddress);

      if (log.isDebugEnabled()) {
        log.debug(
            String.format(
                "malloc partition=%d, name='%s', type=%s, size=0x%X, addr=0x%08X: returns 0x%08X",
                partitionid, name, getTypeName(type), size, addr, allocatedAddress));
        if (log.isTraceEnabled()) {
          log.trace("Free list after malloc: " + getDebugFreeMem());
          log.trace("Allocated blocks after malloc:\n" + getDebugAllocatedMem() + "\n");
        }
      }
    }

    return sysMemInfo;
  }

  public String getDebugFreeMem() {
    return freeMemoryChunks[USER_PARTITION_ID].toString();
  }

  public String getDebugAllocatedMem() {
    StringBuilder result = new StringBuilder();

    // Sort allocated blocks by address
    List<SysMemInfo> sortedBlockList =
        Collections.list(Collections.enumeration(blockList.values()));
    Collections.sort(sortedBlockList);

    for (SysMemInfo sysMemInfo : sortedBlockList) {
      if (result.length() > 0) {
        result.append("\n");
      }
      result.append(sysMemInfo.toString());
    }

    return result.toString();
  }

  public void free(SysMemInfo info) {
    if (info != null) {
      info.free();
      MemoryChunk memoryChunk = new MemoryChunk(info.addr, info.allocatedSize);
      freeMemoryChunks[info.partitionid].add(memoryChunk);

      if (log.isDebugEnabled()) {
        log.debug(String.format("free %s", info.toString()));
        if (log.isTraceEnabled()) {
          log.trace("Free list after free: " + getDebugFreeMem());
          log.trace("Allocated blocks after free:\n" + getDebugAllocatedMem() + "\n");
        }
      }
    }
  }

  public int maxFreeMemSize() {
    int maxFreeMemSize = 0;
    for (MemoryChunk memoryChunk = freeMemoryChunks[USER_PARTITION_ID].getLowMemoryChunk();
        memoryChunk != null;
        memoryChunk = memoryChunk.next) {
      if (memoryChunk.size > maxFreeMemSize) {
        maxFreeMemSize = memoryChunk.size;
      }
    }
    return maxFreeMemSize;
  }

  public int totalFreeMemSize() {
    int totalFreeMemSize = 0;
    for (MemoryChunk memoryChunk = freeMemoryChunks[USER_PARTITION_ID].getLowMemoryChunk();
        memoryChunk != null;
        memoryChunk = memoryChunk.next) {
      totalFreeMemSize += memoryChunk.size;
    }

    return totalFreeMemSize;
  }

  public SysMemInfo getSysMemInfo(int uid) {
    return blockList.get(uid);
  }

  /**
   * @param firmwareVersion : in this format: ABB, where A = major and B = minor, for example 271
   */
  public void setFirmwareVersion(int firmwareVersion) {
    this.firmwareVersion = firmwareVersion;
  }

  public int getFirmwareVersion() {
    return firmwareVersion;
  }

  // note: we're only looking at user memory, so 0x08800000 - 0x0A000000
  // this is mainly to make it fit on one console line
  public void dumpSysMemInfo() {
    final int MEMORY_SIZE = 0x1800000;
    final int SLOT_COUNT = 64; // 0x60000
    final int SLOT_SIZE = MEMORY_SIZE / SLOT_COUNT; // 0x60000
    boolean[] allocated = new boolean[SLOT_COUNT];
    boolean[] fragmented = new boolean[SLOT_COUNT];
    int allocatedSize = 0;
    int fragmentedSize = 0;

    for (Iterator<SysMemInfo> it = blockList.values().iterator(); it.hasNext(); ) {
      SysMemInfo info = it.next();
      for (int i = info.addr; i < info.addr + info.size; i += SLOT_SIZE) {
        if (i >= 0x08800000 && i < 0x0A000000) {
          allocated[(i - 0x08800000) / SLOT_SIZE] = true;
        }
      }
      allocatedSize += info.size;
    }

    for (MemoryChunk memoryChunk = freeMemoryChunks[USER_PARTITION_ID].getLowMemoryChunk();
        memoryChunk != null;
        memoryChunk = memoryChunk.next) {
      for (int i = memoryChunk.addr; i < memoryChunk.addr + memoryChunk.size; i += SLOT_SIZE) {
        if (i >= 0x08800000 && i < 0x0A000000) {
          fragmented[(i - 0x08800000) / SLOT_SIZE] = true;
        }
      }
      fragmentedSize += memoryChunk.size;
    }

    StringBuilder allocatedDiagram = new StringBuilder();
    allocatedDiagram.append("[");
    for (int i = 0; i < SLOT_COUNT; i++) {
      allocatedDiagram.append(allocated[i] ? "X" : " ");
    }
    allocatedDiagram.append("]");

    StringBuilder fragmentedDiagram = new StringBuilder();
    fragmentedDiagram.append("[");
    for (int i = 0; i < SLOT_COUNT; i++) {
      fragmentedDiagram.append(fragmented[i] ? "X" : " ");
    }
    fragmentedDiagram.append("]");

    DumpDebugState.log("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
    DumpDebugState.log(
        String.format("Allocated memory:  %08X %d bytes", allocatedSize, allocatedSize));
    DumpDebugState.log(allocatedDiagram.toString());
    DumpDebugState.log(
        String.format("Fragmented memory: %08X %d bytes", fragmentedSize, fragmentedSize));
    DumpDebugState.log(fragmentedDiagram.toString());

    DumpDebugState.log("Free list: " + getDebugFreeMem());
    DumpDebugState.log("Allocated blocks:\n" + getDebugAllocatedMem() + "\n");
  }

  public int hleKernelPrintf(
      CpuState cpu, PspString formatString, Logger logger, String sceFunctionName) {
    // Format and print the message to stdout
    if (logger.isInfoEnabled()) {
      String formattedMsg = formatString.getString();
      try {
        // For now, use only the 7 register parameters: $a1-$a3, $t0-$t3
        // Further parameters should be retrieved from the stack.
        Object[] formatParameters =
            new Object[] {cpu._a1, cpu._a2, cpu._a3, cpu._t0, cpu._t1, cpu._t2, cpu._t3};

        // Translate the C-like format string to a Java format string:
        // - %u or %i -> %d
        // - %4u -> %4d
        // - %lld or %ld -> %d
        // - %p -> %08X
        String javaMsg = formatString.getString();
        javaMsg = javaMsg.replaceAll("\\%(\\d*)l?l?[uid]", "%$1d");
        javaMsg = javaMsg.replaceAll("\\%p", "%08X");

        // Support for "%s" (at any place and can occur multiple times)
        int index = -1;
        for (int parameterIndex = 0; parameterIndex < formatParameters.length; parameterIndex++) {
          index = javaMsg.indexOf('%', index + 1);
          if (index < 0) {
            break;
          }
          String parameterFormat = javaMsg.substring(index);
          if (parameterFormat.startsWith("%s")) {
            // Convert an integer address to a String by reading
            // the String at the given address
            formatParameters[parameterIndex] =
                Utilities.readStringZ(((Integer) formatParameters[parameterIndex]).intValue());
          }
        }

        // String.format: If there are more arguments than format specifiers, the extra arguments
        // are ignored.
        formattedMsg = String.format(javaMsg, formatParameters);
      } catch (Exception e) {
        // Ignore formatting exception
      }
      logger.info(formattedMsg);
    }

    return 0;
  }

  public int hleKernelGetCompiledSdkVersion() {
    return compiledSdkVersion;
  }

  protected void hleSetCompiledSdkVersion(int sdkVersion) {
    compiledSdkVersion = sdkVersion;
  }

  @HLEFunction(nid = 0xA291F107, version = 150)
  public int sceKernelMaxFreeMemSize() {
    int maxFreeMemSize = maxFreeMemSize();

    // Some games expect size to be rounded down in 16 bytes block
    maxFreeMemSize &= ~15;

    if (log.isDebugEnabled()) {
      log.debug(String.format("sceKernelMaxFreeMemSize returning %d(hex=0x%1$X)", maxFreeMemSize));
    }

    return maxFreeMemSize;
  }

  @HLEFunction(nid = 0xF919F628, version = 150)
  public int sceKernelTotalFreeMemSize() {
    int totalFreeMemSize = totalFreeMemSize();
    if (log.isDebugEnabled()) {
      log.debug(
          String.format("sceKernelTotalFreeMemSize returning %d(hex=0x%1$X)", totalFreeMemSize));
    }

    return totalFreeMemSize;
  }

  @HLEFunction(nid = 0x237DBD4F, version = 150)
  public int sceKernelAllocPartitionMemory(
      int partitionid, String name, int type, int size, int addr) {
    addr &= Memory.addressMask;

    if (type < PSP_SMEM_Low || type > PSP_SMEM_HighAligned) {
      return SceKernelErrors.ERROR_KERNEL_ILLEGAL_MEMBLOCK_ALLOC_TYPE;
    }

    SysMemInfo info = malloc(partitionid, name, type, size, addr);
    if (info == null) {
      return SceKernelErrors.ERROR_KERNEL_FAILED_ALLOC_MEMBLOCK;
    }

    return info.uid;
  }

  @HLEFunction(nid = 0xB6D61D02, version = 150)
  public int sceKernelFreePartitionMemory(int uid) {
    SceUidManager.checkUidPurpose(uid, "SysMem", true);

    SysMemInfo info = blockList.remove(uid);
    if (info == null) {
      log.warn(String.format("sceKernelFreePartitionMemory unknown uid=0x%X", uid));
      return SceKernelErrors.ERROR_KERNEL_ILLEGAL_CHUNK_ID;
    }

    free(info);

    return 0;
  }

  @HLEFunction(nid = 0x9D9A5BA1, version = 150)
  public int sceKernelGetBlockHeadAddr(int uid) {
    SceUidManager.checkUidPurpose(uid, "SysMem", true);

    SysMemInfo info = blockList.get(uid);
    if (info == null) {
      log.warn(String.format("sceKernelGetBlockHeadAddr unknown uid=0x%X", uid));
      return SceKernelErrors.ERROR_KERNEL_ILLEGAL_CHUNK_ID;
    }

    return info.addr;
  }

  @HLEFunction(nid = 0x13A5ABEF, version = 150)
  public int sceKernelPrintf(CpuState cpu, PspString formatString) {
    return hleKernelPrintf(cpu, formatString, stdout, "sceKernelPrintf");
  }

  @HLEFunction(nid = 0x3FC9AE6A, version = 150)
  public int sceKernelDevkitVersion() {
    int major = firmwareVersion / 100;
    int minor = (firmwareVersion / 10) % 10;
    int revision = firmwareVersion % 10;
    int devkitVersion = (major << 24) | (minor << 16) | (revision << 8) | 0x10;
    if (log.isDebugEnabled()) {
      log.debug(String.format("sceKernelDevkitVersion returning 0x%08X", devkitVersion));
    }

    return devkitVersion;
  }

  @HLEFunction(nid = 0xD8DE5C1E, version = 150)
  public int SysMemUserForUser_D8DE5C1E() {
    // Seems to always return 0...
    return 0;
  }

  @HLEFunction(nid = 0xFC114573, version = 200)
  public int sceKernelGetCompiledSdkVersion() {
    return compiledSdkVersion;
  }

  @HLEFunction(nid = 0x7591C7DB, version = 200)
  public int sceKernelSetCompiledSdkVersion(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0xF77D77CB, version = 200)
  public int sceKernelSetCompilerVersion(int compilerVersion) {
    this.compilerVersion = compilerVersion;

    return 0;
  }

  @HLEUnimplemented
  @HLEFunction(nid = 0xA6848DF8, version = 200)
  public int SysMemUserForUser_A6848DF8() {
    return 0;
  }

  @HLEUnimplemented
  @HLEFunction(nid = 0x2A3E5280, version = 280)
  public int sceKernelQueryMemoryInfo() {
    return 0;
  }

  @HLEUnimplemented
  @HLEFunction(nid = 0x39F49610, version = 280)
  public int sceKernelGetPTRIG() {
    return 0;
  }

  @HLEUnimplemented
  @HLEFunction(nid = 0x6231A71D, version = 280)
  public int sceKernelSetPTRIG() {
    return 0;
  }

  // sceKernelFreeMemoryBlock (internal name)
  @HLEFunction(nid = 0x50F61D8A, version = 352)
  public int SysMemUserForUser_50F61D8A(int uid) {
    SysMemInfo info = blockList.remove(uid);
    if (info == null) {
      log.warn("SysMemUserForUser_50F61D8A(uid=0x" + Integer.toHexString(uid) + ") unknown uid");
      return SceKernelErrors.ERROR_KERNEL_UNKNOWN_UID;
    }

    free(info);

    return 0;
  }

  @HLEUnimplemented
  @HLEFunction(nid = 0xACBD88CA, version = 352)
  public int SysMemUserForUser_ACBD88CA() {
    return 0;
  }

  // sceKernelGetMemoryBlockAddr (internal name)
  @HLEFunction(nid = 0xDB83A952, version = 352)
  public int SysMemUserForUser_DB83A952(int uid, TPointer32 addr) {
    SysMemInfo info = blockList.get(uid);
    if (info == null) {
      log.warn(
          String.format("SysMemUserForUser_DB83A952 uid=0x%X, addr=%s: unknown uid", uid, addr));
      return SceKernelErrors.ERROR_KERNEL_UNKNOWN_UID;
    }

    addr.setValue(info.addr);

    return 0;
  }

  // sceKernelAllocMemoryBlock (internal name)
  @HLEFunction(nid = 0xFE707FDF, version = 352)
  public int SysMemUserForUser_FE707FDF(
      @StringInfo(maxLength = 32) PspString name,
      int type,
      int size,
      @CanBeNull TPointer paramsAddr) {
    if (paramsAddr.isNotNull()) {
      int length = paramsAddr.getValue32();
      if (length != 4) {
        log.warn(
            String.format("SysMemUserForUser_FE707FDF: unknown parameters with length=%d", length));
      }
    }

    if (type < PSP_SMEM_Low || type > PSP_SMEM_High) {
      return SceKernelErrors.ERROR_KERNEL_ILLEGAL_MEMBLOCK_ALLOC_TYPE;
    }

    // Always allocate memory in user area (partitionid == 2).
    SysMemInfo info = malloc(SysMemUserForUser.USER_PARTITION_ID, name.getString(), type, size, 0);
    if (info == null) {
      return SceKernelErrors.ERROR_KERNEL_FAILED_ALLOC_MEMBLOCK;
    }

    return info.uid;
  }

  @HLEFunction(nid = 0x342061E5, version = 370)
  public int sceKernelSetCompiledSdkVersion370(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0x315AD3A0, version = 380)
  public int sceKernelSetCompiledSdkVersion380_390(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0xEBD5C3E6, version = 395)
  public int sceKernelSetCompiledSdkVersion395(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0x91DE343C, version = 500)
  public int sceKernelSetCompiledSdkVersion500_505(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0x7893F79A, version = 507)
  public int sceKernelSetCompiledSdkVersion507(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0x35669D4C, version = 600)
  public int sceKernelSetCompiledSdkVersion600_602(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0x1B4217BC, version = 603)
  public int sceKernelSetCompiledSdkVersion603_605(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }

  @HLEFunction(nid = 0x358CA1BB, version = 606)
  public int sceKernelSetCompiledSdkVersion606(int sdkVersion) {
    hleSetCompiledSdkVersion(sdkVersion);

    return 0;
  }
}