15 Commits
Author SHA1 Message Date
CoronaSendandCopilot App 2387b8615a Select sends Back (star power); tilt bit parsed but unused by game
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 16:21:32 -06:00
CoronaSendandCopilot App 759763d729 Tool: plain-language menu
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:40:16 -06:00
CoronaSendandCopilot App b779ff4334 Tool: clean commented guitar_map.ini and tidy capture report
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:32:21 -06:00
CoronaSendandCopilot App f6415d6840 Replace scripts with GuitarTool.exe (installer + guitar mapper)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:31:45 -06:00
CoronaSendandCopilot App a063c0d5c4 Add one-click installer and simple READ_ME_FIRST
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:26:44 -06:00
CoronaSendandCopilot App f993ebb9a3 Include plugins.ini (RB4DX + guitar plugin)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:25:16 -06:00
CoronaSendandCopilot App a8cd38a175 Guide: zip layout mirrors PS4 paths
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:23:59 -06:00
CoronaSendandCopilot App c8d514ce1c Mapping tool: launcher stays open and shows errors
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:22:07 -06:00
CoronaSendandCopilot App 7076ef8ea3 Guide: describe zip contents
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:20:08 -06:00
CoronaSendandCopilot App 97dbf7eb3d Add step-by-step SETUP_GUIDE
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:18:35 -06:00
CoronaSendandCopilot App b7288bfe48 Add mapping file support and PC mapping tool for other guitars/adapters
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 05:16:36 -06:00
CoronaSendandCopilot App 4a109bcd59 Add Discord invite to README
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-11 00:00:29 -06:00
CoronaSendandCopilot App 26095c19ef Add Discord to notification; fix build.ps1 for paths with spaces
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-10 23:13:42 -06:00
CoronaSendandCopilot App f3b72af773 README: descriptive title
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-10 23:07:45 -06:00
CoronaSendandCopilot App 86861f3fdf README: explain what the plugin does
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-10 23:07:28 -06:00
10 changed files with 833 additions and 30 deletions

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
bin/
build/
build_shim/
Binary file not shown.
+42 -18
View File
@@ -1,11 +1,30 @@
# USB Guitar Plugin for Rock Band 4 (PS4)
# USB Guitar Plugin: Use a PS2 Guitar in Rock Band 4 on PS4
**Made by CoronaSend**
**Made by CoronaSend** | [Join the Discord](https://discord.gg/mGRCKG9BB)
A GoldHEN plugin that lets a **PS2 guitar on a generic PS2-to-USB adapter** work in **Rock Band 4** on a jailbroken PS4.
Tested with a generic adapter (USB ID `2563:0575`) on a jailbroken PS4 with GoldHEN and Rock Band 4 version **02.21**, USA (`CUSA02084`), together with RB4DX.
**Start here: download `Guitar_Plugin_Release.zip` from the Releases page, extract it, and double-click `GuitarTool.exe` (see `READ_ME_FIRST.txt`).** One program installs the plugin on your PS4 and maps any guitar that does not work out of the box. Rebuild it with `tools\build_tool.ps1`.
**Support status**
- `2563:0575`: verified on real hardware.
- `0810:0001` and `0810:0003` (other generic PS2 adapters): **untested.** They use the same button layout as the verified adapter. If your buttons come out wrong, your adapter's report differs. `tools\read_raw_report.ps1` prints the raw bytes, so you can report them.
- Only Rock Band 4 version **02.21** is supported. The plugin hooks fixed addresses in that build and does nothing on other versions.
- Not tested without RB4DX.
## What does this do?
Rock Band 4 on PS4 only accepts official PS4 instruments (plus some PS3-era ones). An old PS2 guitar on a cheap PS2-to-USB adapter is not recognized, so the game ignores it.
This plugin loads into Rock Band 4 through GoldHEN and sits between the game and the PS4's USB stack. When it sees the adapter, it:
1. Makes the game think the adapter is a PS3 Rock Band guitar.
2. Reads the adapter's raw button data and translates it into the format a PS3 Rock Band guitar sends (frets, strum, whammy, Start/Options).
You then play with your PS2 guitar as if it were a supported guitar. Nothing on the PS4 is permanently changed, and removing the plugin restores normal behavior.
## Requirements
- Jailbroken PS4 running GoldHEN (2.3.0 or newer) with the plugin loader enabled
@@ -36,7 +55,7 @@ Tested with a generic adapter (USB ID `2563:0575`) on a jailbroken PS4 with Gold
Don't replace an existing `plugins.ini` with the sample one in this folder. It exists only to show the format.
5. **Plug the guitar adapter into the PS4 before launching the game.**
6. Launch Rock Band 4. A "USB Guitar Plugin - Made by CoronaSend" notification means the plugin loaded.
6. Launch Rock Band 4. A "USB Guitar Plugin - Made by CoronaSend - Discord: @CoronaSend" notification means the plugin loaded.
## Uninstall
@@ -49,8 +68,9 @@ Delete the `OrbisInstrumentalizer.prx` line from `plugins.ini`, and delete the `
| Green, Red, Yellow, Blue, Orange | Same color fret |
| Strum up / down | Strum up / down |
| Whammy bar | Whammy |
| Start or Select | Options / Start (both do the same thing) |
| Tilt | Not mapped |
| Start | Options / Start |
| Select | Back, which activates **star power** in a song (tested) |
| Tilt | Not working: the adapter reports a tilt bit, but the game ignored it in tests. Use Select for star power. |
## Remapping
@@ -95,18 +115,22 @@ if ((raw[0] & 0x02) != 0) parsed_report.buttons |= BIT(0); // red
if ((raw[0] & 0x04) != 0) parsed_report.buttons |= BIT(2); // blue
```
### Example: make Select act as Back instead of Options
Replace the Start/Select line with:
```c
if ((raw[1] & 0x01) != 0) parsed_report.buttons |= BIT(8); // select -> back
if ((raw[1] & 0x02) != 0) parsed_report.buttons |= BIT(9); // start -> options
```
After any change, rebuild, copy the new `.prx` to the PS4, and **fully restart the game** (plugins only load at game launch).
## Using a different adapter
## Different guitar or adapter: the mapping tool (no coding)
If the game detects your guitar but buttons do nothing, or you use another adapter, make a map file:
1. Plug the adapter into a Windows PC and double-click `tools\RUN_ME.bat`.
2. Follow the prompts: press Enter, then hold each button for 3 seconds. Keep the guitar still while it records the idle state.
3. It saves `guitar_map.ini` (and `guitar_capture.txt`) to your Desktop.
4. Copy `guitar_map.ini` to `/data/GoldHEN/` on the PS4 (next to `plugins.ini`) and fully restart the game.
The klog shows `Mapping file loaded (id VVVV:PPPP)` when it is used. With no map file, the built-in layout for the verified 2563:0575 adapter and guitar is used. Map file format is one `key=byte,mask,value` per line (hex); a button is pressed when `(report[byte] & mask) == value`. Keys: `green red yellow blue orange start select strum_up strum_down`, plus `whammy=byte,rest,full` and `id=VID,PID`.
Tested: the generated layout for the 2563:0575 adapter and our guitar loads and works on a real PS4, including Select as star power. Other adapters and guitars are untested. Tilt does not work (see above), and the plugin still only supports Rock Band 4 version 02.21.
## Using a different adapter (manual)
1. Plug the adapter into a Windows PC, then run `tools\read_raw_report.ps1`:
@@ -129,10 +153,10 @@ You need a Windows PC with:
- The [OpenOrbis PS4 Toolchain](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/releases) release, extracted
- The [GoldHEN Plugins SDK](https://github.com/GoldHEN/GoldHEN_Plugins_SDK), cloned
Then in PowerShell, from this folder:
Then in PowerShell, from this folder (the bypass flag is needed if script execution is disabled):
```powershell
.\build.ps1 -Toolchain "C:\path\to\OpenOrbis\PS4Toolchain" -GoldHenSdk "C:\path\to\GoldHEN_Plugins_SDK"
powershell -ExecutionPolicy Bypass -File .\build.ps1 -Toolchain "C:\path\to\OpenOrbis\PS4Toolchain" -GoldHenSdk "C:\path\to\GoldHEN_Plugins_SDK"
```
The result is `bin\OrbisInstrumentalizer.prx`.
@@ -143,7 +167,7 @@ The result is `bin\OrbisInstrumentalizer.prx`.
- **RB4DX stopped loading:** a new `plugins.ini` replaces GoldHEN's defaults. List every plugin you use under the heading, including `RB4DX-Plugin.prx`.
- **Guitar not detected:** unplug the adapter, plug it into the PS4 **before** starting the game, and restart the game.
- **A fret is the wrong color or a button does nothing:** see "Remapping".
- **Menus can't be navigated with the guitar:** use a normal controller for menus. Start/Select work for Options only.
- **Menus can't be navigated with the guitar:** use a normal controller for menus. Start works for Options only, and Select is Back.
## Credits and license
+52
View File
@@ -0,0 +1,52 @@
PS2 GUITAR FOR ROCK BAND 4 ON PS4 - READ ME FIRST
=================================================
You need: a jailbroken PS4 with GoldHEN, Rock Band 4 version 02.21 (US),
a PS2 guitar + USB adapter, and a Windows PC on the same network as the PS4.
Everything is in ONE program: GuitarTool.exe (the plugin is built into it).
STEP 1 - INSTALL (about 2 minutes)
a) Right-click the zip and choose "Extract All".
b) On the PS4: jailbreak it, then in the GoldHEN menu turn ON the FTP server.
Find the PS4's IP address: Settings > Network > View Connection Status.
c) On the PC, double-click GuitarTool.exe and type 1, press Enter.
Type the PS4's IP address, press Enter. It does the rest.
d) Plug the guitar adapter into the PS4, fully close Rock Band 4, start it again.
Try the guitar. If it works, you are finished.
STEP 2 - ONLY IF THE GAME SEES THE GUITAR BUT THE BUTTONS DO NOTHING (or are wrong)
a) Unplug the adapter from the PS4 and plug it into the PC.
b) Double-click GuitarTool.exe and type 2, press Enter.
Follow the prompts: press Enter, then hold the named button when it says
HOLD NOW. Strum UP and DOWN are the two directions of the strum bar.
Hold the whammy bar all the way down for the last one.
c) The tool saves guitar_map.ini (exactly that name) on your Desktop and offers
to send it to the PS4 for you: type y and enter the PS4 IP.
(Or run option 1 later; it sends the map file for you.)
d) Plug the adapter back into the PS4, fully close Rock Band 4, start it again.
WHAT THE INSTALLER CHANGES ON THE PS4
/data/GoldHEN/plugins/OrbisInstrumentalizer.prx (the plugin)
/data/GoldHEN/plugins.ini (adds the plugin line under [CUSA02084];
your old file is saved to your Desktop first)
/data/GoldHEN/guitar_map.ini (only if you made a map in step 2)
If you use RB4DX, its line in plugins.ini is kept. RB4DX itself is not included.
DOING IT BY HAND (no tool): copy the files in the PS4_Files folder to the same
paths on the PS4 with any FTP program (port 2121) and add the plugin line to plugins.ini.
PROBLEMS
- "Could not connect": the PS4 isn't jailbroken yet, FTP is off, or the IP is wrong.
- Only Rock Band 4 version 02.21 works. Other versions do nothing.
- Only ONE guitar adapter should be plugged in while testing.
- To undo the map: delete /data/GoldHEN/guitar_map.ini from the PS4 and restart the game.
- Help: Discord https://discord.gg/mGRCKG9BB (@CoronaSend). Attach guitar_capture.txt
(made on your Desktop by step 2) if the buttons are wrong.
HONEST NOTES
- Verified on one adapter and guitar (USB ID 2563:0575). Other adapters and
guitars are untested; step 2 is how you teach it yours.
- Tilt (star power by tilting) does not work. Press the Select button on the guitar to activate star power instead.
- Windows may show a SmartScreen warning for an unsigned program: More info > Run anyway.
+4 -1
View File
@@ -49,7 +49,10 @@ foreach ($f in Get-ChildItem "$root\source\*.c") {
ld.lld "$GoldHenSdk\build\crtprx.o" (Get-ChildItem "$build\*.o").FullName -o "$bin\OrbisInstrumentalizer.elf" -m elf_x86_64 -pie --script "$Toolchain\link.x" -e _init --eh-frame-hdr "-L$Toolchain\lib" "-L$GoldHenSdk" -lGoldHEN_Hook -lkernel -lSceLibcInternal -lSceSysmodule -lSceUsbd
if ($LASTEXITCODE) { throw 'Link failed' }
cmd /c "`"$Toolchain\bin\windows\create-fself.exe`" -in=$bin\OrbisInstrumentalizer.elf -out=$bin\OrbisInstrumentalizer.oelf --lib=$bin\OrbisInstrumentalizer.prx --paid 0x3800000000000011"
$env:OO_PS4_TOOLCHAIN = $Toolchain
Push-Location $bin
cmd /c "`"$Toolchain\bin\windows\create-fself.exe`" -in=OrbisInstrumentalizer.elf -out=OrbisInstrumentalizer.oelf --lib=OrbisInstrumentalizer.prx --paid 0x3800000000000011"
Pop-Location
if (-not (Test-Path "$bin\OrbisInstrumentalizer.prx")) { throw 'create-fself failed' }
Remove-Item $shim -Recurse -Force
+3 -2
View File
@@ -1,4 +1,5 @@
; Sample only - add these lines to your existing plugins.ini, don't replace it.
[settings]
show_load_notification=true
[CUSA02084]
; /data/GoldHEN/plugins/RB4DX-Plugin.prx (uncomment if you use RB4DX)
/data/GoldHEN/plugins/RB4DX-Plugin.prx
/data/GoldHEN/plugins/OrbisInstrumentalizer.prx
+1 -1
View File
@@ -72,7 +72,7 @@ int32_t attr_module_hidden module_start(size_t argc, const void *args)
for (int i = 0; i < num_UsbdHookTitleIDs; i++) {
if (strcmp(procInfo.titleid, UsbdHookTitleIDs[i]) == 0) {
final_printf("Applying sceUsbd hooks...\n");
DoNotification("USB Guitar Plugin\nMade by CoronaSend");
DoNotification("USB Guitar Plugin\nMade by CoronaSend\nDiscord: @CoronaSend");
InitUsbdHooks();
UsingUsbdHooks = true;
}
+176 -8
View File
@@ -27,9 +27,139 @@ typedef enum _OIRB4DeviceType {
RB4_Type_PS2Guitar
} OIRB4DeviceType;
// generic PS2-to-USB adapter (ShanWan style) with a PS2 guitar plugged in
#define PS2_ADAPTER_VID 0x2563
#define PS2_ADAPTER_PID 0x0575
// generic PS2-to-USB adapters with a PS2 guitar plugged in
// 2563:0575 (ShanWan style) is verified on hardware.
// The others are generic PS2 adapters taken from SDL's controller list and are UNTESTED:
// they use the same parser, so buttons may be wrong if their report layout differs.
typedef struct _PS2AdapterId {
uint16_t vid;
uint16_t pid;
} PS2AdapterId;
static const PS2AdapterId ps2_adapters[] = {
{ 0x2563, 0x0575 }, // verified
{ 0x0810, 0x0001 }, // untested
{ 0x0810, 0x0003 }, // untested
};
// Optional mapping file made by the PC capture tool (tools\guided_capture.ps1).
// Each button is "byte,mask,value" in hex: pressed when (report[byte] & mask) == value.
// With no file present, the built-in mapping for the verified adapter is used.
#define MAP_PATH "/data/GoldHEN/guitar_map.ini"
typedef struct _MapEntry {
bool set;
uint8_t byte, mask, val;
} MapEntry;
static struct {
bool loaded;
bool has_id;
uint16_t vid, pid;
MapEntry green, red, yellow, blue, orange, start, select, up, down, tilt;
bool whammy_set;
uint8_t wbyte, wrest, wfull;
int max_byte;
} g_map;
static int HexVal(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
// parses up to 3 comma-separated hex numbers, returns how many were read
static int ParseHexList(const char *s, unsigned *out, int max) {
int n = 0;
while (*s && *s != '\n' && *s != '\r' && n < max) {
unsigned v = 0;
int digits = 0;
while (HexVal(*s) >= 0) { v = (v << 4) | (unsigned)HexVal(*s); s++; digits++; }
if (digits == 0) break;
out[n++] = v;
if (*s == ',') s++;
else break;
}
return n;
}
static void SetEntry(MapEntry *e, const char *val) {
unsigned v[3];
if (ParseHexList(val, v, 3) == 3 && v[0] < 32) {
e->set = true;
e->byte = (uint8_t)v[0];
e->mask = (uint8_t)v[1];
e->val = (uint8_t)v[2];
if ((int)v[0] > g_map.max_byte) g_map.max_byte = (int)v[0];
}
}
static void LoadMapFile(void) {
char text[1024];
int fd = sceKernelOpen(MAP_PATH, 0, 0);
if (fd < 0)
return;
int len = (int)sceKernelRead(fd, text, sizeof(text) - 1);
sceKernelClose(fd);
if (len <= 0)
return;
text[len] = 0;
memset(&g_map, 0, sizeof(g_map));
g_map.max_byte = -1;
char *line = text;
while (*line) {
char *eol = line;
while (*eol && *eol != '\n') eol++;
char saved = *eol;
*eol = 0;
char *eq = line;
while (*eq && *eq != '=') eq++;
if (*eq == '=') {
*eq = 0;
const char *key = line, *val = eq + 1;
unsigned v[3];
if (strcmp(key, "id") == 0) {
// "id=VID,PID"
if (ParseHexList(val, v, 2) == 2) { g_map.has_id = true; g_map.vid = (uint16_t)v[0]; g_map.pid = (uint16_t)v[1]; }
} else if (strcmp(key, "green") == 0) SetEntry(&g_map.green, val);
else if (strcmp(key, "red") == 0) SetEntry(&g_map.red, val);
else if (strcmp(key, "yellow") == 0) SetEntry(&g_map.yellow, val);
else if (strcmp(key, "blue") == 0) SetEntry(&g_map.blue, val);
else if (strcmp(key, "orange") == 0) SetEntry(&g_map.orange, val);
else if (strcmp(key, "start") == 0) SetEntry(&g_map.start, val);
else if (strcmp(key, "select") == 0) SetEntry(&g_map.select, val);
else if (strcmp(key, "strum_up") == 0) SetEntry(&g_map.up, val);
else if (strcmp(key, "strum_down") == 0) SetEntry(&g_map.down, val);
else if (strcmp(key, "tilt") == 0) SetEntry(&g_map.tilt, val);
else if (strcmp(key, "whammy") == 0) {
// "whammy=byte,rest,full"
if (ParseHexList(val, v, 3) == 3 && v[0] < 32 && v[1] != v[2]) {
g_map.whammy_set = true;
g_map.wbyte = (uint8_t)v[0];
g_map.wrest = (uint8_t)v[1];
g_map.wfull = (uint8_t)v[2];
if ((int)v[0] > g_map.max_byte) g_map.max_byte = (int)v[0];
}
}
}
*eol = saved;
if (!saved) break;
line = eol + 1;
}
g_map.loaded = g_map.green.set && g_map.red.set && g_map.yellow.set && g_map.blue.set && g_map.orange.set;
klog("[OrbisInstrumentalizer] Mapping file %s (id %04x:%04x)\n", g_map.loaded ? "loaded" : "incomplete, ignored", g_map.vid, g_map.pid);
}
static bool IsPS2AdapterId(uint16_t vid, uint16_t pid) {
if (g_map.loaded && g_map.has_id && g_map.vid == vid && g_map.pid == pid)
return true;
for (size_t i = 0; i < sizeof(ps2_adapters) / sizeof(ps2_adapters[0]); i++) {
if (ps2_adapters[i].vid == vid && ps2_adapters[i].pid == pid)
return true;
}
return false;
}
typedef struct _OIRB4OpenDevice {
bool is_open;
@@ -141,6 +271,8 @@ static OIRB4DeviceType IdentifyDevice(libusb_device *device) {
}
#define BIT(i) (1 << i)
// accel_x value sent while the guitar is tilted up (0x00 is level; matches the original plugin tilt mapping)
#define TILT_UP 0x7F
// TODO: this seems bad i dont even know if it do drum
typedef struct _ps3_rb_guitar_report {
uint16_t buttons;
@@ -197,8 +329,43 @@ void ParseXInputCallback(struct libusb_transfer *transfer) {
// [1] bit0 select, bit1 start
// [2] hat: 6 = idle, 5 = strum down, 7 = strum up
// [4] whammy axis: 0x7F at rest, 0x00 fully pressed
static bool MapPressed(const MapEntry *e, const uint8_t *raw) {
return e->set && (raw[e->byte] & e->mask) == e->val;
}
static void ParseMappedPS2(struct libusb_transfer *transfer) {
uint8_t raw[32] = { 0 };
int n = transfer->actual_length < (int)sizeof(raw) ? transfer->actual_length : (int)sizeof(raw);
memcpy(raw, transfer->buffer, n);
ps3_rb_guitar_report parsed_report = { 0 };
if (MapPressed(&g_map.green, raw)) parsed_report.buttons |= BIT(1);
if (MapPressed(&g_map.red, raw)) parsed_report.buttons |= BIT(0);
if (MapPressed(&g_map.yellow, raw)) parsed_report.buttons |= BIT(4);
if (MapPressed(&g_map.blue, raw)) parsed_report.buttons |= BIT(2);
if (MapPressed(&g_map.orange, raw)) parsed_report.buttons |= BIT(3);
if (MapPressed(&g_map.start, raw)) parsed_report.buttons |= BIT(9);
if (MapPressed(&g_map.select, raw)) parsed_report.buttons |= BIT(8); // back / star power
parsed_report.hat = 0x08;
if (MapPressed(&g_map.up, raw)) parsed_report.hat = 0x00;
else if (MapPressed(&g_map.down, raw)) parsed_report.hat = 0x04;
if (g_map.whammy_set) {
int span = (int)g_map.wfull - (int)g_map.wrest;
int v = ((int)raw[g_map.wbyte] - (int)g_map.wrest) * 255 / span;
parsed_report.whammy = (uint8_t)(v < 0 ? 0 : (v > 255 ? 255 : v));
}
parsed_report.accel_x = MapPressed(&g_map.tilt, raw) ? TILT_UP : 0x00;
size_t len = transfer->length < sizeof(parsed_report) ? transfer->length : sizeof(parsed_report);
memcpy(transfer->buffer, &parsed_report, len);
}
void ParsePS2Callback(struct libusb_transfer *transfer) {
if (transfer->status == LIBUSB_TRANSFER_COMPLETED && transfer->actual_length >= 7) {
if (transfer->status == LIBUSB_TRANSFER_COMPLETED && g_map.loaded && transfer->actual_length > g_map.max_byte) {
ParseMappedPS2(transfer);
} else if (transfer->status == LIBUSB_TRANSFER_COMPLETED && !g_map.loaded && transfer->actual_length >= 7) {
uint8_t raw[8];
memcpy(raw, transfer->buffer, sizeof(raw));
ps3_rb_guitar_report parsed_report = { 0 };
@@ -209,15 +376,15 @@ void ParsePS2Callback(struct libusb_transfer *transfer) {
if ((raw[0] & 0x01) != 0) parsed_report.buttons |= BIT(4); // yellow
if ((raw[0] & 0x04) != 0) parsed_report.buttons |= BIT(2); // blue
if ((raw[0] & 0x08) != 0) parsed_report.buttons |= BIT(3); // orange
if ((raw[1] & 0x03) != 0) parsed_report.buttons |= BIT(9); // start and select both act as start/options
if ((raw[1] & 0x02) != 0) parsed_report.buttons |= BIT(9); // start
if ((raw[1] & 0x01) != 0) parsed_report.buttons |= BIT(8); // select = back / star power
parsed_report.accel_x = (raw[0] & 0x40) ? TILT_UP : 0x00; // tilt switch
parsed_report.hat = 0x08;
if (raw[2] == 7) parsed_report.hat = 0x00; // strum up
if (raw[2] == 5) parsed_report.hat = 0x04; // strum down
parsed_report.whammy = (raw[4] >= 0x7F) ? 0x00 : (uint8_t)((0x7F - raw[4]) * 2);
parsed_report.accel_x = 0x80;
size_t len = transfer->length < sizeof(parsed_report) ? transfer->length : sizeof(parsed_report);
memcpy(transfer->buffer, &parsed_report, len);
}
@@ -302,7 +469,7 @@ int TsceUsbdGetDeviceDescriptor_hook(libusb_device *device, struct libusb_device
}
}
if (desc->idVendor == PS2_ADAPTER_VID && desc->idProduct == PS2_ADAPTER_PID) {
if (IsPS2AdapterId(desc->idVendor, desc->idProduct)) {
RegisterPS2Device(device);
desc->idVendor = 0x12BA; // licensed by SCEA
desc->idProduct = 0x0200; // PS3 Rock Band guitar
@@ -350,6 +517,7 @@ void TsceUsbdFillInterruptTransfer_hook(struct libusb_transfer *transfer, libusb
void InitUsbdHooks() {
// make sure we have the USBD module loaded into memory
sceSysmoduleLoadModule(ORBIS_SYSMODULE_USBD);
LoadMapFile();
struct proc_info procInfo;
sys_sdk_proc_info(&procInfo);
+541
View File
@@ -0,0 +1,541 @@
// GuitarTool.exe - installs the PS2 guitar plugin for Rock Band 4 on a jailbroken PS4 and
// builds a guitar_map.ini for any guitar/adapter plugged into this Windows PC.
// Built with the C# compiler that ships with Windows (no extra installs needed to run).
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
static class Native
{
[DllImport("hid.dll")] public static extern void HidD_GetHidGuid(out Guid g);
[DllImport("hid.dll", SetLastError = true)] public static extern bool HidD_GetAttributes(SafeFileHandle h, ref HIDD_ATTRIBUTES a);
[DllImport("hid.dll", SetLastError = true)] public static extern bool HidD_GetProductString(SafeFileHandle h, byte[] buf, int len);
[DllImport("hid.dll", SetLastError = true)] public static extern bool HidD_GetPreparsedData(SafeFileHandle h, out IntPtr p);
[DllImport("hid.dll")] public static extern bool HidD_FreePreparsedData(IntPtr p);
[DllImport("hid.dll")] public static extern int HidP_GetCaps(IntPtr p, out HIDP_CAPS c);
[DllImport("setupapi.dll", SetLastError = true)] public static extern IntPtr SetupDiGetClassDevs(ref Guid g, IntPtr e, IntPtr h, int flags);
[DllImport("setupapi.dll", SetLastError = true)] public static extern bool SetupDiEnumDeviceInterfaces(IntPtr set, IntPtr info, ref Guid g, int idx, ref SP_DEVICE_INTERFACE_DATA d);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr set, ref SP_DEVICE_INTERFACE_DATA d, IntPtr detail, int size, out int req, IntPtr info);
[DllImport("setupapi.dll")] public static extern bool SetupDiDestroyDeviceInfoList(IntPtr set);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern SafeFileHandle CreateFile(string f, uint access, uint share, IntPtr sec, uint disp, uint flags, IntPtr tmpl);
[StructLayout(LayoutKind.Sequential)] public struct HIDD_ATTRIBUTES { public int Size; public ushort VendorID; public ushort ProductID; public ushort VersionNumber; }
[StructLayout(LayoutKind.Sequential)] public struct SP_DEVICE_INTERFACE_DATA { public int cbSize; public Guid InterfaceClassGuid; public int Flags; public IntPtr Reserved; }
[StructLayout(LayoutKind.Sequential)]
public struct HIDP_CAPS
{
public ushort Usage, UsagePage, InputReportByteLength, OutputReportByteLength, FeatureReportByteLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] public ushort[] Reserved;
public ushort NumberLinkCollectionNodes, NumberInputButtonCaps, NumberInputValueCaps, NumberInputDataIndices,
NumberOutputButtonCaps, NumberOutputValueCaps, NumberOutputDataIndices,
NumberFeatureButtonCaps, NumberFeatureValueCaps, NumberFeatureDataIndices;
}
}
class HidDev
{
public string Path; public ushort Vid, Pid, Page, Usage; public int InLen; public string Name;
public string Id { get { return Vid.ToString("X4") + ":" + Pid.ToString("X4"); } }
public static List<HidDev> Enumerate()
{
var list = new List<HidDev>();
Guid g; Native.HidD_GetHidGuid(out g);
IntPtr set = Native.SetupDiGetClassDevs(ref g, IntPtr.Zero, IntPtr.Zero, 0x12);
if (set == new IntPtr(-1)) return list;
try
{
for (int i = 0; ; i++)
{
var d = new Native.SP_DEVICE_INTERFACE_DATA();
d.cbSize = Marshal.SizeOf(typeof(Native.SP_DEVICE_INTERFACE_DATA));
if (!Native.SetupDiEnumDeviceInterfaces(set, IntPtr.Zero, ref g, i, ref d)) break;
int req;
Native.SetupDiGetDeviceInterfaceDetail(set, ref d, IntPtr.Zero, 0, out req, IntPtr.Zero);
IntPtr buf = Marshal.AllocHGlobal(req);
try
{
Marshal.WriteInt32(buf, IntPtr.Size == 8 ? 8 : 6);
if (!Native.SetupDiGetDeviceInterfaceDetail(set, ref d, buf, req, out req, IntPtr.Zero)) continue;
string path = Marshal.PtrToStringAuto(new IntPtr(buf.ToInt64() + 4));
var dev = Probe(path);
if (dev != null) list.Add(dev);
}
finally { Marshal.FreeHGlobal(buf); }
}
}
finally { Native.SetupDiDestroyDeviceInfoList(set); }
return list;
}
static HidDev Probe(string path)
{
using (var h = Native.CreateFile(path, 0, 3, IntPtr.Zero, 3, 0, IntPtr.Zero))
{
if (h.IsInvalid) return null;
var a = new Native.HIDD_ATTRIBUTES(); a.Size = Marshal.SizeOf(typeof(Native.HIDD_ATTRIBUTES));
if (!Native.HidD_GetAttributes(h, ref a)) return null;
IntPtr pp; if (!Native.HidD_GetPreparsedData(h, out pp)) return null;
Native.HIDP_CAPS caps;
int st = Native.HidP_GetCaps(pp, out caps);
Native.HidD_FreePreparsedData(pp);
if (st != 0x110000 || caps.InputReportByteLength == 0) return null;
bool skip = (caps.UsagePage == 1 && (caps.Usage == 2 || caps.Usage == 6 || caps.Usage == 0x80)) || caps.UsagePage == 0x0C;
if (skip) return null;
var nb = new byte[256]; string name = "";
if (Native.HidD_GetProductString(h, nb, nb.Length)) name = Encoding.Unicode.GetString(nb).TrimEnd('\0');
return new HidDev { Path = path, Vid = a.VendorID, Pid = a.ProductID, Page = caps.UsagePage, Usage = caps.Usage, InLen = caps.InputReportByteLength, Name = name.Length > 0 ? name : "(unnamed device)" };
}
}
}
class Reader : IDisposable
{
FileStream fs; byte[] buf; Task<int> pending;
public Reader(HidDev d)
{
var h = Native.CreateFile(d.Path, 0x80000000, 3, IntPtr.Zero, 3, 0x40000000, IntPtr.Zero);
if (h.IsInvalid) throw new IOException("Could not open the device for reading.");
fs = new FileStream(h, FileAccess.Read, 64, true);
buf = new byte[Math.Max(64, d.InLen)];
}
// Returns the report without the leading report-ID byte (what the PS4 plugin sees), or null on timeout.
public byte[] Next(int ms)
{
if (pending == null) pending = fs.ReadAsync(buf, 0, buf.Length);
try { if (!pending.Wait(ms)) return null; }
catch (AggregateException) { pending = null; throw new IOException("The device stopped responding. Unplug it, plug it back in, and try again."); }
int n = pending.Result; pending = null;
if (n < 2) return new byte[0];
var r = new byte[n - 1]; Array.Copy(buf, 1, r, 0, n - 1); return r;
}
public void Dispose() { if (fs != null) fs.Dispose(); }
}
class Entry { public int B, M, V; public Entry(int b, int m, int v) { B = b; M = m; V = v; } public override string ToString() { return B.ToString("x") + "," + M.ToString("x") + "," + V.ToString("x"); } }
static class Mapper
{
public static int Pop(int v) { int n = 0; while (v != 0) { n += v & 1; v >>= 1; } return n; }
// Pressed when (report[byte] & mask) == val. Picks the changed byte with the fewest changed bits, so
// a single button bit wins over an analog byte that merely jitters.
public static Entry Btn(byte[] idle, byte[] p)
{
Entry best = null; int bp = 99; int n = Math.Min(idle.Length, p.Length);
for (int i = 0; i < n; i++)
if (idle[i] != p[i]) { int m = idle[i] ^ p[i]; int pc = Pop(m); if (pc < bp) { bp = pc; best = new Entry(i, m, p[i] & m); } }
return best;
}
// Strum is often a hat value (several states in one nibble), so compare the whole nibble.
public static bool Strum(byte[] idle, byte[] up, byte[] down, out Entry u, out Entry d)
{
int n = Math.Min(idle.Length, Math.Min(up.Length, down.Length));
for (int i = 0; i < n; i++)
{
if (idle[i] == up[i] || idle[i] == down[i] || up[i] == down[i]) continue;
int ch = (idle[i] ^ up[i]) | (idle[i] ^ down[i]);
int mask = 0;
if ((ch & 0xF0) == 0) mask = 0x0F; else if ((ch & 0x0F) == 0) mask = 0xF0;
if (mask != 0) { u = new Entry(i, mask, up[i] & mask); d = new Entry(i, mask, down[i] & mask); return true; }
}
u = Btn(idle, up); d = Btn(idle, down);
return u != null && d != null;
}
public static bool Whammy(byte[] idle, byte[] p, out int b, out int rest, out int full)
{
int n = Math.Min(idle.Length, p.Length), best = -1, diff = 0;
for (int i = 0; i < n; i++) { int dd = Math.Abs(idle[i] - p[i]); if (dd > diff) { diff = dd; best = i; } }
b = best; rest = best >= 0 ? idle[best] : 0; full = best >= 0 ? p[best] : 0;
return best >= 0 && diff >= 16;
}
public static List<string> Build(string vid, string pid, Dictionary<string, byte[]> c, List<string> problems)
{
var lines = new List<string>();
lines.Add("id=" + vid.ToLower() + "," + pid.ToLower());
var idle = c["idle"]; var seen = new Dictionary<string, string>();
foreach (var k in new[] { "green", "red", "yellow", "blue", "orange", "start", "select" })
{
var e = Btn(idle, c[k]);
if (e == null) { problems.Add(k.ToUpper() + " - nothing changed while you held it"); continue; }
string sig = e.ToString();
if (seen.ContainsKey(sig)) { problems.Add(k.ToUpper() + " gave the same signal as " + seen[sig].ToUpper() + " - was the right button held?"); continue; }
seen[sig] = k; lines.Add(k + "=" + sig);
}
Entry u, d;
if (Strum(idle, c["strum_up"], c["strum_down"], out u, out d)) { lines.Add("strum_up=" + u); lines.Add("strum_down=" + d); }
else problems.Add("STRUM - could not tell up from down");
int wb, wr, wf;
if (Whammy(idle, c["whammy"], out wb, out wr, out wf)) lines.Add("whammy=" + wb.ToString("x") + "," + wr.ToString("x") + "," + wf.ToString("x"));
else problems.Add("WHAMMY - no analog change found (this guitar may have no whammy output)");
return lines;
}
}
static class Ftp
{
static FtpWebRequest Req(string ip, string path, string method)
{
var r = (FtpWebRequest)WebRequest.Create("ftp://" + ip + ":2121" + path);
r.Method = method; r.Credentials = new NetworkCredential("anonymous", "guest@");
r.UsePassive = true; r.UseBinary = true; r.KeepAlive = false; r.Timeout = 10000; r.ReadWriteTimeout = 15000;
return r;
}
public static string List(string ip, string path)
{
using (var resp = (FtpWebResponse)Req(ip, path, WebRequestMethods.Ftp.ListDirectoryDetails).GetResponse())
using (var sr = new StreamReader(resp.GetResponseStream())) return sr.ReadToEnd();
}
public static void Put(string ip, string path, byte[] data)
{
var r = Req(ip, path, WebRequestMethods.Ftp.UploadFile);
using (var s = r.GetRequestStream()) s.Write(data, 0, data.Length);
r.GetResponse().Close();
}
public static byte[] Get(string ip, string path)
{
try
{
using (var resp = (FtpWebResponse)Req(ip, path, WebRequestMethods.Ftp.DownloadFile).GetResponse())
using (var s = resp.GetResponseStream()) using (var ms = new MemoryStream()) { s.CopyTo(ms); return ms.ToArray(); }
}
catch (WebException ex)
{
var fr = ex.Response as FtpWebResponse;
if (fr != null && fr.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable) return null;
throw;
}
}
}
static class Program
{
const string Section = "[CUSA02084]";
const string PluginLine = "/data/GoldHEN/plugins/OrbisInstrumentalizer.prx";
const string Rb4dxLine = "/data/GoldHEN/plugins/RB4DX-Plugin.prx";
static string lastIp = "";
static readonly UTF8Encoding Utf8 = new UTF8Encoding(false);
static string Desktop { get { return Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); } }
static string Ask(string q) { Console.Write(q); string s = Console.ReadLine(); return s == null ? "" : s.Trim(); }
static void Say(string s) { Console.WriteLine(s); }
// Adds missing lines under [CUSA02084] and leaves everything else as it was.
public static string UpdateIni(string text, string[] need, out bool changed)
{
var L = new List<string>();
if (string.IsNullOrWhiteSpace(text)) text = "[settings]\nshow_load_notification=true";
foreach (var x in text.Replace("\r\n", "\n").Split('\n')) L.Add(x);
while (L.Count > 0 && L[L.Count - 1].Trim() == "") L.RemoveAt(L.Count - 1);
int idx = -1;
for (int i = 0; i < L.Count; i++) if (L[i].Trim() == Section) { idx = i; break; }
if (idx < 0) { L.Add(Section); idx = L.Count - 1; }
int end = idx + 1;
while (end < L.Count && !L[end].Trim().StartsWith("[")) end++;
changed = false;
foreach (var n in need)
{
bool has = false;
for (int i = idx + 1; i < end; i++) { string t = L[i].Trim(); if (t == n || t.StartsWith(n + "=")) has = true; }
if (!has) { L.Insert(end, n); end++; changed = true; }
}
return string.Join("\n", L.ToArray()) + "\n";
}
static byte[] PluginBytes()
{
using (var s = Assembly.GetExecutingAssembly().GetManifestResourceStream("plugin.prx"))
{
if (s == null) throw new Exception("The plugin is missing from this program.");
using (var ms = new MemoryStream()) { s.CopyTo(ms); return ms.ToArray(); }
}
}
static string GetIp(string given)
{
if (!string.IsNullOrEmpty(given)) { lastIp = given; return given; }
Say("");
Say("Do these on the PS4 first:");
Say(" 1. Turn the PS4 on and run the jailbreak so GoldHEN loads.");
Say(" 2. Open the GoldHEN menu and turn ON the FTP server.");
Say(" 3. Find the PS4's IP address: Settings > Network > View Connection Status.");
Say("The PC and the PS4 must be on the same network (same router).");
Say("");
string def = lastIp.Length > 0 ? " [" + lastIp + "]" : "";
string ip = Ask("Type the PS4 IP address (example 192.168.1.27)" + def + " and press Enter: ");
if (ip.Length == 0) ip = lastIp;
lastIp = ip;
return ip;
}
static bool Connect(string ip)
{
try { Ftp.List(ip, "/data/GoldHEN/"); return true; }
catch (Exception ex)
{
Say("");
Say("COULD NOT CONNECT to " + ip + " on port 2121.");
Say("Check: the PS4 is on and jailbroken, the GoldHEN FTP server is ON, and the IP is right.");
Say("(" + ex.Message + ")");
return false;
}
}
static void Install(string ipArg)
{
Say("");
Say("--- INSTALL THE PLUGIN ON THE PS4 ---");
string ip = GetIp(ipArg);
if (ip.Length == 0) { Say("No IP address entered."); return; }
Say("[1/4] Connecting to the PS4...");
if (!Connect(ip)) return;
byte[] prx = PluginBytes();
Say("[2/4] Sending the plugin (" + prx.Length + " bytes)...");
Ftp.Put(ip, "/data/GoldHEN/plugins/OrbisInstrumentalizer.prx", prx);
Say("[3/4] Checking plugins.ini...");
string plugins = Ftp.List(ip, "/data/GoldHEN/plugins/");
var need = new List<string>();
if (plugins.Contains("RB4DX-Plugin.prx")) need.Add(Rb4dxLine);
need.Add(PluginLine);
byte[] oldB = Ftp.Get(ip, "/data/GoldHEN/plugins.ini");
string old = oldB == null ? "" : Utf8.GetString(oldB);
bool changed; string neu = UpdateIni(old, need.ToArray(), out changed);
if (changed)
{
if (old.Length > 0)
{
string bk = Path.Combine(Desktop, "plugins_backup_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".ini");
File.WriteAllText(bk, old, Utf8);
Say("Your old plugins.ini was saved to: " + bk);
}
Ftp.Put(ip, "/data/GoldHEN/plugins.ini", Utf8.GetBytes(neu));
Say("plugins.ini updated.");
}
else Say("plugins.ini was already correct.");
Say("[4/4] Checking for a map file...");
string map = Path.Combine(Desktop, "guitar_map.ini");
bool sentMap = false;
if (File.Exists(map))
{
Ftp.Put(ip, "/data/GoldHEN/guitar_map.ini", File.ReadAllBytes(map));
Say("Found guitar_map.ini on your Desktop and sent it to the PS4.");
sentMap = true;
}
else Say("No guitar_map.ini on your Desktop (that is fine).");
Say("");
Say("INSTALL COMPLETE. What to do now:");
Say(" 1. Plug the guitar adapter into the PS4.");
Say(" 2. FULLY CLOSE Rock Band 4 (PS button > Close Application), then start it again.");
Say(" 3. Try the guitar in the menus.");
if (!sentMap) Say(" 4. If the game sees the guitar but the buttons do nothing, come back and choose option 2.");
}
static Dictionary<string, byte[]> cap;
static byte[] Capture(Reader r, double seconds, string label)
{
var counts = new Dictionary<string, int>(); var keep = new Dictionary<string, byte[]>();
var sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < seconds)
{
var b = r.Next(300);
if (b == null || b.Length == 0) continue;
string k = BitConverter.ToString(b);
int c; counts.TryGetValue(k, out c); counts[k] = c + 1; keep[k] = b;
}
string best = null; int bc = 0;
foreach (var kv in counts) if (kv.Value > bc) { bc = kv.Value; best = kv.Key; }
return best == null ? null : keep[best];
}
static void Drain(Reader r, int ms) { var sw = Stopwatch.StartNew(); while (sw.ElapsedMilliseconds < ms) r.Next(50); }
static void MakeMap()
{
Say("");
Say("--- MAKE A MAP FILE FOR MY GUITAR ---");
Say("This teaches the plugin which button is which on YOUR guitar. It takes about 2 minutes.");
Say("");
Say("Before you start:");
Say(" 1. Unplug the guitar adapter from the PS4 and plug it into THIS PC.");
Say(" 2. Have the guitar in your hands.");
Say(" 3. You will be asked to hold 10 things, one at a time, for about 2 seconds each:");
Say(" green, red, yellow, blue, orange, strum up, strum down, start, select, whammy.");
Say(" Only hold the ONE thing it asks for. Let go when it says it got it.");
Ask("Press Enter when the adapter is plugged into this PC. ");
var devs = HidDev.Enumerate();
if (devs.Count == 0) { Say("No game controller found. Plug the guitar adapter into THIS PC (not the PS4) and try again."); return; }
HidDev dev = null;
foreach (var d in devs) if ((d.Vid == 0x2563 && d.Pid == 0x0575) || d.Vid == 0x0810) { dev = d; break; }
if (dev == null)
{
Say("Devices found:");
for (int i = 0; i < devs.Count; i++) Say(string.Format(" [{0}] {1} ({2})", i + 1, devs[i].Name, devs[i].Id));
int pick = 1;
if (devs.Count > 1) { if (!int.TryParse(Ask("Type the number of your guitar adapter and press Enter: "), out pick) || pick < 1 || pick > devs.Count) { Say("Not a valid number."); return; } }
dev = devs[pick - 1];
}
Say("Using: " + dev.Name + " (USB ID " + dev.Id + ")");
var raw = new List<string>();
cap = new Dictionary<string, byte[]>();
using (var r = new Reader(dev))
{
Say("");
Ask("STEP 0: Put the guitar DOWN on a table, touch NOTHING, then press Enter. ");
Say(" recording the resting state...");
Drain(r, 300);
cap["idle"] = Capture(r, 2.5, "idle");
if (cap["idle"] == null) { Say("No data came from the adapter. Try another USB port, or press a button while it is plugged in."); return; }
raw.Add("IDLE: " + BitConverter.ToString(cap["idle"]));
var steps = new[] {
new[] { "green", "GREEN fret" }, new[] { "red", "RED fret" }, new[] { "yellow", "YELLOW fret" },
new[] { "blue", "BLUE fret" }, new[] { "orange", "ORANGE fret" },
new[] { "strum_up", "STRUM UP (push the strum bar up)" }, new[] { "strum_down", "STRUM DOWN (push the strum bar down)" },
new[] { "start", "START" }, new[] { "select", "SELECT" }, new[] { "whammy", "WHAMMY bar pushed ALL the way down" } };
int stepNo = 0;
foreach (var s in steps)
{
stepNo++;
Say("");
Say(string.Format("STEP {0} of {1}: {2}", stepNo, steps.Length, s[1]));
Ask(" Get your hand ready, press Enter, then HOLD it when it says HOLD NOW. ");
Say(" get ready...");
Drain(r, 900);
Say(" >>> HOLD NOW! (keep holding about 2 seconds) <<<");
var b = Capture(r, 2.5, s[0]);
Say(" OK, let go.");
if (b == null) b = cap["idle"];
cap[s[0]] = b;
raw.Add(s[0] + ": " + BitConverter.ToString(b));
}
}
var problems = new List<string>();
var lines = Mapper.Build(dev.Vid.ToString("X4"), dev.Pid.ToString("X4"), cap, problems);
string safeName = dev.Name.Replace("=", "-").Replace("\r", " ").Replace("\n", " ");
if (safeName.Length > 40) safeName = safeName.Substring(0, 40);
var mapText = new List<string>();
mapText.Add("# Guitar map made by GuitarTool for " + safeName + " on " + DateTime.Now.ToString("yyyy-MM-dd"));
mapText.Add("# Put this file at /data/GoldHEN/guitar_map.ini on the PS4. Each line: button, byte, mask, value (hex).");
mapText.AddRange(lines);
string ini = Path.Combine(Desktop, "guitar_map.ini");
File.WriteAllText(ini, string.Join("\r\n", mapText.ToArray()).Replace("\r\n", "\n") + "\n", Utf8);
var report = new List<string>();
report.Add("GuitarTool capture report - " + DateTime.Now.ToString("yyyy-MM-dd HH:mm"));
report.Add("Device: " + dev.Name + " USB ID: " + dev.Id + " report length: " + (dev.InLen - 1) + " bytes");
report.Add("");
report.Add("Raw reports (one per button, hex bytes):");
report.AddRange(raw);
report.Add("");
report.Add("Map that was generated:");
report.AddRange(lines);
if (problems.Count > 0) { report.Add(""); report.Add("Not detected:"); foreach (var p in problems) report.Add(" " + p); }
File.WriteAllText(Path.Combine(Desktop, "guitar_capture.txt"), string.Join("\n", report.ToArray()) + "\n", Utf8);
Say("");
Say("Saved on your Desktop:");
Say(" guitar_map.ini - the map file (exactly the name the PS4 needs)");
Say(" guitar_capture.txt - the raw data (keep it, in case something needs fixing)");
Say("");
Say("The map that was made:");
foreach (var l in lines) Say(" " + l);
if (problems.Count > 0)
{
Say("");
Say("PROBLEMS - these were not detected, so the map is incomplete:");
foreach (var p in problems) Say(" - " + p);
Say("Choose option 2 again and hold each of those the WHOLE time it says HOLD NOW.");
Say("If it keeps failing, send guitar_capture.txt to the author (Discord @CoronaSend).");
return;
}
Say("");
Say("Map complete. Now it has to get onto the PS4:");
string yn = Ask("Send guitar_map.ini to the PS4 now? Type y or n and press Enter: ");
if (yn.ToLower().StartsWith("y"))
{
string ip = GetIp(null);
if (ip.Length > 0 && Connect(ip))
{
Ftp.Put(ip, "/data/GoldHEN/guitar_map.ini", File.ReadAllBytes(ini));
Say("Sent to /data/GoldHEN/guitar_map.ini on the PS4.");
Say("");
Say("What to do now:");
Say(" 1. Unplug the adapter from this PC and plug it into the PS4.");
Say(" 2. FULLY CLOSE Rock Band 4 (PS button > Close Application), then start it again.");
Say(" 3. Try the guitar in the menus.");
}
}
else
{
Say("OK. When you are ready, choose option 1 and it will send the map file for you.");
}
}
static int SelfTest()
{
var c = new Dictionary<string, byte[]>();
Func<string, byte[]> B = s => { var p = s.Split(' '); var r = new byte[p.Length]; for (int i = 0; i < p.Length; i++) r[i] = Convert.ToByte(p[i], 16); return r; };
c["idle"] = B("00 00 06 7F 7F 7F 00 00"); c["green"] = B("80 00 06 7F 7F 7F 00 00"); c["red"] = B("02 00 06 7F 7F 7F 00 00");
c["yellow"] = B("01 00 06 7F 7F 7F 00 00"); c["blue"] = B("04 00 06 7F 7F 7F 00 00"); c["orange"] = B("08 00 06 7F 7F 7F 00 00");
c["start"] = B("00 02 06 7F 7F 7F 00 00"); c["select"] = B("00 01 06 7F 7F 7F 00 00");
c["strum_up"] = B("00 00 07 7F 7F 7F 00 00"); c["strum_down"] = B("00 00 05 7F 7F 7F 00 00"); c["whammy"] = B("00 00 06 7F 00 7F 00 00");
var pr = new List<string>();
foreach (var l in Mapper.Build("2563", "0575", c, pr)) Say(l);
Say("problems: " + pr.Count);
bool ch; Say("--- ini: empty"); Say(UpdateIni("", new[] { Rb4dxLine, PluginLine }, out ch));
Say("--- ini: other plugins"); Say(UpdateIni("[settings]\r\nshow_load_notification=true\r\n[CUSA00419]\r\n/a.prx\r\n", new[] { PluginLine }, out ch));
Say("--- ini: section in middle"); Say(UpdateIni("[CUSA02084]\n" + Rb4dxLine + "\n[CUSA11100]\n/b.prx\n", new[] { Rb4dxLine, PluginLine }, out ch));
UpdateIni("[CUSA02084]\n" + Rb4dxLine + "\n" + PluginLine + "\n", new[] { Rb4dxLine, PluginLine }, out ch); Say("already complete -> changed: " + ch);
Say("devices: " + HidDev.Enumerate().Count);
Say("plugin bytes: " + PluginBytes().Length);
return 0;
}
static int Main(string[] args)
{
Console.Title = "Guitar Tool for Rock Band 4 on PS4";
try
{
if (args.Length > 0 && args[0] == "--selftest") return SelfTest();
if (args.Length > 1 && args[0] == "--install") { Install(args[1]); return 0; }
while (true)
{
Say("");
Say("==============================================");
Say(" GUITAR TOOL - Rock Band 4 on PS4");
Say("==============================================");
Say("");
Say(" What do you want to do?");
Say("");
Say(" Type 1 = Put the guitar plugin on my PS4");
Say(" (do this first, the PS4 must be on and jailbroken)");
Say("");
Say(" Type 2 = My guitar's buttons are wrong or do nothing");
Say(" (plug the guitar into this PC, I will learn its buttons)");
Say("");
Say(" Type 3 = Close this program");
Say("");
string c = Ask("Type 1, 2 or 3, then press the Enter key: ");
try
{
if (c == "1") Install(null);
else if (c == "2") MakeMap();
else if (c == "3") return 0;
}
catch (Exception ex) { Say(""); Say("ERROR: " + ex.Message); }
}
}
catch (Exception ex) { Say("ERROR: " + ex.Message); Ask("Press Enter to close."); return 1; }
}
}
+10
View File
@@ -0,0 +1,10 @@
$ErrorActionPreference = 'Stop'
# Builds GuitarTool.exe with the C# compiler that ships with Windows. The plugin is embedded in the exe.
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$root = Split-Path -Parent $here
$csc = "$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
$prx = Join-Path $root 'bin\OrbisInstrumentalizer.prx'
$out = Join-Path $root 'bin\GuitarTool.exe'
& $csc /nologo /target:exe /platform:anycpu /optimize+ "/out:$out" "/resource:$prx,plugin.prx" "$here\GuitarTool.cs"
if ($LASTEXITCODE -ne 0) { throw 'compile failed' }
"Built $out"