📜 ⬆️ ⬇️

We connect to the PSP gamepad from Xbox 360 using Raspberry Pi

... or a tale about how penguin America made friends with Japan.



So, in ancient times, people played Sony Playstation and they were all arranged. But progress did not stand still. Increasing the degree of integration of the chip. Engineering thought was constantly looking for new form factors, and marketing thought - new sales markets. So in 2005, the Sony Playstation Portable gaming system went on sale outside of Japan. Its game lineup (as well as at any other prefix) was itself specially compiled for PSP games. But she also had enough computing power to run games from the original PlayStation through the built-in emulator. Although, perhaps, here the key role was played by the fact that both the PlayStation and the PSP had a processor of the same architecture - namely, MIPS. But the most remarkable thing about this system is that immediately, in the year of launch, the libraries of the PSP SDK leaked to the network. As a result, after almost 10 years since launch, we have a huge library of games and debugged homebrew. Also, now, in a not the largest city in Russia, a fully working PSP (the most functional version) can be bought with hands for 3000 rubles. All this makes it a very attractive budget game system with just a huge installation base. The most functional modification has a component output for connecting to the TV. But the connector to connect to the TV is unsuccessful in terms of the long use of the PSP as a gamepad. In addition, with prolonged use, it is necessary to connect a second wire - from the charger. And the usability of such a chimera tends to zero. How relatively budgetary and at the same time geeky to solve this problem - it will be discussed in this article. Also in brief will be touched upon the themes of programming the USB client driver under the PSP, the method of installing hook functions in the PSP, working under Linux with USB devices and with joysticks via API. We begin.
')

The idea of ​​connecting a portable console to the TV is not new


But before we begin, I will tell about one interesting fact. Since the PSP was released almost 10 years ago, the next generation of portable consoles from Sony, namely the Sony Playstation Vita, is currently relevant. And the fact is that in Japan there was a stationary version of the portable console. Sony PlayStation Vita TV.
PS Vita TV


As a gamepad, it uses the usual Dualshock 3 from the Playstation 3. A USB or Bluetooth connection is supported. Vita TV, like Vita, can play games from Vita, PSP and from the original Playstation. Thus, the idea of ​​a “stationary portable” console is quite consistent and interesting.

How to make friends gamepad and PSP?


Then the question arose how to connect an external gamepad to the PSP. It would seem that the PSP has a USB connector, through which enthusiasts taught the PSP to launch games from the folder of a connected computer or transfer the entire picture of the game to the window of this USB-connected computer. But as it turned out , USB in the PSP can only be a client. And even official accessories (for example, a camera) work in the host mode (by the way, Google, when working with peripherals in android, recommends transferring the smartphone to also client mode). Those. Connect the gamepad to the PSP directly is useless. Therefore, you need some kind of intermediate device. In a local electronic store, debugging boards of varying degrees of steepness cost from 1 to 10 thousand rubles. While these are microcontrollers, you need to think about the USB host separately. Then came across the Raspberry Pi.
Raspberry pi


This machine has everything you need - 2 USB ports, as well as a full-fledged Linux. In not the largest city in Russia, the older model (with 512 MB of memory and Ethernet) costs 1,500 rubles through free classified ads. The price is commensurate with the cheapest controller debug boards, and the functionality is not an example anymore. And also “Made in the UK”.

Begin research


