Rush - это библиотека эмуляции процессора PSX - R3000A, написанная Organic'ом,
то есть мной :) изначально она расчитывалась, как _хардкорно_ оптимизированный
интерпретатор, но за счет открытости интерфейса (я сам не ожидал такого xex), появилась
возможность добавить динамическую рекомпиляцию (сейчас я ищу человека, который смог
бы это сделать). скачать библиотеку пока нельзя.. еще не совсем готова :)
ИНИЦИАЛИЗАЦИЯ БИБЛИОТЕКИ
Rush - динамически подключаемая библиотека, содержащая 2 экспортируемых
функции - RushInit и RushShutdown. для инициализации библиотеки,
используется RushInit, которая возвращает указатель на структуру
процессора R3000A, например:
/* пример инициализации Rush */
R3000A *cpu; /* должен быть глобальным */
main()
{
cpu = RushInit(RUSH_CPU_UNKNOWN, RUSH_HLE_DISABLE);
if(!cpu) SysError("can't initialize RushLibrary");
/* другие действия */
...
return 0;
}
первый параметр RushInit - тип используемой эмуляции:
- RUSH_CPU_INTERPRETER - интерпретатор
- RUSH_CPU_DYNAREC - динамический рекомпилятор
- RUSH_CPU_DEBUGGER - интерпертируемый дебагер
- RUSH_CPU_UNKNOWN - для автоопределения
второй параметр - флаги для эмулятора. по умолчанию их состояние - DISABLE.
флаги могут объединяться, например RUSH_HLE_DISABLE|RUSH_RAND_ENABLE.
можно подумать, что если какая-то опция выключена, то она является дохлым грузом для Rush.
но на самом деле это не так: Rush заменит свои методы на те, в которых эта опция просто удалена,
поэтому никаких потерь в скорости за счет проверок опций просто не существует.
то есть тут работает принцип: хочешь использовать - используй (и тормози :) ).
в последующих версиях Rush могут появиться еще флаги, например TLB или поддержка точек останова...
- RUSH_HLE_ENABLE - разрешает поддержку высокоуровневой эмуляции BIOS и KERNEL
- RUSH_HLE_DISABLE - запрещает поддержку высокоуровневой эмуляции BIOS и KERNEL
- RUSH_RAND_ENABLE - разрешает поддержку эмуляции регистра сопроцессора - RAND
- RUSH_RAND_DISABLE - запрещает поддержку эмуляции регистра сопроцессора - RAND
примечания: если вы используете RUSH_CPU_UNKNOWN, то Rush автоматически определит скорость
вашего процессора и сам выберет тип эмуляции (интерпретатор или динамическую
рекомпиляцию). насчет флагов: для стандартного эмулятора используйте комбинацию RUSH_HLE_ENABLE|RUSH_RAND_DISABLE.
RUSH_CPU_DEBUGGER идеально подходит для разного рода отладчиков. он сохраняет все возможности
интерпретации, при этом полностью (на сколько это возможно) эмулирует железо PSX.
в этом режиме не работают никакие флаги: HLE всегда выключена, а эмуляция RAND и прочих функций (TLB, BP) включена.
позднее он будет описан подробнее.
ИНИЦИАЛИЗАЦИЯ ПРОЦЕССОРА
для инициализации процессора, вызывается метод R3000A - Init:
/* пример инициализации процессора */
R3000A *cpu; /* должен быть глобальным */
main()
{
RUSH_RESULT res;
/* инициализируем Rush и процессор */
cpu = RushInit(RUSH_CPU_UNKNOWN, RUSH_HLE_DISABLE);
if(!cpu) SysError("can't initialize RushLibrary");
res = cpu->Init();
if(res != RUSH_OKAY) SysError("can't initialize CPU");
/* другие действия */
...
return 0;
}
ЗАВЕРШЕНИЕ РАБОТЫ
для завершения работы, надо вначале уничтожить процессор, а потом Rush. делается это
просто:
/* пример работы с Rush */
R3000A *cpu; /* должен быть глобальным */
main()
{
RUSH_RESULT res;
/* инициализируем Rush и процессор */
cpu = RushInit(RUSH_CPU_UNKNOWN, RUSH_HLE_DISABLE);
if(!cpu) SysError("can't initialize RushLibrary");
res = cpu->Init();
if(res != RUSH_OKAY) SysError("can't initialize CPU");
/* другие действия */
...
/* уничтожаем вначале процессор, потом Rush */
cpu->Shutdown();
RushShutdown();
return 0;
}
ПОЛУЧЕНИЕ СВЕДЕНИЙ
чтобы получить необходимые данные, о состоянии эмуляции, используется метод
R3000A - Get, например:
char *name;
/* получить имя используемой библиотеки Rush */
name = (char *)cpu->Get(RUSH_GET_NAME);
параметр Get может принимать несколько значений, также, как и тип
возвращаемых данных:
входной параметр |
тип возвращаемых данных |
описание |
RUSH_GET_NAME
|
(char *)
|
получить имя библиотеки Rush, например "Rush PSX R3000A emulation library"
|
RUSH_GET_VERSION
|
(char *)
|
получить версию библиотеки Rush, например "1.0a"
|
RUSH_GET_CLOCK_COUNTER
|
(unsigned long *)
|
получить указатель на счетчик инструкций (тактов) процессора
|
RUSH_GET_PROGRAM_COUNTER
|
(unsigned long)
|
получить значение счетчика команд на текущий момент (НЕ указатель!)
|
RUSH_GET_INSTRUCTION
|
(unsigned long *)
|
получить указатель на текущую инструкцию процессора (только для интерпретатора)
|
RUSH_GET_CPU_REGISTERS
|
(unsigned long *)
|
получить указатель на 32 регитсра общего назначения (GPR)
|
RUSH_GET_COP0_REGISTERS
|
(unsigned long *)
|
получить указатель на 32 регитсра системного сопроцессора
|
RUSH_GET_HI_REGISTER
|
(unsigned long *)
|
получить указатель на регитср HI
|
RUSH_GET_LO_REGISTER
|
(unsigned long *)
|
получить указатель на регитср LO
|
примечание: параметр Get можно получить только один раз. далее его можно использовать
в программе где угодно. например, получив указатель на счетчик тактов, его можно использовать без
повторного получения. это не относится к RUSH_GET_PROGRAM_COUNTER. на самом деле, значение PC
в Rush не хранит PSX адрес инструкции, поэтому оно должно правильно транслироваться назад в
адресное пространство PSX..
ОПЕРАЦИИ С ПАМЯТЬЮ PSX
для получения прямого доступа к адресному пространству PSX, используются два
метода структуры R3000A - Lock и UnLock:
/* пример прямого доступа к памяти PSX */
main()
{
...
unsigned long *bios;
/* получим указатель на BIOS */
bios = cpu->Lock(RUSH_LOCK_BIOS);
/* операции с BIOS */
...
/* освободим доступ */
cpu->UnLock();
...
}
в принципе, если мы один раз залочили область памяти, то разлочивание пока ни к чему
не приводит. связка LOCK-UNLOCK оставлена для будущего использования (а так бы эти
методы были бы включены в Get). возможные области:
- RUSH_LOCK_RAM - оперативная память
- RUSH_LOCK_BIOS - BIOS (или ROM)
- RUSH_LOCK_PORT - параллельный порт
- RUSH_LOCK_PAD - scratch pad
- RUSH_LOCK_HARDWARE - регистры устройств
примечание: хотя с помощью этих методов можно получить полный доступ к аппаратным регистрам PSX,
делать это не рекомендуется, так как теряется смысл использования аппаратных ловушек на
эти регистры (см. далее). самое лучшее применение этих методов - загрузка файла с BIOS'ом.
УСТАНОВКА ЛОВУШЕК
Rush поддерживает установку ловушек на чтение/запись в регистры устройств
(область памяти 1F801000-1F802FFF). так как регистры могут быть 8-, 16- и 32-битные,
в Rush есть методы для каждого типа ловушек. для этого в структуре R3000A есть шесть
методов - SetHook8, KillHook8, SetHook16, KillHook16 и SetHook32 c KillHook32.
/* пример использования ловушек */
/* функции чтения/записи в регистр статуса GPU */
/* находятся в эмуляторе */
unsigned long GPU_ReadStatus(void)
{
/* используем плагин */
return GPUreadStatus();
}
void GPU_WriteStatus(unsigned long gdata)
{
/* используем плагин */
GPUwriteStatus(gdata);
}
/* функции чтения/записи в регистр CDREG0 */
/* находятся в эмуляторе */
unsigned char CDR_Read0(void)
{
/* тут эмялятор обслуживает доступ к регистру */
...
}
void CDR_Write0(unsigned char gdata)
{
/* тут эмялятор обслуживает доступ к регистру */
...
}
main()
{
RUSH_RESULT res;
...
/* устанавливаем ловушки на регистр статуса GPU */
res = cpu->SetHook32(0x1F801814, GPU_ReadStatus, GPU_WriteStatus);
if(res != RUSH_OKAY) SysError("can't set hook on GPU status");
/* а так ставим ловушки на регистры CDROM */
res = cpu->SetHook8(0x1F801800, CDR_Read0, CDR_Write0);
if(res != RUSH_OKAY) SysError("can't set hook on CDREG0");
/* освобождаем ловушки */
res = cpu->KillHook32(0x1F801814);
if(res != RUSH_OKAY) SysError("can't clear hook on GPU status");
res = cpu->KillHook8(0x1F801800);
if(res != RUSH_OKAY) SysError("can't clear hook on CDREG0");
...
}
вот, на всякий случай, заголовоки SetHookX:
RUSH_RESULT SetHook8 (
unsigned long addr, // адрес регистра
unsigned char (*ReadData)(void), // ловушка на чтение
void (*WriteData)(unsigned char) // ловушка на запись
);
RUSH_RESULT SetHook16 (
unsigned long addr, // адрес регистра
unsigned short (*ReadData)(void), // ловушка на чтение
void (*WriteData)(unsigned short) // ловушка на запись
);
RUSH_RESULT SetHook32 (
unsigned long addr, // адрес регистра
unsigned long (*ReadData)(void), // ловушка на чтение
void (*WriteData)(unsigned long) // ловушка на запись
);
возвращают RUSH_OKAY, если ловушка установлена, или RUSH_ERR, если нет.
ОБРАБОТКА ИСКЛЮЧЕНИЙ И ПРЕРЫВАНИЙ
эмуляция исключений и прерываний основана на том факте, что исключения происходят в
процессоре, поэтому их обработка находится в Rush, а прерывания поступают от внешних устройств.
поэтому обработку прерываний устройств эмулятор должен (если он хочет правильно работать) предоставить
в распоряжение Rush. для этого существуют такие методы:
Exception - вызвать исключение процессора. у этого метода один параметр -
тип исключения. из десятка с лишним возможных исключений у Rush используются только эти:
- RUSH_XCPT_RESET - холодный перезапуск PSX
- RUSH_XCPT_UNKNOWN - приводит к критическому завершению программы
остальные исключения используются внутри Rush.
метод Interrupt должен быть инициализирован эмулятором. этот экспортируемый метод
обычно работает со счетчиком тактов (clock counter). и обслуживает такие вещи, как ROOT COUNTERS, чтение CDROM и VSYNC.
ИНТЕГРАЦИЯ С СИСТЕМОЙ PSEmuPro
Rush может использоваться как расширение к системе плагинов PSEmuPro. для этого в состав Rush
были включены еще 3 функции, стандартные для всех PSEmuPro-плагинов. подробнее про PSEmuPro, читай где-то на этом сайте..
только вот единственное различие между PSEmuPro и Rush в уровнях прослойки. если PSEmuPro принадлежит скорее
к интерфейсу высшего уровня, то уровень Rush находится даже ниже, чем эмулятор.
получается такой пирог: PSEmuPro->эмулятор->Rush. поэтому Rush нельзя назвать CPU-плагином, он скорее всего
расширение...
THAT'S ALL
ну вот, в принципе, и все описание интерфейса библиотеки Rush, версии 1.0. в ближайше время
появится и RushSDK.
назад...