xref: /reactos/dll/directx/wine/dinput/joystick.c (revision 58aee30e)
1 /*  DirectInput Generic Joystick device
2  *
3  * Copyright 1998 Marcus Meissner
4  * Copyright 1998,1999 Lionel Ulmer
5  * Copyright 2000-2001 TransGaming Technologies Inc.
6  * Copyright 2009 Aric Stewart, CodeWeavers
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 /*
24  * To Do:
25  *	dead zone
26  *	force feedback
27  */
28 
29 #include <stdio.h>
30 
31 #include "joystick_private.h"
32 #include "wine/debug.h"
33 #include "winreg.h"
34 
35 WINE_DEFAULT_DEBUG_CHANNEL(dinput);
36 
37 static inline JoystickGenericImpl *impl_from_IDirectInputDevice8A(IDirectInputDevice8A *iface)
38 {
39     return CONTAINING_RECORD(CONTAINING_RECORD(iface, IDirectInputDeviceImpl, IDirectInputDevice8A_iface), JoystickGenericImpl, base);
40 }
41 static inline JoystickGenericImpl *impl_from_IDirectInputDevice8W(IDirectInputDevice8W *iface)
42 {
43     return CONTAINING_RECORD(CONTAINING_RECORD(iface, IDirectInputDeviceImpl, IDirectInputDevice8W_iface), JoystickGenericImpl, base);
44 }
45 static inline IDirectInputDevice8A *IDirectInputDevice8A_from_impl(JoystickGenericImpl *This)
46 {
47     return &This->base.IDirectInputDevice8A_iface;
48 }
49 static inline IDirectInputDevice8W *IDirectInputDevice8W_from_impl(JoystickGenericImpl *This)
50 {
51     return &This->base.IDirectInputDevice8W_iface;
52 }
53 
54 DWORD typeFromGUID(REFGUID guid)
55 {
56     if (IsEqualGUID(guid, &GUID_ConstantForce)) {
57         return DIEFT_CONSTANTFORCE;
58     } else if (IsEqualGUID(guid, &GUID_Square)
59             || IsEqualGUID(guid, &GUID_Sine)
60             || IsEqualGUID(guid, &GUID_Triangle)
61             || IsEqualGUID(guid, &GUID_SawtoothUp)
62             || IsEqualGUID(guid, &GUID_SawtoothDown)) {
63         return DIEFT_PERIODIC;
64     } else if (IsEqualGUID(guid, &GUID_RampForce)) {
65         return DIEFT_RAMPFORCE;
66     } else if (IsEqualGUID(guid, &GUID_Spring)
67             || IsEqualGUID(guid, &GUID_Damper)
68             || IsEqualGUID(guid, &GUID_Inertia)
69             || IsEqualGUID(guid, &GUID_Friction)) {
70         return DIEFT_CONDITION;
71     } else if (IsEqualGUID(guid, &GUID_CustomForce)) {
72         return DIEFT_CUSTOMFORCE;
73     } else {
74         WARN("GUID (%s) is not a known force type\n", _dump_dinput_GUID(guid));
75         return 0;
76     }
77 }
78 
79 static void _dump_DIEFFECT_flags(DWORD dwFlags)
80 {
81     if (TRACE_ON(dinput)) {
82         unsigned int   i;
83         static const struct {
84             DWORD       mask;
85             const char  *name;
86         } flags[] = {
87 #define FE(x) { x, #x}
88             FE(DIEFF_CARTESIAN),
89             FE(DIEFF_OBJECTIDS),
90             FE(DIEFF_OBJECTOFFSETS),
91             FE(DIEFF_POLAR),
92             FE(DIEFF_SPHERICAL)
93 #undef FE
94         };
95         for (i = 0; i < ARRAY_SIZE(flags); i++)
96             if (flags[i].mask & dwFlags)
97                 TRACE("%s ", flags[i].name);
98         TRACE("\n");
99     }
100 }
101 
102 static void _dump_DIENVELOPE(LPCDIENVELOPE env)
103 {
104     if (env->dwSize != sizeof(DIENVELOPE)) {
105         WARN("Non-standard DIENVELOPE structure size %d.\n", env->dwSize);
106     }
107     TRACE("Envelope has attack (level: %d time: %d), fade (level: %d time: %d)\n",
108           env->dwAttackLevel, env->dwAttackTime, env->dwFadeLevel, env->dwFadeTime);
109 }
110 
111 static void _dump_DICONSTANTFORCE(LPCDICONSTANTFORCE frc)
112 {
113     TRACE("Constant force has magnitude %d\n", frc->lMagnitude);
114 }
115 
116 static void _dump_DIPERIODIC(LPCDIPERIODIC frc)
117 {
118     TRACE("Periodic force has magnitude %d, offset %d, phase %d, period %d\n",
119           frc->dwMagnitude, frc->lOffset, frc->dwPhase, frc->dwPeriod);
120 }
121 
122 static void _dump_DIRAMPFORCE(LPCDIRAMPFORCE frc)
123 {
124     TRACE("Ramp force has start %d, end %d\n",
125           frc->lStart, frc->lEnd);
126 }
127 
128 static void _dump_DICONDITION(LPCDICONDITION frc)
129 {
130     TRACE("Condition has offset %d, pos/neg coefficients %d and %d, pos/neg saturations %d and %d, deadband %d\n",
131           frc->lOffset, frc->lPositiveCoefficient, frc->lNegativeCoefficient,
132           frc->dwPositiveSaturation, frc->dwNegativeSaturation, frc->lDeadBand);
133 }
134 
135 static void _dump_DICUSTOMFORCE(LPCDICUSTOMFORCE frc)
136 {
137     unsigned int i;
138     TRACE("Custom force uses %d channels, sample period %d.  Has %d samples at %p.\n",
139           frc->cChannels, frc->dwSamplePeriod, frc->cSamples, frc->rglForceData);
140     if (frc->cSamples % frc->cChannels != 0)
141         WARN("Custom force has a non-integral samples-per-channel count!\n");
142     if (TRACE_ON(dinput)) {
143         TRACE("Custom force data (time aligned, axes in order):\n");
144         for (i = 1; i <= frc->cSamples; ++i) {
145             TRACE("%d ", frc->rglForceData[i]);
146             if (i % frc->cChannels == 0)
147                 TRACE("\n");
148         }
149     }
150 }
151 
152 void dump_DIEFFECT(LPCDIEFFECT eff, REFGUID guid, DWORD dwFlags)
153 {
154     DWORD type = typeFromGUID(guid);
155     unsigned int i;
156 
157     TRACE("Dumping DIEFFECT structure:\n");
158     TRACE("  - dwSize: %d\n", eff->dwSize);
159     if ((eff->dwSize != sizeof(DIEFFECT)) && (eff->dwSize != sizeof(DIEFFECT_DX5))) {
160         WARN("Non-standard DIEFFECT structure size %d\n", eff->dwSize);
161     }
162     TRACE("  - dwFlags: %d\n", eff->dwFlags);
163     TRACE("    ");
164     _dump_DIEFFECT_flags(eff->dwFlags);
165     TRACE("  - dwDuration: %d\n", eff->dwDuration);
166     TRACE("  - dwGain: %d\n", eff->dwGain);
167 
168     if (eff->dwGain > 10000)
169         WARN("dwGain is out of range (>10,000)\n");
170 
171     TRACE("  - dwTriggerButton: %d\n", eff->dwTriggerButton);
172     TRACE("  - dwTriggerRepeatInterval: %d\n", eff->dwTriggerRepeatInterval);
173     TRACE("  - rglDirection: %p\n", eff->rglDirection);
174     TRACE("  - cbTypeSpecificParams: %d\n", eff->cbTypeSpecificParams);
175     TRACE("  - lpvTypeSpecificParams: %p\n", eff->lpvTypeSpecificParams);
176 
177     /* Only trace some members if dwFlags indicates they have data */
178     if (dwFlags & DIEP_AXES) {
179         TRACE("  - cAxes: %d\n", eff->cAxes);
180         TRACE("  - rgdwAxes: %p\n", eff->rgdwAxes);
181 
182         if (TRACE_ON(dinput) && eff->rgdwAxes) {
183             TRACE("    ");
184             for (i = 0; i < eff->cAxes; ++i)
185                 TRACE("%d ", eff->rgdwAxes[i]);
186             TRACE("\n");
187         }
188     }
189 
190     if (dwFlags & DIEP_ENVELOPE) {
191         TRACE("  - lpEnvelope: %p\n", eff->lpEnvelope);
192         if (eff->lpEnvelope != NULL)
193             _dump_DIENVELOPE(eff->lpEnvelope);
194     }
195 
196     if (eff->dwSize > sizeof(DIEFFECT_DX5))
197         TRACE("  - dwStartDelay: %d\n", eff->dwStartDelay);
198 
199     if (type == DIEFT_CONSTANTFORCE) {
200         if (eff->cbTypeSpecificParams != sizeof(DICONSTANTFORCE)) {
201             WARN("Effect claims to be a constant force but the type-specific params are the wrong size!\n");
202         } else {
203             _dump_DICONSTANTFORCE(eff->lpvTypeSpecificParams);
204         }
205     } else if (type == DIEFT_PERIODIC) {
206         if (eff->cbTypeSpecificParams != sizeof(DIPERIODIC)) {
207             WARN("Effect claims to be a periodic force but the type-specific params are the wrong size!\n");
208         } else {
209             _dump_DIPERIODIC(eff->lpvTypeSpecificParams);
210         }
211     } else if (type == DIEFT_RAMPFORCE) {
212         if (eff->cbTypeSpecificParams != sizeof(DIRAMPFORCE)) {
213             WARN("Effect claims to be a ramp force but the type-specific params are the wrong size!\n");
214         } else {
215             _dump_DIRAMPFORCE(eff->lpvTypeSpecificParams);
216         }
217     } else if (type == DIEFT_CONDITION) {
218         if (eff->cbTypeSpecificParams == sizeof(DICONDITION)) {
219             _dump_DICONDITION(eff->lpvTypeSpecificParams);
220         } else if (eff->cbTypeSpecificParams == 2 * sizeof(DICONDITION)) {
221             DICONDITION *condition = eff->lpvTypeSpecificParams;
222             _dump_DICONDITION(&condition[0]);
223             _dump_DICONDITION(&condition[1]);
224         } else {
225             WARN("Effect claims to be a condition but the type-specific params are the wrong size!\n");
226         }
227     } else if (type == DIEFT_CUSTOMFORCE) {
228         if (eff->cbTypeSpecificParams != sizeof(DICUSTOMFORCE)) {
229             WARN("Effect claims to be a custom force but the type-specific params are the wrong size!\n");
230         } else {
231             _dump_DICUSTOMFORCE(eff->lpvTypeSpecificParams);
232         }
233     }
234 }
235 
236 BOOL device_disabled_registry(const char* name)
237 {
238     static const char disabled_str[] = "disabled";
239     static const char joystick_key[] = "Joysticks";
240     char buffer[MAX_PATH];
241     HKEY hkey, appkey, temp;
242     BOOL do_disable = FALSE;
243 
244     get_app_key(&hkey, &appkey);
245 
246     /* Joystick settings are in the 'joysticks' subkey */
247     if (appkey)
248     {
249         if (RegOpenKeyA(appkey, joystick_key, &temp)) temp = 0;
250         RegCloseKey(appkey);
251         appkey = temp;
252     }
253     if (hkey)
254     {
255         if (RegOpenKeyA(hkey, joystick_key, &temp)) temp = 0;
256         RegCloseKey(hkey);
257         hkey = temp;
258     }
259 
260     /* Look for the "controllername"="disabled" key */
261     if (!get_config_key(hkey, appkey, name, buffer, sizeof(buffer)))
262         if (!strcmp(disabled_str, buffer))
263         {
264             TRACE("Disabling joystick '%s' based on registry key.\n", name);
265             do_disable = TRUE;
266         }
267 
268     if (appkey) RegCloseKey(appkey);
269     if (hkey)   RegCloseKey(hkey);
270 
271     return do_disable;
272 }
273 
274 /******************************************************************************
275   *     SetProperty : change input device properties
276   */
277 HRESULT WINAPI JoystickWGenericImpl_SetProperty(LPDIRECTINPUTDEVICE8W iface, REFGUID rguid, LPCDIPROPHEADER ph)
278 {
279     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
280     DWORD i;
281     ObjProps remap_props;
282 
283     TRACE("(%p,%s,%p)\n",This,debugstr_guid(rguid),ph);
284 
285     if (ph == NULL) {
286         WARN("invalid parameter: ph == NULL\n");
287         return DIERR_INVALIDPARAM;
288     }
289 
290     if (TRACE_ON(dinput))
291         _dump_DIPROPHEADER(ph);
292 
293     if (IS_DIPROP(rguid)) {
294         switch (LOWORD(rguid)) {
295         case (DWORD_PTR)DIPROP_RANGE: {
296             LPCDIPROPRANGE pr = (LPCDIPROPRANGE)ph;
297             if (ph->dwHow == DIPH_DEVICE) {
298 
299                 /* Many games poll the joystick immediately after setting the range
300                  * for calibration purposes, so the old values need to be remapped
301                  * to the new range before it does so */
302 
303                 TRACE("proprange(%d,%d) all\n", pr->lMin, pr->lMax);
304                 for (i = 0; i < This->base.data_format.wine_df->dwNumObjs; i++) {
305 
306                     remap_props.lDevMin = This->props[i].lMin;
307                     remap_props.lDevMax = This->props[i].lMax;
308 
309                     remap_props.lDeadZone = This->props[i].lDeadZone;
310                     remap_props.lSaturation = This->props[i].lSaturation;
311 
312                     remap_props.lMin = pr->lMin;
313                     remap_props.lMax = pr->lMax;
314 
315                     switch (This->base.data_format.wine_df->rgodf[i].dwOfs) {
316                     case DIJOFS_X        : This->js.lX  = joystick_map_axis(&remap_props, This->js.lX); break;
317                     case DIJOFS_Y        : This->js.lY  = joystick_map_axis(&remap_props, This->js.lY); break;
318                     case DIJOFS_Z        : This->js.lZ  = joystick_map_axis(&remap_props, This->js.lZ); break;
319                     case DIJOFS_RX       : This->js.lRx = joystick_map_axis(&remap_props, This->js.lRx); break;
320                     case DIJOFS_RY       : This->js.lRy = joystick_map_axis(&remap_props, This->js.lRy); break;
321                     case DIJOFS_RZ       : This->js.lRz = joystick_map_axis(&remap_props, This->js.lRz); break;
322                     case DIJOFS_SLIDER(0): This->js.rglSlider[0] = joystick_map_axis(&remap_props, This->js.rglSlider[0]); break;
323                     case DIJOFS_SLIDER(1): This->js.rglSlider[1] = joystick_map_axis(&remap_props, This->js.rglSlider[1]); break;
324 	            default: break;
325                     }
326 
327                     This->props[i].lMin = pr->lMin;
328                     This->props[i].lMax = pr->lMax;
329                 }
330             } else {
331                 int obj = find_property(&This->base.data_format, ph);
332 
333                 TRACE("proprange(%d,%d) obj=%d\n", pr->lMin, pr->lMax, obj);
334                 if (obj >= 0) {
335 
336                     remap_props.lDevMin = This->props[obj].lMin;
337                     remap_props.lDevMax = This->props[obj].lMax;
338 
339                     remap_props.lDeadZone = This->props[obj].lDeadZone;
340                     remap_props.lSaturation = This->props[obj].lSaturation;
341 
342                     remap_props.lMin = pr->lMin;
343                     remap_props.lMax = pr->lMax;
344 
345                     switch (This->base.data_format.wine_df->rgodf[obj].dwOfs) {
346                     case DIJOFS_X        : This->js.lX  = joystick_map_axis(&remap_props, This->js.lX); break;
347                     case DIJOFS_Y        : This->js.lY  = joystick_map_axis(&remap_props, This->js.lY); break;
348                     case DIJOFS_Z        : This->js.lZ  = joystick_map_axis(&remap_props, This->js.lZ); break;
349                     case DIJOFS_RX       : This->js.lRx = joystick_map_axis(&remap_props, This->js.lRx); break;
350                     case DIJOFS_RY       : This->js.lRy = joystick_map_axis(&remap_props, This->js.lRy); break;
351                     case DIJOFS_RZ       : This->js.lRz = joystick_map_axis(&remap_props, This->js.lRz); break;
352                     case DIJOFS_SLIDER(0): This->js.rglSlider[0] = joystick_map_axis(&remap_props, This->js.rglSlider[0]); break;
353                     case DIJOFS_SLIDER(1): This->js.rglSlider[1] = joystick_map_axis(&remap_props, This->js.rglSlider[1]); break;
354 		    default: break;
355                     }
356 
357                     This->props[obj].lMin = pr->lMin;
358                     This->props[obj].lMax = pr->lMax;
359                     return DI_OK;
360                 }
361             }
362             break;
363         }
364         case (DWORD_PTR)DIPROP_DEADZONE: {
365             LPCDIPROPDWORD pd = (LPCDIPROPDWORD)ph;
366             if (ph->dwHow == DIPH_DEVICE) {
367                 TRACE("deadzone(%d) all\n", pd->dwData);
368                 for (i = 0; i < This->base.data_format.wine_df->dwNumObjs; i++)
369                     This->props[i].lDeadZone  = pd->dwData;
370             } else {
371                 int obj = find_property(&This->base.data_format, ph);
372 
373                 TRACE("deadzone(%d) obj=%d\n", pd->dwData, obj);
374                 if (obj >= 0) {
375                     This->props[obj].lDeadZone  = pd->dwData;
376                     return DI_OK;
377                 }
378             }
379             break;
380         }
381         case (DWORD_PTR)DIPROP_SATURATION: {
382             LPCDIPROPDWORD pd = (LPCDIPROPDWORD)ph;
383             if (ph->dwHow == DIPH_DEVICE) {
384                 TRACE("saturation(%d) all\n", pd->dwData);
385                 for (i = 0; i < This->base.data_format.wine_df->dwNumObjs; i++)
386                     This->props[i].lSaturation = pd->dwData;
387             } else {
388                 int obj = find_property(&This->base.data_format, ph);
389 
390                 TRACE("saturation(%d) obj=%d\n", pd->dwData, obj);
391                 if (obj >= 0) {
392                     This->props[obj].lSaturation = pd->dwData;
393                     return DI_OK;
394                 }
395             }
396             break;
397         }
398         default:
399             return IDirectInputDevice2WImpl_SetProperty(iface, rguid, ph);
400         }
401     }
402 
403     return DI_OK;
404 }
405 
406 HRESULT WINAPI JoystickAGenericImpl_SetProperty(LPDIRECTINPUTDEVICE8A iface, REFGUID rguid, LPCDIPROPHEADER ph)
407 {
408     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
409     return JoystickWGenericImpl_SetProperty(IDirectInputDevice8W_from_impl(This), rguid, ph);
410 }
411 
412 #define DEBUG_TYPE(x) case (x): str = #x; break
413 void _dump_DIDEVCAPS(const DIDEVCAPS *lpDIDevCaps)
414 {
415     int type = GET_DIDEVICE_TYPE(lpDIDevCaps->dwDevType);
416     const char *str, *hid = "";
417     TRACE("dwSize: %d\n", lpDIDevCaps->dwSize);
418     TRACE("dwFlags: %08x\n", lpDIDevCaps->dwFlags);
419     switch(type)
420     {
421         /* Direct X <= 7 definitions */
422         DEBUG_TYPE(DIDEVTYPE_DEVICE);
423         DEBUG_TYPE(DIDEVTYPE_MOUSE);
424         DEBUG_TYPE(DIDEVTYPE_KEYBOARD);
425         DEBUG_TYPE(DIDEVTYPE_JOYSTICK);
426         /* Direct X >= 8 definitions */
427         DEBUG_TYPE(DI8DEVTYPE_DEVICE);
428         DEBUG_TYPE(DI8DEVTYPE_MOUSE);
429         DEBUG_TYPE(DI8DEVTYPE_KEYBOARD);
430         DEBUG_TYPE(DI8DEVTYPE_JOYSTICK);
431         DEBUG_TYPE(DI8DEVTYPE_GAMEPAD);
432         DEBUG_TYPE(DI8DEVTYPE_DRIVING);
433         DEBUG_TYPE(DI8DEVTYPE_FLIGHT);
434         DEBUG_TYPE(DI8DEVTYPE_1STPERSON);
435         DEBUG_TYPE(DI8DEVTYPE_DEVICECTRL);
436         DEBUG_TYPE(DI8DEVTYPE_SCREENPOINTER);
437         DEBUG_TYPE(DI8DEVTYPE_REMOTE);
438         DEBUG_TYPE(DI8DEVTYPE_SUPPLEMENTAL);
439         default: str = "UNKNOWN";
440     }
441 
442     if (lpDIDevCaps->dwDevType & DIDEVTYPE_HID)
443         hid = " (HID)";
444 
445     TRACE("dwDevType: %08x %s%s\n", lpDIDevCaps->dwDevType, str, hid);
446     TRACE("dwAxes: %d\n", lpDIDevCaps->dwAxes);
447     TRACE("dwButtons: %d\n", lpDIDevCaps->dwButtons);
448     TRACE("dwPOVs: %d\n", lpDIDevCaps->dwPOVs);
449     if (lpDIDevCaps->dwSize > sizeof(DIDEVCAPS_DX3)) {
450         TRACE("dwFFSamplePeriod: %d\n", lpDIDevCaps->dwFFSamplePeriod);
451         TRACE("dwFFMinTimeResolution: %d\n", lpDIDevCaps->dwFFMinTimeResolution);
452         TRACE("dwFirmwareRevision: %d\n", lpDIDevCaps->dwFirmwareRevision);
453         TRACE("dwHardwareRevision: %d\n", lpDIDevCaps->dwHardwareRevision);
454         TRACE("dwFFDriverVersion: %d\n", lpDIDevCaps->dwFFDriverVersion);
455     }
456 }
457 #undef DEBUG_TYPE
458 
459 HRESULT WINAPI JoystickWGenericImpl_GetCapabilities(LPDIRECTINPUTDEVICE8W iface, LPDIDEVCAPS lpDIDevCaps)
460 {
461     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
462     int size;
463 
464     TRACE("%p->(%p)\n",iface,lpDIDevCaps);
465 
466     if (lpDIDevCaps == NULL) {
467         WARN("invalid pointer\n");
468         return E_POINTER;
469     }
470 
471     size = lpDIDevCaps->dwSize;
472 
473     if (!(size == sizeof(DIDEVCAPS) || size == sizeof(DIDEVCAPS_DX3))) {
474         WARN("invalid parameter\n");
475         return DIERR_INVALIDPARAM;
476     }
477 
478     CopyMemory(lpDIDevCaps, &This->devcaps, size);
479     lpDIDevCaps->dwSize = size;
480 
481     if (TRACE_ON(dinput))
482         _dump_DIDEVCAPS(lpDIDevCaps);
483 
484     return DI_OK;
485 }
486 
487 HRESULT WINAPI JoystickAGenericImpl_GetCapabilities(LPDIRECTINPUTDEVICE8A iface, LPDIDEVCAPS lpDIDevCaps)
488 {
489     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
490     return JoystickWGenericImpl_GetCapabilities(IDirectInputDevice8W_from_impl(This), lpDIDevCaps);
491 }
492 
493 /******************************************************************************
494   *     GetObjectInfo : get object info
495   */
496 HRESULT WINAPI JoystickWGenericImpl_GetObjectInfo(LPDIRECTINPUTDEVICE8W iface,
497         LPDIDEVICEOBJECTINSTANCEW pdidoi, DWORD dwObj, DWORD dwHow)
498 {
499     static const WCHAR axisW[] = {'A','x','i','s',' ','%','d',0};
500     static const WCHAR povW[] = {'P','O','V',' ','%','d',0};
501     static const WCHAR buttonW[] = {'B','u','t','t','o','n',' ','%','d',0};
502     HRESULT res;
503 
504     res = IDirectInputDevice2WImpl_GetObjectInfo(iface, pdidoi, dwObj, dwHow);
505     if (res != DI_OK) return res;
506 
507     if (pdidoi->dwType & DIDFT_AXIS) {
508         sprintfW(pdidoi->tszName, axisW, DIDFT_GETINSTANCE(pdidoi->dwType));
509         pdidoi->dwFlags |= DIDOI_ASPECTPOSITION;
510     } else if (pdidoi->dwType & DIDFT_POV)
511         sprintfW(pdidoi->tszName, povW, DIDFT_GETINSTANCE(pdidoi->dwType));
512     else if (pdidoi->dwType & DIDFT_BUTTON)
513         sprintfW(pdidoi->tszName, buttonW, DIDFT_GETINSTANCE(pdidoi->dwType));
514 
515     _dump_OBJECTINSTANCEW(pdidoi);
516     return res;
517 }
518 
519 HRESULT WINAPI JoystickAGenericImpl_GetObjectInfo(LPDIRECTINPUTDEVICE8A iface,
520         LPDIDEVICEOBJECTINSTANCEA pdidoi, DWORD dwObj, DWORD dwHow)
521 {
522     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
523     HRESULT res;
524     DIDEVICEOBJECTINSTANCEW didoiW;
525     DWORD dwSize = pdidoi->dwSize;
526 
527     didoiW.dwSize = sizeof(didoiW);
528     res = JoystickWGenericImpl_GetObjectInfo(IDirectInputDevice8W_from_impl(This), &didoiW, dwObj, dwHow);
529     if (res != DI_OK) return res;
530 
531     memset(pdidoi, 0, pdidoi->dwSize);
532     memcpy(pdidoi, &didoiW, FIELD_OFFSET(DIDEVICEOBJECTINSTANCEW, tszName));
533     pdidoi->dwSize = dwSize;
534     WideCharToMultiByte(CP_ACP, 0, didoiW.tszName, -1, pdidoi->tszName,
535                         sizeof(pdidoi->tszName), NULL, NULL);
536 
537     return res;
538 }
539 
540 /******************************************************************************
541   *     GetProperty : get input device properties
542   */
543 HRESULT WINAPI JoystickWGenericImpl_GetProperty(LPDIRECTINPUTDEVICE8W iface, REFGUID rguid, LPDIPROPHEADER pdiph)
544 {
545     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
546 
547     TRACE("(%p,%s,%p)\n", iface, debugstr_guid(rguid), pdiph);
548 
549     if (TRACE_ON(dinput))
550         _dump_DIPROPHEADER(pdiph);
551 
552     if (IS_DIPROP(rguid)) {
553         switch (LOWORD(rguid)) {
554         case (DWORD_PTR) DIPROP_RANGE: {
555             LPDIPROPRANGE pr = (LPDIPROPRANGE)pdiph;
556             int obj = find_property(&This->base.data_format, pdiph);
557 
558             /* The app is querying the current range of the axis
559              * return the lMin and lMax values */
560             if (obj >= 0) {
561                 pr->lMin = This->props[obj].lMin;
562                 pr->lMax = This->props[obj].lMax;
563                 TRACE("range(%d, %d) obj=%d\n", pr->lMin, pr->lMax, obj);
564                 return DI_OK;
565             }
566             break;
567         }
568         case (DWORD_PTR) DIPROP_DEADZONE: {
569             LPDIPROPDWORD pd = (LPDIPROPDWORD)pdiph;
570             int obj = find_property(&This->base.data_format, pdiph);
571 
572             if (obj >= 0) {
573                 pd->dwData = This->props[obj].lDeadZone;
574                 TRACE("deadzone(%d) obj=%d\n", pd->dwData, obj);
575                 return DI_OK;
576             }
577             break;
578         }
579         case (DWORD_PTR) DIPROP_SATURATION: {
580             LPDIPROPDWORD pd = (LPDIPROPDWORD)pdiph;
581             int obj = find_property(&This->base.data_format, pdiph);
582 
583             if (obj >= 0) {
584                 pd->dwData = This->props[obj].lSaturation;
585                 TRACE("saturation(%d) obj=%d\n", pd->dwData, obj);
586                 return DI_OK;
587             }
588             break;
589         }
590         case (DWORD_PTR) DIPROP_PRODUCTNAME:
591         case (DWORD_PTR) DIPROP_INSTANCENAME: {
592             DIPROPSTRING *ps = (DIPROPSTRING*) pdiph;
593             DIDEVICEINSTANCEW didev;
594 
595             didev.dwSize = sizeof(didev);
596 
597             IDirectInputDevice_GetDeviceInfo(iface, &didev);
598             if (LOWORD(rguid) == (DWORD_PTR) DIPROP_PRODUCTNAME)
599                 lstrcpynW(ps->wsz, didev.tszProductName, MAX_PATH);
600             else
601                 lstrcpynW(ps->wsz, didev.tszInstanceName, MAX_PATH);
602 
603             return DI_OK;
604         }
605         default:
606             return IDirectInputDevice2WImpl_GetProperty(iface, rguid, pdiph);
607         }
608     }
609 
610     return DI_OK;
611 }
612 
613 HRESULT WINAPI JoystickAGenericImpl_GetProperty(LPDIRECTINPUTDEVICE8A iface, REFGUID rguid, LPDIPROPHEADER pdiph)
614 {
615     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
616     return JoystickWGenericImpl_GetProperty(IDirectInputDevice8W_from_impl(This), rguid, pdiph);
617 }
618 
619 /******************************************************************************
620   *     GetDeviceInfo : get information about a device's identity
621   */
622 HRESULT WINAPI JoystickAGenericImpl_GetDeviceInfo(
623     LPDIRECTINPUTDEVICE8A iface,
624     LPDIDEVICEINSTANCEA pdidi)
625 {
626     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
627     DIPROPDWORD pd;
628     DWORD index = 0;
629 
630     TRACE("(%p,%p)\n", iface, pdidi);
631 
632     if (pdidi == NULL) {
633         WARN("invalid pointer\n");
634         return E_POINTER;
635     }
636 
637     if ((pdidi->dwSize != sizeof(DIDEVICEINSTANCE_DX3A)) &&
638         (pdidi->dwSize != sizeof(DIDEVICEINSTANCEA))) {
639         WARN("invalid parameter: pdidi->dwSize = %d\n", pdidi->dwSize);
640         return DIERR_INVALIDPARAM;
641     }
642 
643     /* Try to get joystick index */
644     pd.diph.dwSize = sizeof(pd);
645     pd.diph.dwHeaderSize = sizeof(pd.diph);
646     pd.diph.dwObj = 0;
647     pd.diph.dwHow = DIPH_DEVICE;
648     if (SUCCEEDED(IDirectInputDevice2_GetProperty(iface, DIPROP_JOYSTICKID, &pd.diph)))
649         index = pd.dwData;
650 
651     /* Return joystick */
652     pdidi->guidInstance = This->guidInstance;
653     pdidi->guidProduct = This->guidProduct;
654     /* we only support traditional joysticks for now */
655     pdidi->dwDevType = This->devcaps.dwDevType;
656     snprintf(pdidi->tszInstanceName, MAX_PATH, "Joystick %d", index);
657     strcpy(pdidi->tszProductName, This->name);
658     if (pdidi->dwSize > sizeof(DIDEVICEINSTANCE_DX3A)) {
659         pdidi->guidFFDriver = GUID_NULL;
660         pdidi->wUsagePage = 0;
661         pdidi->wUsage = 0;
662     }
663 
664     return DI_OK;
665 }
666 
667 /******************************************************************************
668   *     GetDeviceInfo : get information about a device's identity
669   */
670 HRESULT WINAPI JoystickWGenericImpl_GetDeviceInfo(
671     LPDIRECTINPUTDEVICE8W iface,
672     LPDIDEVICEINSTANCEW pdidi)
673 {
674     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
675     CHAR buffer[MAX_PATH];
676     DIPROPDWORD pd;
677     DWORD index = 0;
678 
679     TRACE("(%p,%p)\n", iface, pdidi);
680 
681     if ((pdidi->dwSize != sizeof(DIDEVICEINSTANCE_DX3W)) &&
682         (pdidi->dwSize != sizeof(DIDEVICEINSTANCEW))) {
683         WARN("invalid parameter: pdidi->dwSize = %d\n", pdidi->dwSize);
684         return DIERR_INVALIDPARAM;
685     }
686 
687     /* Try to get joystick index */
688     pd.diph.dwSize = sizeof(pd);
689     pd.diph.dwHeaderSize = sizeof(pd.diph);
690     pd.diph.dwObj = 0;
691     pd.diph.dwHow = DIPH_DEVICE;
692     if (SUCCEEDED(IDirectInputDevice2_GetProperty(iface, DIPROP_JOYSTICKID, &pd.diph)))
693         index = pd.dwData;
694 
695     /* Return joystick */
696     pdidi->guidInstance = This->guidInstance;
697     pdidi->guidProduct = This->guidProduct;
698     /* we only support traditional joysticks for now */
699     pdidi->dwDevType = This->devcaps.dwDevType;
700     snprintf(buffer, sizeof(buffer), "Joystick %d", index);
701     MultiByteToWideChar(CP_ACP, 0, buffer, -1, pdidi->tszInstanceName, MAX_PATH);
702     MultiByteToWideChar(CP_ACP, 0, This->name, -1, pdidi->tszProductName, MAX_PATH);
703     if (pdidi->dwSize > sizeof(DIDEVICEINSTANCE_DX3W)) {
704         pdidi->guidFFDriver = GUID_NULL;
705         pdidi->wUsagePage = 0;
706         pdidi->wUsage = 0;
707     }
708 
709     return DI_OK;
710 }
711 
712 HRESULT WINAPI JoystickWGenericImpl_Poll(LPDIRECTINPUTDEVICE8W iface)
713 {
714     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
715 
716     TRACE("(%p)\n",This);
717 
718     if (!This->base.acquired) {
719         WARN("not acquired\n");
720         return DIERR_NOTACQUIRED;
721     }
722 
723     This->joy_polldev(IDirectInputDevice8A_from_impl(This));
724     return DI_OK;
725 }
726 
727 HRESULT WINAPI JoystickAGenericImpl_Poll(LPDIRECTINPUTDEVICE8A iface)
728 {
729     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
730     return JoystickWGenericImpl_Poll(IDirectInputDevice8W_from_impl(This));
731 }
732 
733 /******************************************************************************
734   *     GetDeviceState : returns the "state" of the joystick.
735   *
736   */
737 HRESULT WINAPI JoystickWGenericImpl_GetDeviceState(LPDIRECTINPUTDEVICE8W iface, DWORD len, LPVOID ptr)
738 {
739     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
740 
741     TRACE("(%p,0x%08x,%p)\n", This, len, ptr);
742 
743     if (!This->base.acquired) {
744         WARN("not acquired\n");
745         return DIERR_NOTACQUIRED;
746     }
747 
748     /* update joystick state */
749     This->joy_polldev(IDirectInputDevice8A_from_impl(This));
750 
751     /* convert and copy data to user supplied buffer */
752     fill_DataFormat(ptr, len, &This->js, &This->base.data_format);
753 
754     return DI_OK;
755 }
756 
757 HRESULT WINAPI JoystickAGenericImpl_GetDeviceState(LPDIRECTINPUTDEVICE8A iface, DWORD len, LPVOID ptr)
758 {
759     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
760     return JoystickWGenericImpl_GetDeviceState(IDirectInputDevice8W_from_impl(This), len, ptr);
761 }
762 
763 
764 HRESULT WINAPI JoystickWGenericImpl_BuildActionMap(LPDIRECTINPUTDEVICE8W iface,
765                                                    LPDIACTIONFORMATW lpdiaf,
766                                                    LPCWSTR lpszUserName,
767                                                    DWORD dwFlags)
768 {
769     static const DWORD object_types[] = { DIDFT_AXIS, DIDFT_BUTTON };
770     static const DWORD type_map[] = { DIDFT_RELAXIS, DIDFT_PSHBUTTON };
771     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
772     unsigned int i, j;
773     BOOL has_actions = FALSE;
774 
775     FIXME("(%p)->(%p,%s,%08x): semi-stub !\n", iface, lpdiaf, debugstr_w(lpszUserName), dwFlags);
776 
777     for (i=0; i < lpdiaf->dwNumActions; i++)
778     {
779         DWORD inst = (0x000000ff & (lpdiaf->rgoAction[i].dwSemantic)) - 1;
780         DWORD type = 0x000000ff & (lpdiaf->rgoAction[i].dwSemantic >> 8);
781         DWORD genre = 0xff000000 & lpdiaf->rgoAction[i].dwSemantic;
782 
783         /* Don't touch a user configured action */
784         if (lpdiaf->rgoAction[i].dwHow == DIAH_USERCONFIG) continue;
785 
786         /* Only consider actions of the right genre */
787         if (lpdiaf->dwGenre != genre && genre != DIGENRE_ANY) continue;
788 
789         for (j = 0; j < ARRAY_SIZE(object_types); j++)
790         {
791             if (type & object_types[j])
792             {
793                 /* Ensure that the object exists */
794                 LPDIOBJECTDATAFORMAT odf = dataformat_to_odf_by_type(This->base.data_format.wine_df, inst, object_types[j]);
795 
796                 if (odf != NULL)
797                 {
798                     lpdiaf->rgoAction[i].dwObjID = type_map[j] | (0x0000ff00 & (inst << 8));
799                     lpdiaf->rgoAction[i].guidInstance = This->base.guid;
800                     lpdiaf->rgoAction[i].dwHow = DIAH_DEFAULT;
801 
802                     has_actions = TRUE;
803 
804                     /* No need to try other types if the action was already mapped */
805                     break;
806                 }
807             }
808         }
809     }
810 
811     if (!has_actions) return DI_NOEFFECT;
812 
813     return IDirectInputDevice8WImpl_BuildActionMap(iface, lpdiaf, lpszUserName, dwFlags);
814 }
815 
816 HRESULT WINAPI JoystickAGenericImpl_BuildActionMap(LPDIRECTINPUTDEVICE8A iface,
817                                                    LPDIACTIONFORMATA lpdiaf,
818                                                    LPCSTR lpszUserName,
819                                                    DWORD dwFlags)
820 {
821     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
822     DIACTIONFORMATW diafW;
823     HRESULT hr;
824     WCHAR *lpszUserNameW = NULL;
825     int username_size;
826 
827     diafW.rgoAction = HeapAlloc(GetProcessHeap(), 0, sizeof(DIACTIONW)*lpdiaf->dwNumActions);
828     _copy_diactionformatAtoW(&diafW, lpdiaf);
829 
830     if (lpszUserName != NULL)
831     {
832         username_size = MultiByteToWideChar(CP_ACP, 0, lpszUserName, -1, NULL, 0);
833         lpszUserNameW = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*username_size);
834         MultiByteToWideChar(CP_ACP, 0, lpszUserName, -1, lpszUserNameW, username_size);
835     }
836 
837     hr = JoystickWGenericImpl_BuildActionMap(&This->base.IDirectInputDevice8W_iface, &diafW, lpszUserNameW, dwFlags);
838 
839     _copy_diactionformatWtoA(lpdiaf, &diafW);
840     HeapFree(GetProcessHeap(), 0, diafW.rgoAction);
841     HeapFree(GetProcessHeap(), 0, lpszUserNameW);
842 
843     return hr;
844 }
845 
846 HRESULT WINAPI JoystickWGenericImpl_SetActionMap(LPDIRECTINPUTDEVICE8W iface,
847                                                  LPDIACTIONFORMATW lpdiaf,
848                                                  LPCWSTR lpszUserName,
849                                                  DWORD dwFlags)
850 {
851     JoystickGenericImpl *This = impl_from_IDirectInputDevice8W(iface);
852 
853     FIXME("(%p)->(%p,%s,%08x): semi-stub !\n", iface, lpdiaf, debugstr_w(lpszUserName), dwFlags);
854 
855     return _set_action_map(iface, lpdiaf, lpszUserName, dwFlags, This->base.data_format.wine_df);
856 }
857 
858 HRESULT WINAPI JoystickAGenericImpl_SetActionMap(LPDIRECTINPUTDEVICE8A iface,
859                                                  LPDIACTIONFORMATA lpdiaf,
860                                                  LPCSTR lpszUserName,
861                                                  DWORD dwFlags)
862 {
863     JoystickGenericImpl *This = impl_from_IDirectInputDevice8A(iface);
864     DIACTIONFORMATW diafW;
865     HRESULT hr;
866     WCHAR *lpszUserNameW = NULL;
867     int username_size;
868 
869     diafW.rgoAction = HeapAlloc(GetProcessHeap(), 0, sizeof(DIACTIONW)*lpdiaf->dwNumActions);
870     _copy_diactionformatAtoW(&diafW, lpdiaf);
871 
872     if (lpszUserName != NULL)
873     {
874         username_size = MultiByteToWideChar(CP_ACP, 0, lpszUserName, -1, NULL, 0);
875         lpszUserNameW = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*username_size);
876         MultiByteToWideChar(CP_ACP, 0, lpszUserName, -1, lpszUserNameW, username_size);
877     }
878 
879     hr = JoystickWGenericImpl_SetActionMap(&This->base.IDirectInputDevice8W_iface, &diafW, lpszUserNameW, dwFlags);
880 
881     HeapFree(GetProcessHeap(), 0, diafW.rgoAction);
882     HeapFree(GetProcessHeap(), 0, lpszUserNameW);
883 
884     return hr;
885 }
886 
887 /*
888  * This maps the read value (from the input event) to a value in the
889  * 'wanted' range.
890  * Notes:
891  *   Dead zone is in % multiplied by a 100 (range 0..10000)
892  */
893 LONG joystick_map_axis(ObjProps *props, int val)
894 {
895     LONG ret;
896     LONG dead_zone = MulDiv( props->lDeadZone, props->lDevMax - props->lDevMin, 10000 );
897     LONG dev_range = props->lDevMax - props->lDevMin - dead_zone;
898 
899     /* Center input */
900     val -= (props->lDevMin + props->lDevMax) / 2;
901 
902     /* Remove dead zone */
903     if (abs( val ) <= dead_zone / 2)
904         val = 0;
905     else
906         val = val < 0 ? val + dead_zone / 2 : val - dead_zone / 2;
907 
908     /* Scale and map the value from the device range into the required range */
909     ret = MulDiv( val, props->lMax - props->lMin, dev_range ) +
910           (props->lMin + props->lMax) / 2;
911 
912     /* Clamp in case or rounding errors */
913     if      (ret > props->lMax) ret = props->lMax;
914     else if (ret < props->lMin) ret = props->lMin;
915 
916     TRACE( "(%d <%d> %d) -> (%d <%d> %d): val=%d ret=%d\n",
917            props->lDevMin, dead_zone, props->lDevMax,
918            props->lMin, props->lDeadZone, props->lMax,
919            val, ret );
920 
921     return ret;
922 }
923 
924 /*
925  * Maps POV x & y event values to a DX "clock" position:
926  *         0
927  *   31500    4500
928  * 27000  -1    9000
929  *   22500   13500
930  *       18000
931  */
932 DWORD joystick_map_pov(const POINTL *p)
933 {
934     if (p->x > 0)
935         return p->y < 0 ?  4500 : !p->y ?  9000 : 13500;
936     else if (p->x < 0)
937         return p->y < 0 ? 31500 : !p->y ? 27000 : 22500;
938     else
939         return p->y < 0 ?     0 : !p->y ?    -1 : 18000;
940 }
941 
942 /*
943  * Setup the dinput options.
944  */
945 
946 HRESULT setup_dinput_options(JoystickGenericImpl *This, const int *default_axis_map)
947 {
948     char buffer[MAX_PATH+16];
949     HKEY hkey, appkey;
950     int tokens = 0;
951     int axis = 0;
952     int pov = 0;
953 
954     get_app_key(&hkey, &appkey);
955 
956     /* get options */
957 
958     if (!get_config_key(hkey, appkey, "DefaultDeadZone", buffer, sizeof(buffer)))
959     {
960         This->deadzone = atoi(buffer);
961         TRACE("setting default deadzone to: \"%s\" %d\n", buffer, This->deadzone);
962     }
963 
964     This->axis_map = HeapAlloc(GetProcessHeap(), 0, This->device_axis_count * sizeof(int));
965     if (!This->axis_map) return DIERR_OUTOFMEMORY;
966 
967     if (!get_config_key(hkey, appkey, This->name, buffer, sizeof(buffer)))
968     {
969         static const char *axis_names[] = {"X", "Y", "Z", "Rx", "Ry", "Rz",
970                                            "Slider1", "Slider2",
971                                            "POV1", "POV2", "POV3", "POV4"};
972         const char *delim = ",";
973         char * ptr;
974         TRACE("\"%s\" = \"%s\"\n", This->name, buffer);
975 
976         if ((ptr = strtok(buffer, delim)) != NULL)
977         {
978             do
979             {
980                 int i;
981 
982                 for (i = 0; i < ARRAY_SIZE(axis_names); i++)
983                 {
984                     if (!strcmp(ptr, axis_names[i]))
985                     {
986                         if (!strncmp(ptr, "POV", 3))
987                         {
988                             if (pov >= 4)
989                             {
990                                 WARN("Only 4 POVs supported - ignoring extra\n");
991                                 i = -1;
992                             }
993                             else
994                             {
995                                 /* Pov takes two axes */
996                                 This->axis_map[tokens++] = i;
997                                 pov++;
998                             }
999                         }
1000                         else
1001                         {
1002                             if (axis >= 8)
1003                             {
1004                                 FIXME("Only 8 Axes supported - ignoring extra\n");
1005                                 i = -1;
1006                             }
1007                             else
1008                                 axis++;
1009                         }
1010                         break;
1011                     }
1012                 }
1013 
1014                 if (i == ARRAY_SIZE(axis_names))
1015                 {
1016                     ERR("invalid joystick axis type: \"%s\"\n", ptr);
1017                     i = -1;
1018                 }
1019 
1020                 This->axis_map[tokens] = i;
1021                 tokens++;
1022             } while ((ptr = strtok(NULL, delim)) != NULL);
1023 
1024             if (tokens != This->device_axis_count)
1025             {
1026                 ERR("not all joystick axes mapped: %d axes(%d,%d), %d arguments\n",
1027                     This->device_axis_count, axis, pov, tokens);
1028                 while (tokens < This->device_axis_count)
1029                 {
1030                     This->axis_map[tokens] = -1;
1031                     tokens++;
1032                 }
1033             }
1034         }
1035     }
1036     else
1037     {
1038         int i;
1039 
1040         if (default_axis_map)
1041         {
1042             /* Use default mapping from the driver */
1043             for (i = 0; i < This->device_axis_count; i++)
1044             {
1045                 This->axis_map[i] = default_axis_map[i];
1046                 tokens = default_axis_map[i];
1047                 if (tokens < 0)
1048                     continue;
1049                 if (tokens < 8)
1050                     axis++;
1051                 else if (tokens < 15)
1052                 {
1053                     i++;
1054                     pov++;
1055                     This->axis_map[i] = default_axis_map[i];
1056                 }
1057             }
1058         }
1059         else
1060         {
1061             /* No config - set default mapping. */
1062             for (i = 0; i < This->device_axis_count; i++)
1063             {
1064                 if (i < 8)
1065                     This->axis_map[i] = axis++;
1066                 else if (i < 15)
1067                 {
1068                     This->axis_map[i++] = 8 + pov;
1069                     This->axis_map[i  ] = 8 + pov++;
1070                 }
1071                 else
1072                     This->axis_map[i] = -1;
1073             }
1074         }
1075     }
1076     This->devcaps.dwAxes = axis;
1077     This->devcaps.dwPOVs = pov;
1078 
1079     if (appkey) RegCloseKey(appkey);
1080     if (hkey)   RegCloseKey(hkey);
1081 
1082     return DI_OK;
1083 }
1084