If you simply connect the USB cable to the PSP, it will become visible as a USB flash drive. We need it to accept commands about external management. Those. in the PSP, there must be some kind of code that will receive information via USB and simulate pressing the controls on the PSP itself. The very possibility of launching some code besides licensed games is possible only on pirated firmware. Technically, pirated firmware is a program that pretends to be an official program located on a memory card, and which when launched replaces the working firmware code with a modified one in RAM that allows you to run games from .iso files from the PSP memory card. Thus, the firmware works exactly until the next reboot of the PSP. But this is not what is important to us, but that it supports plugins. Plug-ins are object files, linked in a specific format, which start in separate streams in parallel with the launch of the main menu, PSP games or games of the original Playstation. The latest version of the original PSP firmware is as much as 6.60. Plug-ins sharpened for earlier firmware versions may not work under the latest firmware. It happened in this case. The plug-in , which can send via USB from the PSP to a PC running Windows video of everything that happens on the PSP and receive data from the gamepad connected to the PC to the PSP, via USB, on firmware 6.60 worked only half, i.e. The data from the gamepad to the PSP reached, but the imitation of the control of the PSP controls did not work. I started looking for plugins that somehow work with the control on firmware 6.60. And found. Another plugin is used to work with an analog PSP stick, and it works on the latest firmware. All sources for PSP are compiled by this homebrew SDK.

Modification of the source code of PSP plugins. Hooks


For the basis of the plugin project, which I will modify, I chose the one that already contained the working code of the USB client. But for the usual debugging and generally cozy atmosphere I needed printf (). On the PSP. In the selected plugin it was not. But in the plugin from which I wanted to pull out the working code for intercepting the events of the PSP controls, it was done by intercepting the function of drawing the next frame to the frame buffer and drawing to the frame the debugging lines I needed. The interception of the (hook) drawing function itself is implemented as follows:

#define GET_JUMP_TARGET_(x) (0x80000000 | (((x) & 0x03FFFFFF) << 2)) int (*g_setframebuf)(int unk, void* addr, int width, int psm, int sync); int setframebuf_hook_func(int unk, void* addr, int width, int psm, int sync) { if(g_info == 1) { dbgprint( debugmsg, addr, psm ); if (!g_info) DEBUG_RESET() } return g_setframebuf(unk, addr, width, psm, sync); } int hook_function(unsigned int* jump, void* hook, unsigned int* result) { unsigned int target; unsigned int func; int inst; target = GET_JUMP_TARGET_(*jump); while (((inst = _lw(target+4)) & ~0x03FFFFFF) != 0x0C000000) // search next JAL instruction target += 4; if((inst & ~0x03FFFFFF) != 0x0C000000) { printf("invalid!\n"); return 1; } *result = GET_JUMP_TARGET_(inst); func = (unsigned int) hook; func = (func & 0x0FFFFFFF) >> 2; _sw(0x0C000000 | func, target+4); return 0; } int module_start( SceSize args, void *argp ) { //... hook_function( (unsigned int*) sceDisplaySetFrameBuf, setframebuf_hook_func, (unsigned int*)&g_setframebuf ); //... } 

After calling hook_function (), the PSP operating system, when calling its internal kernel function, sceDisplaySetFrameBuf () will actually call setframebuf_hook_func (). And to call the original sceDisplaySetFrameBuf () you now need to call g_setframebuf (). Who is interested in the topic of hooks, in more detail you can read for example here .

Modification of the source code of PSP plugins. Control.


Then I added working hooks on the control functions sceCtrlReadBufferPositive (), sceCtrlPeekBufferPositive (), sceCtrlReadBufferNegative () and sceCtrlPeekBufferNegative () to the modified project, taking them from the same JoySens. Only made it so that the input data inside them was the latest data sent to the PSP about the state of the gamepad connected to the PC host. Here is the archive with all the necessary binaries and sources. Before starting the PC part of the program, you need to install USB drivers. First you need to run the plug-in on the PSP (how to run the plug-in, you can find out by Google ’s google). Then restart the PSP and connect it to the PC. The PSP Type B device should be detected. Next, download the drivers . Install the driver through the wizard (bin \ inf-wizard.exe), pointing out our PSP Type B device and saying at the end to install the driver.

Preparation of the minimum version of the PSP-part


