Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for loading game-specific plugins #13335

Merged
merged 4 commits into from
Sep 8, 2020

Conversation

unknownbrackets
Copy link
Collaborator

@unknownbrackets unknownbrackets commented Aug 26, 2020

This makes it possible to define plugins, as prx files, for specific games. An immediate example is fan translation.

IMPORTANT: This does not support original PSP firmware plugins which use hacks of PSP firmware internals to inject into games. The same internals do not exist in PPSSPP.

Basic usage:

  1. Create a plugin directory inside PSP/PLUGINS/, i.e. PSP/PLUGINS/mycrisiscoretranslation/.
  2. Add an ini file, such as:
    [options]
    version = 1
    type = prx
    filename = patch.prx
    
    ; Always specify games to indicate what games are supported.
    [games]
    ; Normal usage: specify game IDs that are supported.
    ULJM05275 = true
    ; Advanced usage: specify another ini to use a separate prx for a certain game ID.
    ; (in most cases, you won't use this.)
    ULES01048 = spanish.ini
    ; General plugins for any game: use ALL to indicate all game IDs.
    ALL = true
    
    ; Optional, specify ini for another prx for specific user interface languages.
    ; (in most cases, you won't use this.)
    [lang]
    hr_HR = croatian.ini
  3. Compile a prx plugin using the pspsdk, and it'll be loaded. However, you cannot use many kernel or HEN functions, since they don't work in PPSSPP. The internal architecture is different. You can however call the same functions games can call, update RAM, etc.

Other options:

  • You can use memory = 64 under [options] to allocate more RAM, up to 93 MB due to memory map limitations. Beware this gives the game overall more RAM, and may affect cheats and memory management in the game.
  • Under [games], you can use ULJM05275 = foo.ini to allow your plugin to use a different prx for different game IDs, i.e. multidisc games. true is the same as using plugin.ini, in other words use [options] in the same file. ALL = true or ALL = otherfile.ini can also be used to apply to all games. seplugins won't work, because HEN/kernel funcs are not supported.
  • You can also add [lang] and put something like se_SE = swedish.ini. This allows you to load a prx just for a user's specific language. This can be in addition to, or instead of, a main plugin.

In theory, this could allow more things. If we wanted to allow lua or ecma or python, we could add it as a different type. We could also add an import to be able to call things like:

  • ppssppVibrate(type)
  • ppssppUseTouch(enable), ppssppGetTouches()
  • ppssppPreloadTexture(addr, bufw, w, h)
  • ppssppReplaceTexture(addr, hash0, hash1, hash2), ppssppForgetTexture(addr)
  • ppssppReplaceSound(atracID, "filename.mp3")
  • ppssppGetRenderResolution(), ppssppDownloadFramebuffer(fb_address, ram_address, bufw, w, h, bilinear) (coupled with memory = 93)

Though, not sure if a scripting language is more appropriate. I don't want PPSSPP itself to try to solve game bugs with game-specific hacks, but I'm all for allowing people to make game-specific enhancements. Not sure what people are interested in making, though, and in what language... cwcheat doesn't seem like enough.

-[Unknown]

@hrydgard
Copy link
Owner

Nice. Some people are motivated by also being able to run their stuff on a real PSP, so having support for PRX plugins is probably a good thing even if we also add support for other languages.

Of course for uses like custom touch interfaces etc that can't run on a real PSP, lua or ecma might indeed be more appropriate - somewhat safer and easier than prx plugins (no need to install SDK etc).

@hrydgard
Copy link
Owner

One comment on the post: You write

Under [games], you can use ULJM05275 = foo.ini to allow your plugin to use a different prx for different game IDs, i.e. multidisc games. ALL = true can also be used to apply to all games

But in the example you use ULJM05275 = true . Seems like a third type of usage?

@unknownbrackets
Copy link
Collaborator Author

unknownbrackets commented Aug 26, 2020

Right, and PRX plugins are - as long as we're not building out the whole internal kernel framework - easy to support. Far easier than any scripting language.

But I think we'd probably need a scripting language if we wanted hooks like running code when you save state or something. That'd be annoying to do with a PRX plugin...

But in the example you use ULJM05275 = true . Seems like a third type of usage?

Oops, I'll amend. true is essentially an alias for plugin.ini. So you don't need to have multiple inis if you are only supporting a single game id, which is probably the most likely. For ALL I don't know when you'd point to a separate ini, although you can.

-[Unknown]

@i30817
Copy link

i30817 commented Aug 26, 2020

My opinion about runtime game patches is that there should always be a way to place them in the same dir as the game for ease of movement and transfer.

In other projects it pisses me off when i can't place (for instance) texture replacement projects in the same dir as the game, as a subdir or a zip.

I understand that the psp already has conventions that ppsspp is reusing - for instance for the certificates to run certain games - but i wish it was possible to shadow files/folder structure the game dir, if the files thus shadowed are 'single game related'.

@devinprater
Copy link

devinprater commented Aug 26, 2020 via email

@hrydgard
Copy link
Owner

hrydgard commented Sep 6, 2020

To follow up, I think this is great except possibly for the ini configuration interface.

i30817 has a point with the ability to easily copy patches around with the games, although it is also established in PPSSPP practice to have similar things separately (like cheats).

As for the game id = parameter, anything that can be set to either a string or true, with different meaning in the latter case, feels quite odd to me.. I wonder if we can think of a more intuitive way.

@unknownbrackets
Copy link
Collaborator Author

Well, I can just remove true. I just think it'll make a bunch of people confused and think they have to create a bunch of separate files, which seems annoying.

Textures are also not grouped with the game. I think it gets tricky with different file formats, like PBP, PBP as folder, CSO, etc.

-[Unknown]

@hrydgard
Copy link
Owner

hrydgard commented Sep 6, 2020

Yeah fair enough. I'll consider it.

Anyway, can probably take this PR out of Draft mode...

@unknownbrackets unknownbrackets marked this pull request as ready for review September 6, 2020 17:46
@unknownbrackets
Copy link
Collaborator Author

Yeah, at least one person has used it with some success.

-[Unknown]

@hrydgard hrydgard added this to the v1.11.0 milestone Sep 6, 2020
@hrydgard hrydgard added the HLE/Kernel Kernel, memory manager, other HLE issues label Sep 6, 2020
@unknownbrackets unknownbrackets changed the title WIP: Support for loading game-specific plugins Support for loading game-specific plugins Sep 8, 2020
@unknownbrackets
Copy link
Collaborator Author

Just as an example, this is a version of the Kingdom Hearts right analog plugin that doesn't use the HEN stuff we don't support. Makefile, exports, etc. unchanged.

PSP/PLUGINS/khbbs_remastered/plugin.ini:

[options]
type = prx
version = 1
filename = khbbs_remastered.prx

[games]
ULUS10505 = true
ULJM05600 = true
ULJM05775 = true
ULES01441 = true

main.c:

/*
  Remastered Controls: Kingdom Hearts
  Copyright (C) 2018, TheFloW

  This program 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.

  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <pspsdk.h>
#include <pspkernel.h>
#include <pspctrl.h>

#include <stdio.h>
#include <string.h>

#include <systemctrl.h>

#define EMULATOR_DEVCTL__IS_EMULATOR     0x00000003

PSP_MODULE_INFO("KHBBSRemastered", 0x1007, 1, 0);

int sceKernelQuerySystemCall(void *function);

#define MAKE_SYSCALL(a, n) _sw(0x0000000C | ((n) << 6), a);
#define MAKE_JAL(a, n) _sw(0x0C000000 | ((n) >> 2), a);

static STMOD_HANDLER previous;

float leftAnalogX() {
  SceCtrlData pad;

  int k1 = pspSdkSetK1(0);
  sceCtrlPeekBufferPositive(&pad, 1);
  pspSdkSetK1(k1);

  return (float)(pad.Rsrv[0] - 128) / 128.0f;
}

float leftAnalogY() {
  SceCtrlData pad;

  int k1 = pspSdkSetK1(0);
  sceCtrlPeekBufferPositive(&pad, 1);
  pspSdkSetK1(k1);

  return (float)(pad.Rsrv[1] - 128) / 128.0f;
}

static void ApplyPatch(u32 text_addr, u32 text_size, int use_jal) {
  u32 i;
  for (i = 0; i < text_size; i += 4) {
    u32 addr = text_addr + i;

    // Fake L trigger as held
    if (_lw(addr + 0x00) == 0x30840004 && _lw(addr + 0x04) == 0x508000BA) {
      _sw(0, addr + 0x04);
      continue;
    }

    if (_lw(addr + 0x00) == 0xAFBF0008 && _lw(addr + 0x04) == 0x1C80000B) {
      // Fake control type B
      _sw(0, addr + 0x04);

      // Redirect left analog stick to right analog stick
      if (_lw(addr + 0x08) == 0x46007506) {
        if (use_jal) {
          MAKE_JAL(addr + 0x14, (intptr_t)leftAnalogX);
          MAKE_JAL(addr + 0x54, (intptr_t)leftAnalogX);
        } else {
          u32 syscall = sceKernelQuerySystemCall(leftAnalogX);
          _sw(_lw(addr + 0x18), addr + 0x14);
          _sw(_lw(addr + 0x58), addr + 0x54);
          MAKE_SYSCALL(addr + 0x18, syscall);
          MAKE_SYSCALL(addr + 0x58, syscall);
        }
      } else if (_lw(addr + 0x08) == 0x4480A000) {
        if (use_jal) {
          MAKE_JAL(addr + 0x14, (intptr_t)leftAnalogY);
          MAKE_JAL(addr + 0x54, (intptr_t)leftAnalogY);
        } else {
          u32 syscall = sceKernelQuerySystemCall(leftAnalogY);
          _sw(_lw(addr + 0x18), addr + 0x14);
          _sw(_lw(addr + 0x58), addr + 0x54);
          MAKE_SYSCALL(addr + 0x18, syscall);
          MAKE_SYSCALL(addr + 0x58, syscall);
        }
      }

      continue;
    }
  }

  sceKernelDcacheWritebackAll();
  sceKernelIcacheClearAll();
}

static int OnModuleStart(SceModule2 *mod) {
  if (strcmp(mod->modname, "MainApp") == 0) {
    ApplyPatch(mod->text_addr, mod->text_size, 0);
  }

  if (!previous)
    return 0;

  return previous(mod);
}

static void CheckModules() {
  SceUID modules[10];
  int count = 0;
  if (sceKernelGetModuleIdList(modules, sizeof(modules), &count) >= 0) {
    int i;
    SceKernelModuleInfo info;
    for (i = 0; i < count; ++i) {
      info.size = sizeof(SceKernelModuleInfo);
      if (sceKernelQueryModuleInfo(modules[i], &info) < 0) {
        continue;
      }

      if (strcmp(info.name, "MainApp") == 0) {
        ApplyPatch(info.text_addr, info.text_size, 1);
      }
    }
  }
}

int module_start(SceSize args, void *argp) {
  sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
  if (sceIoDevctl("kemulator:", EMULATOR_DEVCTL__IS_EMULATOR, NULL, 0, NULL, 0) == 0) {
    // Just scan the modules using normal/official syscalls.
    CheckModules();
  } else {
    previous = sctrlHENSetStartModuleHandler(OnModuleStart);
  }
  return 0;
}

-[Unknown]

@hrydgard
Copy link
Owner

hrydgard commented Sep 8, 2020

OK, I see now how the "true" makes sense.

Nice.

@Back2Life888
Copy link

I know that I am not a developer and I am not allowed to chat here but does that mean Cheat Device for the GTA PSP games works now?

@unknownbrackets
Copy link
Collaborator Author

I don't know what Cheat Device is, but no - I would assume this does not mean this. PSP firmware plugins are unsupported, so if that's some plugin people used on real PSPs it does not work.

-[Unknown]

@Freakler
Copy link

Freakler commented Nov 4, 2021

This might be a small detail/request but on real hardware argp in module_start contains the loaded prx's full path where currently with PPSSPP it is not used?!

Some plugins use this to check where they are loaded from to dynamically load more files from the same location. Especially since PPSSPP uses a different folder for plugins compared to CFWs this would be great to have!

Also thanks for the sample code @unknownbrackets , works great! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
HLE/Kernel Kernel, memory manager, other HLE issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants