1 |
/* PCSX2 - PS2 Emulator for PCs |
2 |
* Copyright (C) 2002-2010 PCSX2 Dev Team |
3 |
* |
4 |
* PCSX2 is free software: you can redistribute it and/or modify it under the terms |
5 |
* of the GNU Lesser General Public License as published by the Free Software Found- |
6 |
* ation, either version 3 of the License, or (at your option) any later version. |
7 |
* |
8 |
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
9 |
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
10 |
* PURPOSE. See the GNU General Public License for more details. |
11 |
* |
12 |
* You should have received a copy of the GNU General Public License along with PCSX2. |
13 |
* If not, see <http://www.gnu.org/licenses/>. |
14 |
*/ |
15 |
|
16 |
|
17 |
#include "PrecompiledHeader.h" |
18 |
#include "IopCommon.h" |
19 |
#include "R5900.h" // for g_GameStarted |
20 |
|
21 |
#include <ctype.h> |
22 |
#include <string.h> |
23 |
|
24 |
#ifndef O_BINARY |
25 |
#define O_BINARY 0 |
26 |
#endif |
27 |
|
28 |
// set this to 0 to disable rewriting 'host:' paths! |
29 |
#define USE_HOST_REWRITE 1 |
30 |
|
31 |
#if USE_HOST_REWRITE |
32 |
# ifdef WIN32 |
33 |
// disable this if you DON'T want "host:/usr/local/" paths |
34 |
// to get rewritten into host:/ |
35 |
# define HOST_REWRITE_USR_LOCAL 1 |
36 |
# else |
37 |
// unix/linux users might want to set it to 1 |
38 |
// if they DO want to keep demos from accessing their systems' /usr/local |
39 |
# define HOST_REWRITE_USR_LOCAL 0 |
40 |
# endif |
41 |
|
42 |
static char HostRoot[1024]; |
43 |
#endif |
44 |
|
45 |
void Hle_SetElfPath(const char* elfFileName) |
46 |
{ |
47 |
#if USE_HOST_REWRITE |
48 |
DevCon.WriteLn("HLE Host: Will load ELF: %s\n", elfFileName); |
49 |
|
50 |
const char* pos1 = strrchr(elfFileName,'/'); |
51 |
const char* pos2 = strrchr(elfFileName,'\\'); |
52 |
|
53 |
if(pos2 > pos1) // we want the LAST path separator |
54 |
pos1=pos2; |
55 |
|
56 |
if(!pos1) // if pos1 is NULL, then pos2 was not > pos1, so it must also be NULL |
57 |
{ |
58 |
Console.WriteLn("HLE Notice: ELF does not have a path.\n"); |
59 |
|
60 |
// use %CD%/host/ |
61 |
getcwd(HostRoot,1000); // save the other 23 chars to append /host/ :P |
62 |
HostRoot[1000]=0; // Be Safe. |
63 |
|
64 |
char* last = HostRoot + strlen(HostRoot) - 1; |
65 |
|
66 |
if((*last!='/') && (*last!='\\')) // PathAppend()-ish |
67 |
last++; |
68 |
|
69 |
strcpy(last,"/host/"); |
70 |
|
71 |
return; |
72 |
} |
73 |
|
74 |
int len = pos1-elfFileName+1; |
75 |
memcpy(HostRoot,elfFileName,len); // include the / (or \\) |
76 |
HostRoot[len] = 0; |
77 |
|
78 |
Console.WriteLn("HLE Host: Set 'host:' root path to: %s\n", HostRoot); |
79 |
|
80 |
#endif |
81 |
} |
82 |
|
83 |
namespace R3000A { |
84 |
|
85 |
#define v0 (psxRegs.GPR.n.v0) |
86 |
#define a0 (psxRegs.GPR.n.a0) |
87 |
#define a1 (psxRegs.GPR.n.a1) |
88 |
#define a2 (psxRegs.GPR.n.a2) |
89 |
#define a3 (psxRegs.GPR.n.a3) |
90 |
#define sp (psxRegs.GPR.n.sp) |
91 |
#define ra (psxRegs.GPR.n.ra) |
92 |
#define pc (psxRegs.pc) |
93 |
|
94 |
#define Ra0 (iopVirtMemR<char>(a0)) |
95 |
#define Ra1 (iopVirtMemR<char>(a1)) |
96 |
#define Ra2 (iopVirtMemR<char>(a2)) |
97 |
#define Ra3 (iopVirtMemR<char>(a3)) |
98 |
|
99 |
// TODO: sandbox option, other permissions |
100 |
class HostFile : public IOManFile |
101 |
{ |
102 |
public: |
103 |
int fd; |
104 |
|
105 |
HostFile(int hostfd) |
106 |
{ |
107 |
fd = hostfd; |
108 |
} |
109 |
|
110 |
static __fi int translate_error(int err) |
111 |
{ |
112 |
if (err >= 0) |
113 |
return err; |
114 |
|
115 |
switch(err) |
116 |
{ |
117 |
case -ENOENT: |
118 |
return -IOP_ENOENT; |
119 |
case -EACCES: |
120 |
return -IOP_EACCES; |
121 |
case -EISDIR: |
122 |
return -IOP_EISDIR; |
123 |
case -EIO: |
124 |
default: |
125 |
return -IOP_EIO; |
126 |
} |
127 |
} |
128 |
|
129 |
static int open(IOManFile **file, const char *name, s32 flags, u16 mode) |
130 |
{ |
131 |
const char *path = strchr(name, ':') + 1; |
132 |
|
133 |
// host: actually DOES let you write! |
134 |
//if (flags != IOP_O_RDONLY) |
135 |
// return -IOP_EROFS; |
136 |
|
137 |
// WIP code. Works well on win32, not so sure on unixes |
138 |
// TODO: get rid of dependency on CWD/PWD |
139 |
#if USE_HOST_REWRITE |
140 |
// we want filenames to be relative to pcs2dir / host |
141 |
|
142 |
static char pathMod[1024]; |
143 |
|
144 |
// partial "rooting", |
145 |
// it will NOT avoid a path like "../../x" from escaping the pcsx2 folder! |
146 |
|
147 |
#if HOST_REWRITE_USR_LOCAL |
148 |
const char *_local_root = "/usr/local/"; |
149 |
if(strncmp(path,_local_root,strlen(_local_root))==0) |
150 |
{ |
151 |
strcpy(pathMod,HostRoot); |
152 |
strcat(pathMod,path+strlen(_local_root)); |
153 |
} |
154 |
else |
155 |
#endif |
156 |
if((path[0] == '/') || (path[0] == '\\') || (isalpha(path[0]) && (path[1] == ':'))) // absolute NATIVE path (X:\blah) |
157 |
{ |
158 |
// TODO: allow some way to use native paths in non-windows platforms |
159 |
// maybe hack it so linux prefixes the path with "X:"? ;P |
160 |
// or have all platforms use a common prefix for native paths |
161 |
strcpy(pathMod,path); |
162 |
} |
163 |
else // relative paths |
164 |
{ |
165 |
strcpy(pathMod,HostRoot); |
166 |
strcat(pathMod,path); |
167 |
} |
168 |
#else |
169 |
const char* pathMod = path; |
170 |
#endif |
171 |
|
172 |
int native_flags = O_BINARY; // necessary in Windows. |
173 |
|
174 |
switch(flags&IOP_O_RDWR) |
175 |
{ |
176 |
case IOP_O_RDONLY: native_flags |= O_RDONLY; break; |
177 |
case IOP_O_WRONLY: native_flags |= O_WRONLY; break; |
178 |
case IOP_O_RDWR: native_flags |= O_RDWR; break; |
179 |
} |
180 |
|
181 |
if(flags&IOP_O_APPEND) native_flags |= O_APPEND; |
182 |
if(flags&IOP_O_CREAT) native_flags |= O_CREAT; |
183 |
if(flags&IOP_O_TRUNC) native_flags |= O_TRUNC; |
184 |
|
185 |
int hostfd = ::open(pathMod, native_flags); |
186 |
if (hostfd < 0) |
187 |
return translate_error(hostfd); |
188 |
|
189 |
*file = new HostFile(hostfd); |
190 |
if (!*file) |
191 |
return -IOP_ENOMEM; |
192 |
|
193 |
return 0; |
194 |
} |
195 |
|
196 |
virtual void close() |
197 |
{ |
198 |
::close(fd); |
199 |
delete this; |
200 |
} |
201 |
|
202 |
virtual int lseek(s32 offset, s32 whence) |
203 |
{ |
204 |
int err; |
205 |
|
206 |
switch (whence) |
207 |
{ |
208 |
case IOP_SEEK_SET: |
209 |
err = ::lseek(fd, offset, SEEK_SET); |
210 |
break; |
211 |
case IOP_SEEK_CUR: |
212 |
err = ::lseek(fd, offset, SEEK_CUR); |
213 |
break; |
214 |
case IOP_SEEK_END: |
215 |
err = ::lseek(fd, offset, SEEK_END); |
216 |
break; |
217 |
default: |
218 |
return -IOP_EIO; |
219 |
} |
220 |
|
221 |
return translate_error(err); |
222 |
} |
223 |
|
224 |
virtual int read(void *buf, u32 count) |
225 |
{ |
226 |
return translate_error(::read(fd, buf, count)); |
227 |
} |
228 |
|
229 |
virtual int write(void *buf, u32 count) |
230 |
{ |
231 |
return translate_error(::write(fd, buf, count)); |
232 |
} |
233 |
}; |
234 |
|
235 |
namespace ioman { |
236 |
const int firstfd = 0x100; |
237 |
const int maxfds = 0x100; |
238 |
int openfds = 0; |
239 |
|
240 |
int freefdcount() |
241 |
{ |
242 |
return maxfds - openfds; |
243 |
} |
244 |
|
245 |
struct filedesc |
246 |
{ |
247 |
enum { |
248 |
FILE_FREE, |
249 |
FILE_FILE, |
250 |
FILE_DIR, |
251 |
} type; |
252 |
union { |
253 |
IOManFile *file; |
254 |
IOManDir *dir; |
255 |
}; |
256 |
|
257 |
operator bool() const { return type != FILE_FREE; } |
258 |
operator IOManFile*() const { return type == FILE_FILE ? file : NULL; } |
259 |
operator IOManDir*() const { return type == FILE_DIR ? dir : NULL; } |
260 |
void operator=(IOManFile *f) { type = FILE_FILE; file = f; openfds++; } |
261 |
void operator=(IOManDir *d) { type = FILE_DIR; dir = d; openfds++; } |
262 |
|
263 |
void close() |
264 |
{ |
265 |
if (type == FILE_FREE) |
266 |
return; |
267 |
|
268 |
switch (type) |
269 |
{ |
270 |
case FILE_FILE: |
271 |
file->close(); |
272 |
file = NULL; |
273 |
break; |
274 |
case FILE_DIR: |
275 |
dir->close(); |
276 |
dir = NULL; |
277 |
break; |
278 |
} |
279 |
|
280 |
type = FILE_FREE; |
281 |
openfds--; |
282 |
} |
283 |
}; |
284 |
|
285 |
filedesc fds[maxfds]; |
286 |
|
287 |
template<typename T> |
288 |
T* getfd(int fd) |
289 |
{ |
290 |
fd -= firstfd; |
291 |
|
292 |
if (fd < 0 || fd >= maxfds) |
293 |
return NULL; |
294 |
|
295 |
return fds[fd]; |
296 |
} |
297 |
|
298 |
template <typename T> |
299 |
int allocfd(T *obj) |
300 |
{ |
301 |
for (int i = 0; i < maxfds; i++) |
302 |
{ |
303 |
if (!fds[i]) |
304 |
{ |
305 |
fds[i] = obj; |
306 |
return firstfd + i; |
307 |
} |
308 |
} |
309 |
|
310 |
obj->close(); |
311 |
return -IOP_EMFILE; |
312 |
} |
313 |
|
314 |
void freefd(int fd) |
315 |
{ |
316 |
fd -= firstfd; |
317 |
|
318 |
if (fd < 0 || fd >= maxfds) |
319 |
return; |
320 |
|
321 |
fds[fd].close(); |
322 |
} |
323 |
|
324 |
void reset() |
325 |
{ |
326 |
for (int i = 0; i < maxfds; i++) |
327 |
fds[i].close(); |
328 |
} |
329 |
|
330 |
int open_HLE() |
331 |
{ |
332 |
IOManFile *file = NULL; |
333 |
const char *name = Ra0; |
334 |
s32 flags = a1; |
335 |
u16 mode = a2; |
336 |
|
337 |
if ((!g_GameStarted || EmuConfig.HostFs) |
338 |
&& !strncmp(name, "host", 4) && name[4 + strspn(name + 4, "0123456789")] == ':') |
339 |
{ |
340 |
if (!freefdcount()) |
341 |
{ |
342 |
v0 = -IOP_EMFILE; |
343 |
pc = ra; |
344 |
return 1; |
345 |
} |
346 |
|
347 |
int err = HostFile::open(&file, name, flags, mode); |
348 |
|
349 |
if (err != 0 || !file) |
350 |
{ |
351 |
if (err == 0) // ??? |
352 |
err = -IOP_EIO; |
353 |
if (file) // ?????? |
354 |
file->close(); |
355 |
v0 = err; |
356 |
} |
357 |
else |
358 |
{ |
359 |
v0 = allocfd(file); |
360 |
if ((s32)v0 < 0) |
361 |
file->close(); |
362 |
} |
363 |
|
364 |
pc = ra; |
365 |
return 1; |
366 |
} |
367 |
|
368 |
return 0; |
369 |
} |
370 |
|
371 |
int close_HLE() |
372 |
{ |
373 |
s32 fd = a0; |
374 |
|
375 |
if (getfd<IOManFile>(fd)) |
376 |
{ |
377 |
freefd(fd); |
378 |
v0 = 0; |
379 |
pc = ra; |
380 |
return 1; |
381 |
} |
382 |
|
383 |
return 0; |
384 |
} |
385 |
|
386 |
int lseek_HLE() |
387 |
{ |
388 |
s32 fd = a0; |
389 |
s32 offset = a1; |
390 |
s32 whence = a2; |
391 |
|
392 |
if (IOManFile *file = getfd<IOManFile>(fd)) |
393 |
{ |
394 |
v0 = file->lseek(offset, whence); |
395 |
pc = ra; |
396 |
return 1; |
397 |
} |
398 |
|
399 |
return 0; |
400 |
} |
401 |
|
402 |
int read_HLE() |
403 |
{ |
404 |
s32 fd = a0; |
405 |
u32 buf = a1; |
406 |
u32 count = a2; |
407 |
|
408 |
if (IOManFile *file = getfd<IOManFile>(fd)) |
409 |
{ |
410 |
if (!iopVirtMemR<void>(buf)) |
411 |
return 0; |
412 |
|
413 |
v0 = file->read(iopVirtMemW<void>(buf), count); |
414 |
pc = ra; |
415 |
return 1; |
416 |
} |
417 |
|
418 |
return 0; |
419 |
} |
420 |
|
421 |
int write_HLE() |
422 |
{ |
423 |
s32 fd = a0; |
424 |
u32 buf = a1; |
425 |
u32 count = a2; |
426 |
|
427 |
if (fd == 1) // stdout |
428 |
{ |
429 |
iopConLog(ShiftJIS_ConvertString(Ra1, a2)); |
430 |
pc = ra; |
431 |
v0 = a2; |
432 |
return 1; |
433 |
} |
434 |
else if (IOManFile *file = getfd<IOManFile>(fd)) |
435 |
{ |
436 |
if (!iopVirtMemR<void>(buf)) |
437 |
return 0; |
438 |
|
439 |
v0 = file->write(iopVirtMemW<void>(buf), count); |
440 |
pc = ra; |
441 |
return 1; |
442 |
} |
443 |
|
444 |
return 0; |
445 |
} |
446 |
} |
447 |
|
448 |
namespace sysmem { |
449 |
int Kprintf_HLE() |
450 |
{ |
451 |
// Emulate the expected Kprintf functionality: |
452 |
iopMemWrite32(sp, a0); |
453 |
iopMemWrite32(sp + 4, a1); |
454 |
iopMemWrite32(sp + 8, a2); |
455 |
iopMemWrite32(sp + 12, a3); |
456 |
pc = ra; |
457 |
|
458 |
|
459 |
// From here we're intercepting the Kprintf and piping it to our console, complete with |
460 |
// printf-style formatting processing. This part can be skipped if the user has the |
461 |
// console disabled. |
462 |
|
463 |
if (!SysConsole.iopConsole.IsActive()) return 1; |
464 |
|
465 |
char tmp[1024], tmp2[1024]; |
466 |
char *ptmp = tmp; |
467 |
int n=1, i=0, j = 0; |
468 |
|
469 |
while (Ra0[i]) |
470 |
{ |
471 |
switch (Ra0[i]) |
472 |
{ |
473 |
case '%': |
474 |
j = 0; |
475 |
tmp2[j++] = '%'; |
476 |
_start: |
477 |
switch (Ra0[++i]) |
478 |
{ |
479 |
case '.': |
480 |
case 'l': |
481 |
tmp2[j++] = Ra0[i]; |
482 |
goto _start; |
483 |
default: |
484 |
if (Ra0[i] >= '0' && Ra0[i] <= '9') |
485 |
{ |
486 |
tmp2[j++] = Ra0[i]; |
487 |
goto _start; |
488 |
} |
489 |
break; |
490 |
} |
491 |
|
492 |
tmp2[j++] = Ra0[i]; |
493 |
tmp2[j] = 0; |
494 |
|
495 |
switch (Ra0[i]) |
496 |
{ |
497 |
case 'f': case 'F': |
498 |
ptmp+= sprintf(ptmp, tmp2, (float)iopMemRead32(sp + n * 4)); |
499 |
n++; |
500 |
break; |
501 |
|
502 |
case 'a': case 'A': |
503 |
case 'e': case 'E': |
504 |
case 'g': case 'G': |
505 |
ptmp+= sprintf(ptmp, tmp2, (double)iopMemRead32(sp + n * 4)); |
506 |
n++; |
507 |
break; |
508 |
|
509 |
case 'p': |
510 |
case 'i': |
511 |
case 'd': case 'D': |
512 |
case 'o': case 'O': |
513 |
case 'x': case 'X': |
514 |
ptmp+= sprintf(ptmp, tmp2, (u32)iopMemRead32(sp + n * 4)); |
515 |
n++; |
516 |
break; |
517 |
|
518 |
case 'c': |
519 |
ptmp+= sprintf(ptmp, tmp2, (u8)iopMemRead32(sp + n * 4)); |
520 |
n++; |
521 |
break; |
522 |
|
523 |
case 's': |
524 |
ptmp+= sprintf(ptmp, tmp2, iopVirtMemR<char>(iopMemRead32(sp + n * 4))); |
525 |
n++; |
526 |
break; |
527 |
|
528 |
case '%': |
529 |
*ptmp++ = Ra0[i]; |
530 |
break; |
531 |
|
532 |
default: |
533 |
break; |
534 |
} |
535 |
i++; |
536 |
break; |
537 |
|
538 |
default: |
539 |
*ptmp++ = Ra0[i++]; |
540 |
break; |
541 |
} |
542 |
} |
543 |
*ptmp = 0; |
544 |
iopConLog( ShiftJIS_ConvertString(tmp, 1023) ); |
545 |
|
546 |
return 1; |
547 |
} |
548 |
} |
549 |
|
550 |
namespace loadcore { |
551 |
void RegisterLibraryEntries_DEBUG() |
552 |
{ |
553 |
DbgCon.WriteLn(Color_Gray, "RegisterLibraryEntries: %8.8s", iopVirtMemR<char>(a0 + 12)); |
554 |
} |
555 |
} |
556 |
|
557 |
namespace intrman { |
558 |
static const char* intrname[] = { |
559 |
"INT_VBLANK", "INT_GM", "INT_CDROM", "INT_DMA", //00 |
560 |
"INT_RTC0", "INT_RTC1", "INT_RTC2", "INT_SIO0", //04 |
561 |
"INT_SIO1", "INT_SPU", "INT_PIO", "INT_EVBLANK", //08 |
562 |
"INT_DVD", "INT_PCMCIA", "INT_RTC3", "INT_RTC4", //0C |
563 |
"INT_RTC5", "INT_SIO2", "INT_HTR0", "INT_HTR1", //10 |
564 |
"INT_HTR2", "INT_HTR3", "INT_USB", "INT_EXTR", //14 |
565 |
"INT_FWRE", "INT_FDMA", "INT_1A", "INT_1B", //18 |
566 |
"INT_1C", "INT_1D", "INT_1E", "INT_1F", //1C |
567 |
"INT_dmaMDECi", "INT_dmaMDECo", "INT_dmaGPU", "INT_dmaCD", //20 |
568 |
"INT_dmaSPU", "INT_dmaPIO", "INT_dmaOTC", "INT_dmaBERR", //24 |
569 |
"INT_dmaSPU2", "INT_dma8", "INT_dmaSIF0", "INT_dmaSIF1", //28 |
570 |
"INT_dmaSIO2i", "INT_dmaSIO2o", "INT_2E", "INT_2F", //2C |
571 |
"INT_30", "INT_31", "INT_32", "INT_33", //30 |
572 |
"INT_34", "INT_35", "INT_36", "INT_37", //34 |
573 |
"INT_38", "INT_39", "INT_3A", "INT_3B", //38 |
574 |
"INT_3C", "INT_3D", "INT_3E", "INT_3F", //3C |
575 |
"INT_MAX" //40 |
576 |
}; |
577 |
|
578 |
void RegisterIntrHandler_DEBUG() |
579 |
{ |
580 |
DbgCon.WriteLn(Color_Gray, "RegisterIntrHandler: intr %s, handler %x", intrname[a0], a2); |
581 |
} |
582 |
} |
583 |
|
584 |
namespace sifcmd { |
585 |
void sceSifRegisterRpc_DEBUG() |
586 |
{ |
587 |
DbgCon.WriteLn( Color_Gray, "sifcmd sceSifRegisterRpc: rpc_id %x", a1); |
588 |
} |
589 |
} |
590 |
|
591 |
const char* irxImportLibname(u32 entrypc) |
592 |
{ |
593 |
u32 i; |
594 |
|
595 |
i = entrypc; |
596 |
while (iopMemRead32(i -= 4) != 0x41e00000) // documented magic number |
597 |
; |
598 |
|
599 |
return iopVirtMemR<char>(i + 12); |
600 |
} |
601 |
|
602 |
const char* irxImportFuncname(const char libname[8], u16 index) |
603 |
{ |
604 |
#include "IopModuleNames.cpp" |
605 |
|
606 |
switch (index) { |
607 |
case 0: return "start"; |
608 |
// case 1: reinit? |
609 |
case 2: return "shutdown"; |
610 |
// case 3: ??? |
611 |
} |
612 |
|
613 |
return 0; |
614 |
} |
615 |
|
616 |
#define MODULE(n) if (!strncmp(libname, #n, 8)) { using namespace n; switch (index) { |
617 |
#define END_MODULE }} |
618 |
#define EXPORT_D(i, n) case (i): return n ## _DEBUG; |
619 |
#define EXPORT_H(i, n) case (i): return n ## _HLE; |
620 |
|
621 |
irxHLE irxImportHLE(const char libname[8], u16 index) |
622 |
{ |
623 |
// debugging output |
624 |
MODULE(sysmem) |
625 |
EXPORT_H( 14, Kprintf) |
626 |
END_MODULE |
627 |
|
628 |
MODULE(ioman) |
629 |
EXPORT_H( 4, open) |
630 |
EXPORT_H( 5, close) |
631 |
EXPORT_H( 6, read) |
632 |
EXPORT_H( 7, write) |
633 |
EXPORT_H( 8, lseek) |
634 |
END_MODULE |
635 |
|
636 |
return 0; |
637 |
} |
638 |
|
639 |
irxDEBUG irxImportDebug(const char libname[8], u16 index) |
640 |
{ |
641 |
MODULE(loadcore) |
642 |
EXPORT_D( 6, RegisterLibraryEntries) |
643 |
END_MODULE |
644 |
MODULE(intrman) |
645 |
EXPORT_D( 4, RegisterIntrHandler) |
646 |
END_MODULE |
647 |
MODULE(sifcmd) |
648 |
EXPORT_D( 17, sceSifRegisterRpc) |
649 |
END_MODULE |
650 |
|
651 |
return 0; |
652 |
} |
653 |
|
654 |
#undef MODULE |
655 |
#undef END_MODULE |
656 |
#undef EXPORT_D |
657 |
#undef EXPORT_H |
658 |
|
659 |
void __fastcall irxImportLog(const char libname[8], u16 index, const char *funcname) |
660 |
{ |
661 |
PSXBIOS_LOG("%8.8s.%03d: %s (%x, %x, %x, %x)", |
662 |
libname, index, funcname ? funcname : "unknown", |
663 |
a0, a1, a2, a3); |
664 |
} |
665 |
|
666 |
int __fastcall irxImportExec(const char libname[8], u16 index) |
667 |
{ |
668 |
const char *funcname = irxImportFuncname(libname, index); |
669 |
irxHLE hle = irxImportHLE(libname, index); |
670 |
irxDEBUG debug = irxImportDebug(libname, index); |
671 |
|
672 |
irxImportLog(libname, index, funcname); |
673 |
|
674 |
if (debug) |
675 |
debug(); |
676 |
|
677 |
if (hle) |
678 |
return hle(); |
679 |
else |
680 |
return 0; |
681 |
} |
682 |
|
683 |
} // end namespace R3000A |