Everything would be fine, but the network has source only for version 0.19 RemoteJoyLite. And it does not work correctly on some games (for example, K-On! Brakes wildly, and graphic artifacts appear in Dungeon Siege ). In version 0.20 this is, as they say, corrected, but the source code of this version is not publicly available. Therefore, it was decided to modify the protocol of the data transmitted via USB in order to transfer only minimal information about the state of the gamepad, as well as to minimize the size of the source code of the PSP part. All data transferred from the PSP to the PC was removed from the protocol, and only one structure was left, which was transferred from the PC to the PSP, with the result that the brakes and artifacts were lost:

 #define USBDATA_PATTERN 0x1234ABFE struct { unsigned int Pattern; unsigned int ButtonData; unsigned int AnalogX; unsigned int AnalogY; } PSPUsbData; 

Analog data from the stick in the PSP itself is presented as a single-byte unsigned integer for each axis (127 is the center), and 4 bytes in the protocol are allocated because of the desire not to even think about the problems of packing and alignment of structures, since in RemoteJoyLite itself, the data is packaged as follows:

 struct HostFsCmd { uint32_t magic; uint32_t command; uint32_t extralen; } __attribute__((packed)); 

And in order not to even think about the relevant problems (after all, RemoteJoyLite itself is compiled by MinGW GCC, and the next step is to create a project in Microsoft Visual Studio to work out the trimmed protocol, which __attribute __ ((packed)) doesn't know), I removed all alignments and packing of structures by bringing them to a 32-bit representation. As a result, this archive contains the sources and binaries of the trimmed project - both the PSP and Windows part. The Windows application is designed as a mfc C ++ project for Microsoft Visual Studio 2010. Windows programming students will find it helpful to see how the keystroke handling of the cursor keys is implemented (in mfc applications, it works only if the window messages of the entire application are in the filter, and not in the interactive keystroke handler), and also due to what printf () works correctly on the form, even from a separate stream. As part of the PSP, it will be interesting to study the accompanying lightweight source, which left only USB, control and printf (). For example, receiving data via USB is asynchronous and implemented as follows. When connected to a USB host, after a correct procedure for obtaining an internal address on the USB bus (see USB description ), UsbAttach () is called, since this call was described in the driver structure registered during initialization:

 #define RJLITE_DRIVERNAME "RJLiteDriver" #define RJLITE_DRIVERPID (0x1C9) struct UsbDriver UsbDriver = { RJLITE_DRIVERNAME, 4, UsbEndpoint, &UsbInterface, &UsbData[0].devdesc[0], &UsbData[0].config, &UsbData[1].devdesc[0], &UsbData[1].config, &StringDescriptor, UsbRequest, UsbUnknown, UsbAttach, UsbDetach, 0, UsbStartFunc, UsbStopFunc, NULL }; int module_start( SceSize args, void *argp ) { //... sceUsbbdRegister(&UsbDriver); if((sceUsbStart(PSP_USBBUS_DRIVERNAME, 0, 0) == 0) && (sceUsbStart(RJLITE_DRIVERNAME, 0, 0) == 0) && //... { //... } //... } 

Where module_start () is a function called in a separate thread when the plug-in is launched with pirated firmware. Also, when the driver is started, a flag variable of the int type is created (ie, 32 flags) with access via a unique object identifier in the PSP operating system and the launch of the auxiliary stream:

 static SceUID UsbMainEventFlag = -1; static int UsbStartFunc( int size, void *p ) { //... UsbMainEventFlag = sceKernelCreateEventFlag( "USBMainEvent", 0x200, 0, NULL ); //... UsbMainThreadID = sceKernelCreateThread( "USBMainThread", UsbMainThread, 10, 0x10000, 0, NULL ); //... sceKernelStartThread( UsbMainThreadID, 0, NULL ); //... } 

So, and called UsbAttach () sets the USB_EVENT_ATTACH flag in the flag variable of the UsbMainEventFlag object:

 static int UsbAttach(int speed, void *arg2, void *arg3) { sceKernelSetEventFlag( UsbMainEventFlag, USB_EVENT_ATTACH); return 0; } 

At the same time, the UsbMainThread () stream that was previously created when UsbStartFunc () was called was written:

 static int UsbMainThread(SceSize size, void *argp) { int ret; u32 result; while(1) { ret = sceKernelWaitEventFlag(UsbMainEventFlag, USB_EVENT_ATTACH | USB_EVENT_ASYNC, PSP_EVENT_WAITOR | PSP_EVENT_WAITCLEAR, &result, NULL); if(ret < 0) { sceKernelExitDeleteThread(0); } if(result&USB_EVENT_ASYNC) { usb_async_events++;//nyashkoshkko: debug SetUsbAyncReq(&PSPUsbData, sizeof(PSPUsbData)); } if(result&USB_EVENT_ATTACH) { usb_attach_events++;//nyashkoshkko: debug SetUsbAyncReq(&PSPUsbData, sizeof(PSPUsbData)); } } return 0; } 

And this means that the stream in an infinite loop waits for the setting of the USB_EVENT_ATTACH flag or the USB_EVENT_ASYNC flag in the flag variable of the UsbMainEventFlag object. Successful communication with the USB host caused the installation of the USB_EVENT_ATTACH flag, by which this thread performs an asynchronous request to receive a data packet via USB, while resetting the USB_EVENT_ASYNC flag:

 static int SetUsbAyncReq( void *data, int size ) { //... UsbAsyncReq.data = data; UsbAsyncReq.size = size; UsbAsyncReq.func = UsbAsyncReqDone; sceKernelClearEventFlag( UsbMainEventFlag, ~USB_EVENT_ASYNC ); return( sceUsbbdReqRecv( &UsbAsyncReq ) ); } 

In this callback request, the call to the UsbAsyncReqDone () function is set:

 static int UsbAsyncReqDone( struct UsbdDeviceReq *req, int arg2, int arg3 ) { sceKernelSetEventFlag( UsbMainEventFlag, USB_EVENT_ASYNC ); return( 0 ); } 

This function, as we see, upon completion of receiving a data packet from a USB host (processed by the core of the PSP operating system by interrupting the PSP USB controller) sets the USB_EVENT_ASYNC flag in the UsbMainEventFlag flag variable of the object. According to it, our infinite loop sets a new asynchronous data request. Such an event mechanism allows not to waste processor time wasted on endless polling of the data readiness flag, because when calling sceKernelWaitEventFlag (), time streams are not allocated to a stream until the necessary event occurs - this is provided by the flow scheduler inside the PSP operating system, and indeed The basic principle works in any multitasking operating system.

Writing a service for working with USB for Linux under the Raspberry Pi


So, the PSP part is complete. Now is the time to develop an application, or rather a service that will be launched automatically when you turn on Raspberry Pi under Linux. Generally, for Raspberry Pi there are several adapted Linux distributions. But I stopped at Fedora, because He draws his roots from Red Hat, with whom I tritely dealt with work and got used to his RPM package distribution. Immediately after installing Fedora Remix 18 and, if necessary, setting up the network (in my case, it was necessary to manually register the network address and gateway, because the DHCP server in the home network does not work correctly), which is done intuitively - by connecting the mouse and clicking on the network connection icon in the upper right corner, an SSH server is working out of the box. But the SMB server could not be quickly configured (problems with smbpasswd), so the source was created and edited remotely via SSH via midnight commander. The first thing I started with was connecting to a PSP. To do this, it was necessary to learn how to interact with Linux in USB. In this regard, there was a small unpleasant story, because of which, once again, the whole charm of Linux is crumbling before my eyes. The fact is that when you try to install the library and header to compile through

 > yum install libusb1-devel 

the package manager scolded and said that it was outdated, use libusbx. Ok team

 > yum install libusbx-devel 

downloaded the necessary files. But the fact is that libusbx for api-calls is incompatible with libusb1, which is the same as the windows version of libusb, which, in turn, was used in the original RemoteJoyLite source code, and generally works fine under Windows. But alright. With usb figured out, now let's move on to accessing the gamepad from under Linux. I have a wired gamepad from the Xbox 360, which feels great in Windows, and, surprisingly, made out of the box in Fedora Remix 18 on Raspberry Pi, creating the device / dev / input / js0 . It worked out the regular xpad driver. There is an alternative xboxdrv driver - it is more flexible in configuration. But we have enough and full-time.
By the way, the android things are exactly the same.
The xpad driver is included in the Linux kernel for Android:

 #define DRIVER_DESC "X-Box pad driver" 

And in the same way , the device / dev / input / js0 is created:

 MODULE_SUPPORTED_DEVICE("input/js"); 

For example, consider how to get a list of input devices in the android. The recommended api tells us to call getDeviceIds () for this, which says :

  /** * Gets the ids of all input devices in the system. * @return The input device ids. */ public static int[] getDeviceIds() { return InputManager.getInstance().getInputDeviceIds(); } 

getInputDeviceIds () :

  private final IInputManager mIm; //... private SparseArray<InputDevice> mInputDevices; //... /** * Gets the ids of all input devices in the system. * @return The input device ids. */ public int[] getInputDeviceIds() { synchronized (mInputDevicesLock) { populateInputDevicesLocked(); final int count = mInputDevices.size(); final int[] ids = new int[count]; for (int i = 0; i < count; i++) { ids[i] = mInputDevices.keyAt(i); } return ids; } } //... private void populateInputDevicesLocked() { if (mInputDevicesChangedListener == null) { final InputDevicesChangedListener listener = new InputDevicesChangedListener(); try { mIm.registerInputDevicesChangedListener(listener); } catch (RemoteException ex) { throw new RuntimeException( "Could not get register input device changed listener", ex); } mInputDevicesChangedListener = listener; } if (mInputDevices == null) { final int[] ids; try { ids = mIm.getInputDeviceIds(); } catch (RemoteException ex) { throw new RuntimeException("Could not get input device ids.", ex); } mInputDevices = new SparseArray<InputDevice>(); for (int i = 0; i < ids.length; i++) { mInputDevices.put(ids[i], null); } } } 

mIm.getInputDeviceIds () :

 interface IInputManager { // Gets input device information. InputDevice getInputDevice(int deviceId); int[] getInputDeviceIds(); //... 

Here the InputManager service comes into play :

 import android.view.InputDevice; //... /* * Wraps the C++ InputManager and provides its callbacks. */ public class InputManagerService extends IInputManager.Stub implements Watchdog.Monitor, DisplayManagerService.InputManagerFuncs { static final String TAG = "InputManager"; static final boolean DEBUG = false; private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; //... private InputDevice[] mInputDevices = new InputDevice[0]; //... /** * Gets the ids of all input devices in the system. * @return The input device ids. */ @Override // Binder call public int[] getInputDeviceIds() { synchronized (mInputDevicesLock) { final int count = mInputDevices.length; int[] ids = new int[count]; for (int i = 0; i < count; i++) { ids[i] = mInputDevices[i].getId(); } return ids; } } 

getId () leads us to where we were (android is amazing):

 public final class InputDevice implements Parcelable { private final int mId; //... /** * Gets the input device id. * <p> * Each input device receives a unique id when it is first configured * by the system. The input device id may change when the system is restarted or if the * input device is disconnected, reconnected or reconfigured at any time. * If you require a stable identifier for a device that persists across * boots and reconfigurations, use {@link #getDescriptor()}. * </p> * * @return The input device id. */ public int getId() { return mId; } //... // Called by native code. private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, int productId, String descriptor, boolean isExternal, int sources, int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasButtonUnderPad) { mId = id; mGeneration = generation; mControllerNumber = controllerNumber; mName = name; mVendorId = vendorId; mProductId = productId; mDescriptor = descriptor; mIsExternal = isExternal; mSources = sources; mKeyboardType = keyboardType; mKeyCharacterMap = keyCharacterMap; mHasVibrator = hasVibrator; mHasButtonUnderPad = hasButtonUnderPad; } private InputDevice(Parcel in) { mId = in.readInt(); mGeneration = in.readInt(); mControllerNumber = in.readInt(); mName = in.readString(); mVendorId = in.readInt(); mProductId = in.readInt(); mDescriptor = in.readString(); mIsExternal = in.readInt() != 0; mSources = in.readInt(); mKeyboardType = in.readInt(); mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in); mHasVibrator = in.readInt() != 0; mHasButtonUnderPad = in.readInt() != 0; for (;;) { int axis = in.readInt(); if (axis < 0) { break; } addMotionRange(axis, in.readInt(), in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat()); } } 

In the native part of the android, an instance of InputDevice () is created here (thanks to the prompt response from the toaster ):

 jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& deviceInfo) { //... ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(), deviceInfo.getControllerNumber(), nameObj.get(), static_cast<int32_t>(ident.vendor), static_cast<int32_t>(ident.product), descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.hasVibrator(), deviceInfo.hasButtonUnderPad())); 

This function is called here :

 void NativeInputManager::notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) { JNIEnv* env = jniEnv(); //... jobject inputDeviceObj = android_view_InputDevice_create(env, inputDevices.itemAt(i)); 

The call to the notifyInputDevicesChanged () function is defined by the callback again in the already familiar InputManager service:

  // Native callback. private void notifyInputDevicesChanged(InputDevice[] inputDevices) { synchronized (mInputDevicesLock) { if (!mInputDevicesChangedPending) { mInputDevicesChangedPending = true; mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED, mInputDevices).sendToTarget(); } mInputDevices = inputDevices; } } 

The call of the callback itself is initiated in the InputReader :

 void InputReader::loopOnce() { //... size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //... // Send out a message that the describes the changed input devices. if (inputDevicesChanged) { mPolicy->notifyInputDevicesChanged(inputDevices); } 

Also here we see that events from the input device are received by the EventHubInterface class:

  sp<EventHubInterface> mEventHub; 

And, as a result, in the implementation of this class EventHub.cpp there is a discovery and operation with the device from / dev / input, as in the usual Linux Fedora in Raspberry Pi:

  static const char *DEVICE_PATH = "/dev/input"; //... char devname[PATH_MAX]; char *filename; //... strcpy(devname, DEVICE_PATH); filename = devname + strlen(devname); *filename++ = '/'; //... strcpy(filename, event->name); //... openDeviceLocked(devname); //... status_t EventHub::openDeviceLocked(const char *devicePath) { char buffer[80]; ALOGV("Opening device: %s", devicePath); int fd = open(devicePath, O_RDWR | O_CLOEXEC); if(fd < 0) { ALOGE("could not open %s, %s\n", devicePath, strerror(errno)); return -1; } 

In general, about this entire input system in android is briefly explained in the source code itself here :

 /* * The input manager is the core of the system event processing. * * The input manager uses two threads. * * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events, * applies policy, and posts messages to a queue managed by the DispatcherThread. * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the * queue and asynchronously dispatches them to applications. * * By design, the InputReaderThread class and InputDispatcherThread class do not share any * internal state. Moreover, all communication is done one way from the InputReaderThread * into the InputDispatcherThread and never the reverse. Both classes may interact with the * InputDispatchPolicy, however. * * The InputManager class never makes any calls into Java itself. Instead, the * InputDispatchPolicy is responsible for performing all external interactions with the * system, including calling DVM services. */ class InputManagerInterface : public virtual RefBase { 

So, the final source code of the service is as follows:
 #include <stdio.h> #include <stdlib.h> #include <termios.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <libusb-1.0/libusb.h> #define SONY_VENDOR_ID 0x054C #define PSP_B_PRODUCT_ID 0x01C9 #define UP 0x00000010 #define DOWN 0x00000040 #define LEFT 0x00000080 #define RIGHT 0x00000020 #define B_X 0x00004000 #define B_O 0x00002000 #define B_KVADRAT 0x00008000 #define B_TREUGOLNIK 0x00001000 #define B_L 0x00000100 #define B_R 0x00000200 #define B_SELECT 0x00000001 #define B_START 0x00000008 #define B_NOTE 0x00800000 struct { unsigned int Pattern; unsigned int Btn; unsigned int X; unsigned int Y; } PS = {0x1234ABFE, 0, 127, 127}; struct js_event { unsigned int time; short value; unsigned char type; unsigned char number; }; int is_usbdevblock(libusb_device *dev) { struct libusb_device_descriptor desc; int r = libusb_get_device_descriptor(dev, &desc); if((desc.idVendor == SONY_VENDOR_ID) && (desc.idProduct == PSP_B_PRODUCT_ID)) { return 1; } return 0; } int main(int argc, char** argv) { unsigned int real_x = 0, real_y = 0; int x, y; int fd = 0; while(1) { libusb_device **list; libusb_device *found = NULL; libusb_context *ctx = NULL; int attached = 0; libusb_init(&ctx); libusb_set_debug(ctx, 3); ssize_t cnt = libusb_get_device_list(ctx, &list); ssize_t i = 0; int err = 0; if(cnt < 0) { return -1; } for(i = 0; i < cnt; i++) { libusb_device *device = list[i]; if(is_usbdevblock(device)) { found = device; break; } } if(found) { libusb_device_handle *handle; err = libusb_open(found, &handle); if (err) { return -1; } if (libusb_kernel_driver_active(handle, 0)) { libusb_detach_kernel_driver(handle, 0); attached = 1; } err = libusb_claim_interface(handle, 0); if (err) { return -1; } if(fd == 0) { fd = open("/dev/input/js0", O_RDONLY); } if(fd < 0) { goto clean; } int nEndpoint = 0x01; int nTimeout = 500; //in milliseconds int BytesWritten = 0; int ret; struct js_event e; int t; while(1) { read(fd, &e, sizeof(struct js_event)); e.type &= ~0x80; t = 0; //transfer = 0; if(e.type == 1) { if(e.value == 1) { if(e.number == 0) {PS.Btn |= B_X; t = 1;} if(e.number == 1) {PS.Btn |= B_O; t = 1;} if(e.number == 2) {PS.Btn |= B_KVADRAT; t = 1;} if(e.number == 3) {PS.Btn |= B_TREUGOLNIK; t = 1;} if(e.number == 4) {PS.Btn |= B_L; t = 1;} if(e.number == 5) {PS.Btn |= B_R; t = 1;} if(e.number == 6) {PS.Btn |= B_SELECT; t = 1;} if(e.number == 7) {PS.Btn |= B_START; t = 1;} if(e.number == 8) {PS.Btn |= B_NOTE; t = 1;}//XBOX_HOME //if(e.number == 9) PS.Btn |= ;//L_STICK_PRESS //if(e.number == 10)PS.Btn |= ;//R_STICK_PRESS } if(e.value == 0) { if(e.number == 0) {PS.Btn &= ~B_X; t = 1;} if(e.number == 1) {PS.Btn &= ~B_O; t = 1;} if(e.number == 2) {PS.Btn &= ~B_KVADRAT; t = 1;} if(e.number == 3) {PS.Btn &= ~B_TREUGOLNIK; t = 1;} if(e.number == 4) {PS.Btn &= ~B_L; t = 1;} if(e.number == 5) {PS.Btn &= ~B_R; t = 1;} if(e.number == 6) {PS.Btn &= ~B_SELECT; t = 1;} if(e.number == 7) {PS.Btn &= ~B_START; t = 1;} if(e.number == 8) {PS.Btn &= ~B_NOTE; t = 1;} } } if(e.type == 2) { if(e.number == 6) { if(e.value == -32767) {PS.Btn |= LEFT; t = 1;} if(e.value == 32767) {PS.Btn |= RIGHT; t = 1;} if(e.value == 0) {PS.Btn &= ~(LEFT | RIGHT); t = 1;} } if(e.number == 7) { if(e.value == -32767) {PS.Btn |= UP; t = 1;} if(e.value == 32767) {PS.Btn |= DOWN; t = 1;} if(e.value == 0) {PS.Btn &= ~(UP | DOWN); t = 1;} } if(e.number == 0) { if(real_x != ((e.value + 32767) / 256)) {real_x = ((e.value + 32767) / 256); t = 1;} } if(e.number == 1) { if(real_y != ((e.value + 32767) / 256)) {real_y = ((e.value + 32767) / 256); t = 1;} } } if(t == 1) { #define KOEF 1.4 //[-128..0..127] x = real_x - 128; y = real_y - 128; x = x * (1. + ((abs(x) * (KOEF-1.))/(127./KOEF))); if(x > 127) x = 127; if(x < -128) x = -128; y = y * (1. + ((abs(y) * (KOEF-1.))/(127./KOEF))); if(y > 127) y = 127; if(y < -128) y = -128; PS.X = 128 + x; PS.Y = 128 + y; ret = libusb_bulk_transfer(handle, nEndpoint, (unsigned char *)&PS, sizeof(PS), &BytesWritten, nTimeout); if(ret < 0) { break; } } } clean: if(fd) { close(fd); fd = 0; } if(attached == 1) { libusb_attach_kernel_driver(handle, 0); } libusb_close(handle); } libusb_free_device_list(list, 1); libusb_exit(ctx); sleep(1); } return 0; } 

Here is the link to the binary service file. And here is the contents of the m.sh compilation script:

 gcc xbox2psp.c -o xbox2psp.o -I/usr/local -L/usr/local -lusb-1.0 

By the very source, I would like to note two points. Firstly, Xbox analog sticks have an accuracy of 16 bits, while the PSP stick has an accuracy of 8 bits. In this regard, I send the packet by changing the value of the axes reduced to 8 bits, and not by changing the source data from the Xbox controller. Secondly, in PSP, the value of the diagonals corresponds to the full scale (i.e., the round stick of the PSP in terms of scale is a square), and in the Xbox, as expected, half the scale:


Therefore, a linearly increasing (the greater the deviation from the center of the axis of the Xbox controller, the greater) is introduced with a maximum of 1.4 (although, as it turned out later, it would be correct to determine the angle value, and the closer the angle is to the diagonal, the larger the coefficient will be). With these values, the Xbox gamepad feels without any discomfort, although, technically, the sensitivity has turned out to be rough. In Doom 0.05, it is convenient to control, in Dungeon Siege, all three movement speeds (depending on the stick deflection force) work and feel like on the PSP itself. Because, when confronted with a problem, at the beginning a simple coefficient was tested (both 1.5 and 1.4), without a linear increase depending on the deviation, and there was a sharp discomfort in these games - it was impossible to play.

Adding your own service to autorun in Fedora Remix 18 for Raspberry Pi


Surface googling on adding a program to autoload in Linux mainly gives recommendations for modifying the init rc script. But in our case, you need to do differently.

1. First you need to copy our xbox2psp.o service to / usr / local / bin and set it to run (all three bits).

2. Then create the file /lib/systemd/system/xbox2psp.service as follows:
 [Unit] Description=xbox2psp After=syslog.target network.target [Service] Type=simple ExecStart=/usr/local/bin/xbox2psp.o [Install] WantedBy=multi-user.target 

3. Go to the / etc / systemd / system / folder and create a link with the command
 > ln -s /lib/systemd/system/xbox2psp.service xbox2psp.service 

4. Reload the startup daemon configuration:
 > systemctl daemon-reload 

5. Activate autostart of new service
 > systemctl enable xbox2psp.service 

6. If necessary, you can immediately start the service with the command
 > systemctl start xbox2psp.service 

As a result, we got a convenient opportunity to control the PSP using the Xbox 360 gamepad. If desired, this project can be modified to connect, for example, Dualshock 3 via Bluetooth.

Source: https://habr.com/ru/post/213513/


All Articles