├── .gitignore ├── images └── demo2a.png ├── src ├── CMakeLists.txt ├── core │ ├── vu │ │ ├── vu.cpp │ │ └── vu.h │ ├── ee │ │ ├── disassembler.h │ │ ├── instruction.h │ │ ├── timers.h │ │ ├── executor.h │ │ ├── intc.h │ │ ├── cop0.h │ │ ├── intc.cpp │ │ ├── context.h │ │ ├── cop0.cpp │ │ ├── cop1.h │ │ ├── dmac.h │ │ ├── interpreter.h │ │ ├── timers.cpp │ │ ├── cop1.cpp │ │ ├── context.cpp │ │ └── dmac.cpp │ ├── iop │ │ ├── disassembler.h │ │ ├── cdvd.h │ │ ├── executor.h │ │ ├── instruction.h │ │ ├── sio2.h │ │ ├── intc.h │ │ ├── cop0.cpp │ │ ├── cdvd.cpp │ │ ├── context.h │ │ ├── cop0.h │ │ ├── intc.cpp │ │ ├── timers.h │ │ ├── dmac.h │ │ ├── interpreter.h │ │ ├── sio2.cpp │ │ ├── timers.cpp │ │ ├── context.cpp │ │ ├── dmac.cpp │ │ └── disassembler.cpp │ ├── spu │ │ ├── spu.h │ │ └── spu.cpp │ ├── ipu │ │ ├── ipu.h │ │ └── ipu.cpp │ ├── vif │ │ ├── vif.h │ │ └── vif.cpp │ ├── core.h │ ├── scheduler.h │ ├── core.cpp │ ├── elf_loader.h │ ├── sif │ │ ├── sif.h │ │ └── sif.cpp │ ├── CMakeLists.txt │ ├── system.h │ ├── gif.h │ ├── gs │ │ ├── page.h │ │ └── context.h │ ├── elf_loader.cpp │ ├── scheduler.cpp │ ├── system.cpp │ └── gif.cpp ├── common │ ├── filesystem.h │ ├── bits.cpp │ ├── string.cpp │ ├── CMakeLists.txt │ ├── games_list.h │ ├── memory.h │ ├── string.h │ ├── log.h │ ├── bits.h │ ├── emu_thread.h │ ├── games_list.cpp │ ├── virtual_page_table.h │ ├── types.h │ ├── filesystem.cpp │ ├── emu_thread.cpp │ ├── queue.h │ └── log.cpp └── frontend │ ├── main.cpp │ ├── CMakeLists.txt │ ├── host_interface.h │ ├── imgui │ ├── imgui_impl_sdl2.h │ ├── imgui_impl_opengl3.h │ └── imconfig.h │ ├── debugger.cpp │ └── host_interface.cpp ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /bios 2 | /build 3 | /roms 4 | .* 5 | !/.gitignore -------------------------------------------------------------------------------- /images/demo2a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strayacode/matcha/HEAD/images/demo2a.png -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(common) 2 | add_subdirectory(core) 3 | add_subdirectory(frontend) -------------------------------------------------------------------------------- /src/core/vu/vu.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void VU::Reset() { 4 | data_memory.fill(0); 5 | code_memory.fill(0); 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # matcha - A PS2 Emulator written in C++ 2 | 3 | # Screenshots 4 | demo2a  5 | 6 | ## Building 7 | ```sh 8 | mkdir build && cd build 9 | ``` 10 | 11 | ```sh 12 | cmake .. && cmake --build . 13 | ``` -------------------------------------------------------------------------------- /src/core/ee/disassembler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "core/ee/context.h" 6 | 7 | namespace ee { 8 | 9 | std::string DisassembleInstruction(Instruction inst, u32 pc); 10 | std::string GetRegisterName(int reg); 11 | 12 | } // namespace ee -------------------------------------------------------------------------------- /src/core/iop/disassembler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "common/log.h" 6 | #include "core/iop/instruction.h" 7 | 8 | namespace iop { 9 | 10 | std::string DisassembleInstruction(Instruction inst, u32 pc); 11 | std::string GetRegisterName(int reg); 12 | 13 | } // namespace iop -------------------------------------------------------------------------------- /src/core/spu/spu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | 5 | class SPU { 6 | public: 7 | void Reset(); 8 | 9 | u32 ReadRegister(u32 addr); 10 | void WriteRegister(u32 addr, u32 data); 11 | void RequestInterrupt(); 12 | 13 | private: 14 | u16 master_volume_left = 0; 15 | u16 status = 0; 16 | }; -------------------------------------------------------------------------------- /src/common/filesystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common/types.h" 6 | 7 | namespace common { 8 | 9 | std::vector ScanDirectoryRecursive(const std::string& path, std::vector extensions); 10 | 11 | std::string GetFormattedSize(u64 size); 12 | 13 | } // namespace common -------------------------------------------------------------------------------- /src/core/ipu/ipu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class IPU { 7 | public: 8 | void Reset(); 9 | void SystemReset(); 10 | 11 | void WriteControl(u32 data); 12 | u32 ReadControl(); 13 | void WriteCommand(u32 data); 14 | private: 15 | u32 control; 16 | u32 command; 17 | }; -------------------------------------------------------------------------------- /src/frontend/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "host_interface.h" 3 | 4 | int main() { 5 | std::unique_ptr host_interface = std::make_unique(); 6 | 7 | if (host_interface->initialise()) { 8 | host_interface->run(); 9 | } 10 | 11 | host_interface->shutdown(); 12 | 13 | return 0; 14 | } -------------------------------------------------------------------------------- /src/core/iop/cdvd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | 5 | namespace iop { 6 | 7 | struct CDVD { 8 | void Reset(); 9 | 10 | u32 ReadRegister(u32 addr); 11 | void WriteRegister(u32 addr, u32 value); 12 | void DoSCommand(); 13 | 14 | private: 15 | u8 n_command_status; 16 | 17 | u8 s_command_status; 18 | u8 s_command; 19 | }; 20 | 21 | } // namespace iop -------------------------------------------------------------------------------- /src/common/bits.cpp: -------------------------------------------------------------------------------- 1 | #include "common/bits.h" 2 | 3 | namespace common { 4 | 5 | u32 CountLeadingSignBits(s32 value) { 6 | if (value < 0) { 7 | value = ~value; 8 | } 9 | 10 | // __builtin_clz is undefined for 0 but we can just handle this ourselves 11 | if (value == 0) { 12 | return 32; 13 | } 14 | 15 | return __builtin_clz(value); 16 | } 17 | 18 | } // namespace common -------------------------------------------------------------------------------- /src/core/vif/vif.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class VIF { 7 | public: 8 | void Reset(); 9 | void SystemReset(); 10 | 11 | void WriteStat(u32 data); 12 | void WriteFBRST(u8 data); 13 | void WriteMark(u16 data); 14 | void WriteERR(u8 data); 15 | private: 16 | u8 fbrst; 17 | u32 stat; 18 | u16 mark; 19 | u8 err; 20 | }; -------------------------------------------------------------------------------- /src/core/iop/executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace iop { 4 | 5 | enum class Exception : int { 6 | Interrupt = 0x00, 7 | LoadError = 0x04, 8 | StoreError = 0x05, 9 | Syscall = 0x08, 10 | Break = 0x09, 11 | Reserved = 0x0a, 12 | Overflow = 0x0c, 13 | }; 14 | 15 | struct Executor { 16 | virtual ~Executor() = default; 17 | virtual void Reset() = 0; 18 | virtual void Run(int cycles) = 0; 19 | }; 20 | 21 | } // namespace iop -------------------------------------------------------------------------------- /src/core/iop/instruction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | 5 | namespace iop { 6 | 7 | union Instruction { 8 | struct { 9 | u32 func : 6; 10 | u32 imm5 : 5; 11 | u32 rd : 5; 12 | u32 rt : 5; 13 | u32 rs : 5; 14 | u32 opcode : 6; 15 | }; 16 | 17 | u32 data; 18 | s16 simm; 19 | u16 imm; 20 | u32 offset : 26; 21 | 22 | Instruction(u32 data) : data(data) {}; 23 | Instruction() : data(0) {}; 24 | }; 25 | 26 | } // namespace iop -------------------------------------------------------------------------------- /src/common/string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common/string.h" 4 | 5 | namespace common { 6 | 7 | std::string ToLower(std::string str) { 8 | std::transform(str.begin(), str.end(), str.begin(), [](char c) { 9 | return std::tolower(c); 10 | }); 11 | 12 | return str; 13 | } 14 | 15 | std::string ToUpper(std::string str) { 16 | std::transform(str.begin(), str.end(), str.begin(), [](char c) { 17 | return std::toupper(c); 18 | }); 19 | 20 | return str; 21 | } 22 | 23 | } // namespace common -------------------------------------------------------------------------------- /src/core/vu/vu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "common/log.h" 6 | 7 | class VU { 8 | public: 9 | void Reset(); 10 | 11 | template 12 | void WriteDataMemory(u32 addr, T data) { 13 | *(T*)&data_memory[addr & 0x3FFF] = data; 14 | } 15 | 16 | template 17 | void WriteCodeMemory(u32 addr, T data) { 18 | *(T*)&code_memory[addr & 0x3FFF] = data; 19 | } 20 | 21 | private: 22 | std::array data_memory; 23 | std::array code_memory; 24 | }; -------------------------------------------------------------------------------- /src/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(common 2 | types.h 3 | log.h log.cpp 4 | emu_thread.h emu_thread.cpp 5 | bits.h bits.cpp 6 | queue.h 7 | memory.h virtual_page_table.h 8 | string.h string.cpp 9 | filesystem.h filesystem.cpp 10 | games_list.h games_list.cpp 11 | ) 12 | 13 | set(THREADS_PREFER_PTHREAD_FLAG ON) 14 | find_package(Threads REQUIRED) 15 | target_link_libraries(common PRIVATE Threads::Threads) 16 | 17 | set_target_properties(common PROPERTIES LINKER_LANGUAGE CXX) 18 | target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") -------------------------------------------------------------------------------- /src/common/games_list.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace common { 8 | 9 | class GamesList { 10 | public: 11 | void Initialise(); 12 | 13 | struct Entry { 14 | std::string path; 15 | std::string name; 16 | std::string type; 17 | std::string size; 18 | }; 19 | 20 | using Entries = std::vector; 21 | 22 | Entries& GetEntries() { return entries; } 23 | 24 | private: 25 | void AddEntry(const std::string& path); 26 | 27 | Entries entries; 28 | }; 29 | 30 | } // namespace common -------------------------------------------------------------------------------- /src/common/memory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | 6 | namespace common { 7 | 8 | template 9 | inline T Read(void* data, int offset = 0) { 10 | T return_value; 11 | std::memcpy(&return_value, (u8*)data + offset, sizeof(T)); 12 | return return_value; 13 | } 14 | 15 | template 16 | inline void Write(void* data, T value, int offset = 0) { 17 | std::memcpy((u8*)data + offset, &value, sizeof(T)); 18 | } 19 | 20 | inline bool InRange(u32 start, u32 end, u32 addr) { 21 | return addr >= start && addr < end; 22 | } 23 | 24 | } // namespace common -------------------------------------------------------------------------------- /src/common/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace common { 7 | 8 | std::string ToLower(std::string str); 9 | 10 | std::string ToUpper(std::string str); 11 | 12 | template 13 | std::string Format(const std::string& format, Args ...args) { 14 | int size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; 15 | 16 | std::unique_ptr buffer = std::make_unique(size); 17 | 18 | std::snprintf(buffer.get(), size, format.c_str(), args...); 19 | return std::string(buffer.get(), buffer.get() + size - 1); 20 | } 21 | 22 | } // namespace common -------------------------------------------------------------------------------- /src/core/core.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/emu_thread.h" 5 | #include "core/system.h" 6 | 7 | enum class CoreState { 8 | Running, 9 | Paused, 10 | Idle, 11 | }; 12 | 13 | class Core { 14 | public: 15 | Core(UpdateFunction update_fps); 16 | 17 | void Reset(); 18 | void SetState(CoreState new_state); 19 | CoreState GetState(); 20 | void RunFrame(); 21 | void SetBootParameters(BootMode boot_mode, std::string path = ""); 22 | void Boot(); 23 | 24 | System system; 25 | 26 | private: 27 | CoreState state = CoreState::Idle; 28 | EmuThread emu_thread; 29 | }; -------------------------------------------------------------------------------- /src/core/ee/instruction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | 5 | namespace ee { 6 | 7 | union Instruction { 8 | struct { 9 | u32 func : 6; 10 | u32 imm5 : 5; 11 | u32 rd : 5; 12 | u32 rt : 5; 13 | u32 rs : 5; 14 | u32 opcode : 6; 15 | }; 16 | 17 | struct { 18 | u32 : 6; 19 | u32 fd : 5; 20 | u32 fs : 5; 21 | u32 ft : 5; 22 | u32 : 11; 23 | }; 24 | 25 | u32 data; 26 | s16 simm; 27 | u16 imm; 28 | u32 offset : 26; 29 | 30 | Instruction(u32 data) : data(data) {}; 31 | Instruction() : data(0) {}; 32 | }; 33 | 34 | } // namespace ee -------------------------------------------------------------------------------- /src/common/log.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace common { 5 | 6 | void Info(const char* format, ...); 7 | void Debug(const char* format, ...); 8 | void Warn(const char* format, ...); 9 | void Error(const char* format, ...); 10 | void Log(const char* format, ...); 11 | void LogNoNewline(const char* format, ...); 12 | 13 | } // namespace common 14 | 15 | #define LOG_TODO(fmt, ...) do {\ 16 | fprintf(stderr, "%s:%d: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__);\ 17 | exit(1);\ 18 | } while (0) 19 | 20 | #define LOG_TODO_NO_ARGS(fmt, ...) do {\ 21 | fprintf(stderr, "%s:%d: " fmt "\n", __FILE__, __LINE__);\ 22 | exit(1);\ 23 | } while (0) -------------------------------------------------------------------------------- /src/common/bits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/memory.h" 5 | 6 | namespace common { 7 | 8 | template 9 | T SignExtend(T value) { 10 | struct { 11 | T data : size; 12 | } s; 13 | 14 | s.data = value; 15 | return s.data; 16 | } 17 | 18 | template 19 | inline To BitCast(From& value) { 20 | static_assert(sizeof(From) == sizeof(To), "BitCast types must be same size"); 21 | return common::Read(&value); 22 | } 23 | 24 | // counts the number of leading bits that are the same value 25 | // as the sign bit 26 | u32 CountLeadingSignBits(s32 value); 27 | 28 | } // namespace common -------------------------------------------------------------------------------- /src/core/ipu/ipu.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void IPU::Reset() { 4 | control = 0; 5 | command = 0; 6 | } 7 | 8 | void IPU::SystemReset() { 9 | common::Log("[IPU] system reset"); 10 | } 11 | 12 | void IPU::WriteControl(u32 data) { 13 | if (data & 0x40000000) { 14 | SystemReset(); 15 | } 16 | 17 | if (data != 0x40000000) { 18 | common::Error("handle"); 19 | } 20 | 21 | common::Log("[IPU] write control %08x", data); 22 | control = data; 23 | } 24 | 25 | u32 IPU::ReadControl() { 26 | return control; 27 | } 28 | 29 | void IPU::WriteCommand(u32 data) { 30 | common::Log("[IPU] write command %08x", data); 31 | 32 | command = data; 33 | } -------------------------------------------------------------------------------- /src/core/ee/timers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/log.h" 5 | #include "core/ee/intc.h" 6 | 7 | namespace ee { 8 | 9 | class Timers { 10 | public: 11 | Timers(INTC& intc); 12 | 13 | void Reset(); 14 | u32 ReadRegister(u32 addr); 15 | void WriteRegister(u32 addr, u32 data); 16 | 17 | u32 ReadChannel(u32 addr); 18 | 19 | void Increment(int index); 20 | void Run(int cycles); 21 | 22 | private: 23 | struct Channel { 24 | u32 counter; 25 | u16 control; 26 | u16 compare; 27 | u16 hold; 28 | int cycles; 29 | int cycles_per_tick; 30 | }; 31 | 32 | Channel channels[4]; 33 | INTC& intc; 34 | }; 35 | 36 | } // namespace ee -------------------------------------------------------------------------------- /src/core/ee/executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ee { 4 | 5 | enum class ExceptionType : int { 6 | Interrupt = 0, 7 | TLBModified = 1, 8 | TLBRefillInstruction = 2, 9 | TLBRefillStore = 3, 10 | AddressErrorInstruction = 4, 11 | AddressErrorStore = 5, 12 | BusErrorInstruction = 6, 13 | BusErrorStore = 7, 14 | Syscall = 8, 15 | Break = 9, 16 | Reserved = 10, 17 | CoprocessorUnusable = 11, 18 | Overflow = 12, 19 | Trap = 13, 20 | Reset = 14, 21 | NMI = 15, 22 | PerformanceCounter = 16, 23 | Debug = 18, 24 | }; 25 | 26 | struct Executor { 27 | virtual ~Executor() = default; 28 | virtual void Reset() = 0; 29 | virtual void Run(int cycles) = 0; 30 | }; 31 | 32 | } // namespace ee -------------------------------------------------------------------------------- /src/core/spu/spu.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/spu/spu.h" 3 | 4 | void SPU::Reset() { 5 | master_volume_left = 0; 6 | status = 0; 7 | } 8 | 9 | u32 SPU::ReadRegister(u32 addr) { 10 | u32 return_value = 0; 11 | 12 | switch (addr) { 13 | case 0x1F900744: 14 | // status 15 | return_value = status; 16 | status &= ~0x80; 17 | break; 18 | default: 19 | common::Log("[SPU] handle read %08x", addr); 20 | } 21 | 22 | return return_value; 23 | } 24 | 25 | void SPU::WriteRegister(u32 addr, u32 data) { 26 | switch (addr) { 27 | case 0x1F900744: 28 | // status 29 | break; 30 | default: 31 | common::Log("[SPU] handle write %08x = %08x", addr, data); 32 | } 33 | } 34 | 35 | void SPU::RequestInterrupt() { 36 | status |= 0x80; 37 | } -------------------------------------------------------------------------------- /src/core/vif/vif.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void VIF::Reset() { 4 | fbrst = 0; 5 | stat = 0; 6 | mark = 0; 7 | err = 0; 8 | } 9 | 10 | void VIF::SystemReset() { 11 | common::Log("[VIF] system reset"); 12 | } 13 | 14 | void VIF::WriteStat(u32 data) { 15 | if (data) { 16 | common::Error("handle"); 17 | } 18 | 19 | common::Log("[VIF] write stat %08x", data); 20 | stat = data; 21 | } 22 | 23 | void VIF::WriteFBRST(u8 data) { 24 | common::Log("[VIF] write fbrst %02x", data); 25 | if (data & 0x1) { 26 | SystemReset(); 27 | } 28 | 29 | fbrst = data; 30 | } 31 | 32 | void VIF::WriteMark(u16 data) { 33 | common::Log("[VIF] write mark %04x", data); 34 | stat &= ~(1 << 6); 35 | mark = data; 36 | } 37 | 38 | void VIF::WriteERR(u8 data) { 39 | common::Log("[VIF] write err %02x", data); 40 | err = data; 41 | } -------------------------------------------------------------------------------- /src/core/scheduler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "common/types.h" 7 | #include "common/log.h" 8 | 9 | enum EventId { 10 | NoneEvent, 11 | TimerEvent, 12 | }; 13 | 14 | struct Event { 15 | u64 start_time; 16 | int id; 17 | std::function callback; 18 | }; 19 | 20 | class Scheduler { 21 | public: 22 | void Reset(); 23 | void Tick(int cycles); 24 | u64 GetCurrentTime(); 25 | u64 GetEventTime(); 26 | void ResetCurrentTime(); 27 | void RunEvents(); 28 | void Add(u64 delay, std::function callback); 29 | void AddWithId(u64 delay, int id, std::function callback); 30 | void Cancel(int id); 31 | int CalculateEventIndex(Event& new_event); 32 | void SchedulerDebug(); 33 | 34 | private: 35 | u64 current_time; 36 | std::vector events; 37 | }; -------------------------------------------------------------------------------- /src/common/emu_thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using RunFunction = std::function; 10 | using UpdateFunction = std::function; 11 | 12 | class EmuThread { 13 | public: 14 | EmuThread(RunFunction run_frame, UpdateFunction update_fps); 15 | ~EmuThread(); 16 | void Start(); 17 | void Reset(); 18 | void Run(); 19 | void Stop(); 20 | auto IsActive() -> bool; 21 | auto GetFPS() -> int; 22 | void ToggleFramelimiter(); 23 | 24 | std::thread thread; 25 | 26 | using frame = std::chrono::duration>; 27 | 28 | RunFunction run_frame; 29 | UpdateFunction update_fps; 30 | 31 | private: 32 | int frames = 0; 33 | bool running = false; 34 | bool framelimiter = false; 35 | 36 | static constexpr int update_interval = 1000; 37 | }; -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.1) 2 | 3 | project(matcha VERSION 0.0.1 LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 9 | 10 | option(LTO "Enable link time optimisations" OFF) 11 | 12 | add_compile_options( 13 | -Wall 14 | -Wextra 15 | -Wno-missing-braces 16 | ) 17 | 18 | if(NOT CMAKE_BUILD_TYPE) 19 | message(STATUS "No build type specified, defaulting to Release") 20 | set(CMAKE_BUILD_TYPE "Release") 21 | endif() 22 | 23 | if(CMAKE_BUILD_TYPE MATCHES "Debug") 24 | add_definitions(-D_DEBUG) 25 | endif() 26 | 27 | if(CMAKE_BUILD_TYPE MATCHES "Release") 28 | message(STATUS "Using -Ofast") 29 | add_compile_options(-Ofast) 30 | endif() 31 | 32 | if(LTO) 33 | message(STATUS "Using link time optimisations") 34 | add_compile_options(-flto) 35 | endif() 36 | 37 | add_subdirectory(src) 38 | -------------------------------------------------------------------------------- /src/core/core.cpp: -------------------------------------------------------------------------------- 1 | #include "core/core.h" 2 | 3 | Core::Core(UpdateFunction update_fps) : emu_thread([this]() { 4 | RunFrame(); 5 | }, update_fps) {} 6 | 7 | void Core::Reset() { 8 | system.Reset(); 9 | } 10 | 11 | void Core::SetState(CoreState new_state) { 12 | switch (new_state) { 13 | case CoreState::Running: 14 | emu_thread.Start(); 15 | break; 16 | case CoreState::Paused: 17 | case CoreState::Idle: 18 | emu_thread.Stop(); 19 | break; 20 | } 21 | 22 | state = new_state; 23 | } 24 | 25 | CoreState Core::GetState() { 26 | return state; 27 | } 28 | 29 | void Core::RunFrame() { 30 | system.RunFrame(); 31 | } 32 | 33 | void Core::SetBootParameters(BootMode boot_mode, std::string path) { 34 | system.SetBootParameters(boot_mode, path); 35 | } 36 | 37 | void Core::Boot() { 38 | SetState(CoreState::Idle); 39 | system.Reset(); 40 | SetState(CoreState::Running); 41 | } -------------------------------------------------------------------------------- /src/core/iop/sio2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "common/queue.h" 6 | #include "core/iop/intc.h" 7 | 8 | namespace iop { 9 | 10 | struct SIO2 { 11 | SIO2(INTC& intc); 12 | 13 | void Reset(); 14 | 15 | u32 ReadRegister(u32 addr); 16 | void WriteRegister(u32 addr, u32 value); 17 | u8 ReadDMA(); 18 | void WriteDMA(u8 data); 19 | 20 | private: 21 | void upload_command(u8 data); 22 | 23 | enum class PeripheralType { 24 | Controller, 25 | Multitap, 26 | Infrared, 27 | Memcard, 28 | None, 29 | }; 30 | 31 | u32 control; 32 | std::array send1; 33 | std::array send2; 34 | std::array send3; 35 | common::Queue fifo; 36 | INTC& intc; 37 | 38 | u8 m_command_length{0}; 39 | u8 m_command_index{0}; 40 | PeripheralType m_peripheral_type{PeripheralType::None}; 41 | }; 42 | 43 | } // namespace iop -------------------------------------------------------------------------------- /src/core/ee/intc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/log.h" 5 | 6 | namespace ee { 7 | 8 | struct Context; 9 | 10 | enum class InterruptSource : int { 11 | GS = 0, 12 | SBUS = 1, 13 | VBlankStart = 2, 14 | VBlankFinish = 3, 15 | VIF0 = 4, 16 | VIF1 = 5, 17 | VU0 = 6, 18 | VU1 = 7, 19 | IPU = 8, 20 | Timer0 = 9, 21 | Timer1 = 10, 22 | Timer2 = 11, 23 | Timer3 = 12, 24 | SFIFO = 13, 25 | VUOWatchdog = 14, 26 | }; 27 | 28 | // the intc deals with interrupt requests 29 | // and can send interrupts to the ee core via the int0 signal 30 | class INTC { 31 | public: 32 | INTC(Context& ee); 33 | 34 | void Reset(); 35 | 36 | u16 ReadMask(); 37 | u16 ReadStat(); 38 | 39 | void WriteMask(u16 data); 40 | void WriteStat(u16 data); 41 | 42 | void CheckInterrupts(); 43 | void RequestInterrupt(InterruptSource interrupt); 44 | private: 45 | u16 mask; 46 | u16 stat; 47 | 48 | Context& ee; 49 | }; 50 | 51 | } // namespace ee -------------------------------------------------------------------------------- /src/core/iop/intc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | 5 | namespace iop { 6 | 7 | struct Context; 8 | 9 | enum class InterruptSource : u32 { 10 | VBlankStart = 0, 11 | GPU = 1, 12 | CDVD = 2, 13 | DMA = 3, 14 | Timer0 = 4, 15 | Timer1 = 5, 16 | Timer2 = 6, 17 | SIO0 = 7, 18 | SIO1 = 8, 19 | SPU2 = 9, 20 | PIO = 10, 21 | VBlankFinish = 11, 22 | DVD = 12, 23 | PCMCIA = 13, 24 | Timer3 = 14, 25 | Timer4 = 15, 26 | Timer5 = 16, 27 | SIO2 = 17, 28 | USB = 22, 29 | }; 30 | 31 | class INTC { 32 | public: 33 | INTC(Context& ctx); 34 | 35 | void Reset(); 36 | 37 | u32 ReadRegister(int offset); 38 | void WriteRegister(int offset, u32 data); 39 | void RequestInterrupt(InterruptSource source); 40 | void UpdateInterrupts(); 41 | 42 | private: 43 | u32 interrupt_mask; 44 | u32 interrupt_status; 45 | u8 interrupt_control; 46 | 47 | static constexpr int WRITE_MASK = (1 << 26) - 1; 48 | 49 | Context& ctx; 50 | }; 51 | 52 | } // namespace iop -------------------------------------------------------------------------------- /src/core/ee/cop0.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "common/log.h" 6 | 7 | namespace ee { 8 | 9 | class COP0 { 10 | public: 11 | void Reset(); 12 | 13 | u32 GetReg(int reg); 14 | void SetReg(int reg, u32 data); 15 | 16 | void CountUp(); 17 | 18 | union Cause { 19 | struct { 20 | u32 : 2; 21 | u8 exception : 5; 22 | u32 : 3; 23 | bool int0_pending : 1; 24 | bool int1_pending : 1; 25 | u32 : 3; 26 | bool timer_pending : 1; 27 | u8 error : 3; 28 | u32 : 9; 29 | u8 cu : 2; 30 | bool bd2 : 1; 31 | bool bd : 1; 32 | }; 33 | 34 | u32 data; 35 | }; 36 | 37 | // union EntryLo { 38 | // struct { 39 | 40 | // }; 41 | // }; 42 | 43 | u32 index; 44 | Cause cause; 45 | std::array gpr; 46 | 47 | private: 48 | // structure of a tlb entry 49 | struct Entry { 50 | 51 | }; 52 | }; 53 | 54 | } // namespace ee -------------------------------------------------------------------------------- /src/core/elf_loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common/types.h" 6 | #include "common/log.h" 7 | 8 | struct System; 9 | 10 | class ELFLoader { 11 | public: 12 | ELFLoader(System& system); 13 | ~ELFLoader(); 14 | void Load(); 15 | void LoadHeader(); 16 | void SetPath(std::string elf_path); 17 | 18 | private: 19 | struct ELFHeader { 20 | u16 type; 21 | u16 machine; 22 | u32 version; 23 | u32 entry; 24 | u32 phoff; 25 | u32 shoff; 26 | u32 flags; 27 | u16 ehsize; 28 | u16 phentsize; 29 | u16 phnum; 30 | u16 shentsize; 31 | u16 shnum; 32 | u16 shstrndx; 33 | }; 34 | 35 | struct ProgramHeader { 36 | u32 type; 37 | u32 offset; 38 | u32 vaddr; 39 | u32 paddr; 40 | u32 filesz; 41 | u32 memsz; 42 | u32 flags; 43 | u32 align; 44 | }; 45 | 46 | std::string path; 47 | ELFHeader header; 48 | 49 | u8* elf = nullptr; 50 | System& system; 51 | int size = 0; 52 | }; -------------------------------------------------------------------------------- /src/core/iop/cop0.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/iop/cop0.h" 3 | 4 | namespace iop { 5 | 6 | void COP0::Reset() { 7 | status.data = 0; 8 | cause.data = 0; 9 | epc = 0; 10 | prid = 0x1f; 11 | } 12 | 13 | u32 COP0::GetReg(int reg) { 14 | switch (reg) { 15 | case 12: 16 | return status.data; 17 | case 13: 18 | return cause.data; 19 | case 14: 20 | return epc; 21 | case 15: 22 | return prid; 23 | default: 24 | common::Error("[iop::COP0] handle read r%d", reg); 25 | } 26 | 27 | return 0; 28 | } 29 | 30 | void COP0::SetReg(int reg, u32 value) { 31 | switch (reg) { 32 | case 3: 33 | case 5: 34 | case 6: 35 | case 7: 36 | case 9: 37 | case 11: 38 | break; 39 | case 12: 40 | status.data = value; 41 | break; 42 | case 13: 43 | common::Log("[iop::COP0] cause write %08x", value); 44 | break; 45 | case 14: 46 | epc = value; 47 | break; 48 | default: 49 | common::Error("[iop::COP0] handle write r%d = %08x", reg, value); 50 | } 51 | } 52 | 53 | } // namespace iop -------------------------------------------------------------------------------- /src/common/games_list.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "common/games_list.h" 5 | #include "common/filesystem.h" 6 | #include "common/string.h" 7 | 8 | namespace common { 9 | 10 | void GamesList::Initialise() { 11 | std::vector extensions = {".elf", ".iso", ".irx"}; 12 | std::vector filepaths = ScanDirectoryRecursive("../roms", extensions); 13 | for (const std::string& path : filepaths) { 14 | AddEntry(path); 15 | } 16 | 17 | std::sort(entries.begin(), entries.end(), [](Entry a, Entry b) { 18 | return common::ToLower(a.name).compare(common::ToLower(b.name)) < 0; 19 | }); 20 | } 21 | 22 | void GamesList::AddEntry(const std::string& path) { 23 | Entry entry; 24 | entry.path = path; 25 | 26 | std::ifstream file(path, std::ios::binary); 27 | 28 | file.unsetf(std::ios::skipws); 29 | file.seekg(0, std::ios::end); 30 | 31 | entry.size = common::GetFormattedSize(file.tellg()); 32 | 33 | entry.name = std::filesystem::path(path).stem(); 34 | entry.type = common::ToUpper(std::string(std::filesystem::path(path).extension()).substr(1)); 35 | entries.push_back(entry); 36 | } 37 | 38 | } // namespace common -------------------------------------------------------------------------------- /src/core/sif/sif.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "common/log.h" 6 | #include "common/queue.h" 7 | 8 | class SIF { 9 | public: 10 | void Reset(); 11 | 12 | void WriteEEControl(u32 data); 13 | void WriteIOPControl(u32 data); 14 | void WriteBD6(u32 data); 15 | void WriteMSCOM(u32 data); 16 | void WriteSMCOM(u32 data); 17 | void SetMSFLAG(u32 data); 18 | void SetSMFLAG(u32 data); 19 | void ResetMSFLAG(u32 data); 20 | void ResetSMFLAG(u32 data); 21 | 22 | u32 ReadMSFLAG(); 23 | u32 ReadMSCOM(); 24 | u32 ReadSMFLAG(); 25 | u32 ReadSMCOM(); 26 | u32 ReadControl(); 27 | 28 | // no clue what this is for 29 | u32 control; 30 | u32 bd6; 31 | 32 | // only writeable by the ee 33 | u32 mscom; 34 | 35 | // only writeable by the iop 36 | u32 smcom; 37 | 38 | u32 msflag; 39 | u32 smflag; 40 | 41 | // TODO: do more research into sif dmas and sif fifo 42 | std::queue sif0_fifo; 43 | std::queue sif1_fifo; 44 | 45 | u32 ReadSIF0FIFO(); 46 | u32 ReadSIF1FIFO(); 47 | void write_sif0_fifo(u32 data); 48 | void write_sif1_fifo(u128 data); 49 | int GetSIF0FIFOSize(); 50 | int GetSIF1FIFOSize(); 51 | }; -------------------------------------------------------------------------------- /src/common/virtual_page_table.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "common/log.h" 6 | 7 | namespace common { 8 | 9 | struct VirtualPageTable { 10 | void Reset() { 11 | page_table.fill(nullptr); 12 | } 13 | 14 | void Map(u8* data, VirtualAddress base, u32 size, u32 mask) { 15 | for (u32 offset = 0; offset < size; offset += PAGE_SIZE) { 16 | VirtualAddress vaddr = base + offset; 17 | int index = vaddr >> PAGE_BITS; 18 | page_table[index] = &data[vaddr & mask]; 19 | } 20 | } 21 | 22 | template 23 | T* Lookup(VirtualAddress vaddr) { 24 | int index = vaddr >> PAGE_BITS; 25 | if (page_table[index] == nullptr) { 26 | return nullptr; 27 | } 28 | 29 | int offset = vaddr & PAGE_MASK; 30 | return reinterpret_cast(page_table[index] + offset); 31 | } 32 | 33 | private: 34 | static constexpr int PAGE_BITS = 12; 35 | static constexpr int PAGE_SIZE = 1 << PAGE_BITS; 36 | static constexpr u32 PAGE_MASK = PAGE_SIZE - 1; 37 | static constexpr int PAGE_COUNT = 1 << (32 - PAGE_BITS); 38 | 39 | std::array page_table; 40 | }; 41 | 42 | } // namespace common -------------------------------------------------------------------------------- /src/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(core 2 | core.h core.cpp 3 | system.h system.cpp 4 | scheduler.h scheduler.cpp 5 | 6 | ee/context.h ee/context.cpp 7 | ee/cop0.h ee/cop0.cpp 8 | ee/cop1.h ee/cop1.cpp 9 | ee/disassembler.h ee/disassembler.cpp 10 | ee/interpreter.h ee/interpreter.cpp 11 | ee/decoder.h ee/executor.h 12 | ee/instruction.h 13 | ee/intc.h ee/intc.cpp 14 | ee/timers.h ee/timers.cpp 15 | ee/dmac.h ee/dmac.cpp 16 | 17 | iop/context.h iop/context.cpp 18 | iop/interpreter.h iop/interpreter.cpp 19 | iop/disassembler.h iop/disassembler.cpp 20 | iop/cop0.h iop/cop0.cpp 21 | iop/dmac.h iop/dmac.cpp 22 | iop/intc.h iop/intc.cpp 23 | iop/timers.h iop/timers.cpp 24 | iop/cdvd.h iop/cdvd.cpp 25 | iop/sio2.h iop/sio2.cpp 26 | iop/executor.h 27 | iop/instruction.h 28 | 29 | gif.h gif.cpp 30 | 31 | gs/context.h gs/context.cpp 32 | gs/page.h 33 | 34 | vu/vu.h vu/vu.cpp 35 | 36 | vif/vif.h vif/vif.cpp 37 | 38 | ipu/ipu.h ipu/ipu.cpp 39 | 40 | sif/sif.h sif/sif.cpp 41 | 42 | elf_loader.h elf_loader.cpp 43 | 44 | spu/spu.h spu/spu.cpp 45 | ) 46 | 47 | include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") 48 | target_link_libraries(core PRIVATE common) -------------------------------------------------------------------------------- /src/core/iop/cdvd.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/iop/cdvd.h" 3 | 4 | namespace iop { 5 | 6 | void CDVD::Reset() { 7 | // set bit 6 to 1 (drive is ready) 8 | n_command_status = 0x40; 9 | 10 | // set bit 6 to 1 (no data available) 11 | s_command_status = 0x40; 12 | 13 | s_command = 0; 14 | } 15 | 16 | u32 CDVD::ReadRegister(u32 addr) { 17 | switch (addr) { 18 | case 0x1f402005: 19 | return n_command_status; 20 | case 0x1f402016: 21 | return s_command; 22 | case 0x1f402017: 23 | return s_command_status; 24 | default: 25 | common::Error("[iop::CDVD] handle read %08x", addr); 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | void CDVD::WriteRegister(u32 addr, u32 value) { 32 | switch (addr) { 33 | case 0x1f402016: 34 | s_command = value; 35 | DoSCommand(); 36 | break; 37 | default: 38 | common::Error("[iop::CDVD] handle write %08x = %08x", addr, value); 39 | } 40 | } 41 | 42 | void CDVD::DoSCommand() { 43 | // TODO: handle s command results 44 | switch (s_command) { 45 | case 0x15: 46 | common::Log("[iop::CDVD] ForbidDVD"); 47 | break; 48 | default: 49 | common::Error("[iop::CDVD] handle s command %02x", s_command); 50 | } 51 | } 52 | 53 | } // namespace iop -------------------------------------------------------------------------------- /src/frontend/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCES 2 | imgui/imconfig.h 3 | imgui/imgui.cpp 4 | imgui/imgui.h 5 | imgui/imfilebrowser.h 6 | imgui/imgui_demo.cpp 7 | imgui/imgui_draw.cpp 8 | imgui/imgui_internal.h 9 | imgui/imgui_widgets.cpp 10 | imgui/imgui_tables.cpp 11 | imgui/imstb_rectpack.h 12 | imgui/imstb_textedit.h 13 | imgui/imstb_truetype.h 14 | imgui/imgui_impl_opengl3.cpp 15 | imgui/imgui_impl_opengl3.h 16 | imgui/imgui_impl_sdl2.cpp 17 | imgui/imgui_impl_sdl2.h 18 | main.cpp 19 | host_interface.cpp 20 | debugger.cpp 21 | ) 22 | 23 | add_executable(matcha ${SOURCES}) 24 | 25 | find_package(SDL2 REQUIRED) 26 | 27 | set(OpenGL_GL_PREFERENCE GLVND) 28 | find_package(OpenGL REQUIRED) 29 | 30 | include_directories(${OpenGL_INCLUDE_DIRS}) 31 | link_directories(${OpenGL_LIBRARY_DIRS}) 32 | add_definitions(${OpenGL_DEFINITIONS}) 33 | if(NOT OPENGL_FOUND) 34 | message(ERROR " OPENGL not found!") 35 | endif(NOT OPENGL_FOUND) 36 | target_link_libraries(matcha ${OPENGL_LIBRARIES}) 37 | 38 | include_directories(imgui ${SDL2_INCLUDE_DIRS}) 39 | target_link_libraries(matcha core common ${SDL2_LIBRARIES}) 40 | 41 | find_package(Threads REQUIRED) 42 | 43 | if (CMAKE_SYSTEM_NAME STREQUAL Linux) 44 | find_package(X11 REQUIRED) 45 | endif() 46 | 47 | target_link_libraries(matcha ${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) -------------------------------------------------------------------------------- /src/core/iop/context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "common/virtual_page_table.h" 6 | #include "core/iop/cop0.h" 7 | #include "core/iop/cdvd.h" 8 | #include "core/iop/sio2.h" 9 | #include "core/iop/dmac.h" 10 | #include "core/iop/timers.h" 11 | #include "core/iop/intc.h" 12 | #include "core/iop/interpreter.h" 13 | 14 | struct System; 15 | 16 | namespace iop { 17 | 18 | struct Context { 19 | Context(System& system); 20 | 21 | void Reset(); 22 | void Run(int cycles); 23 | 24 | u32 GetReg(int reg) { 25 | return gpr[reg]; 26 | } 27 | 28 | void SetReg(int reg, u32 value) { 29 | if (reg) { 30 | gpr[reg] = value; 31 | } 32 | } 33 | 34 | template 35 | T Read(VirtualAddress vaddr); 36 | 37 | template 38 | void Write(VirtualAddress vaddr, T value); 39 | 40 | void RaiseInterrupt(bool value); 41 | 42 | std::array gpr; 43 | u32 pc; 44 | u32 npc; 45 | u32 hi; 46 | u32 lo; 47 | 48 | COP0 cop0; 49 | CDVD cdvd; 50 | DMAC dmac; 51 | INTC intc; 52 | Timers timers; 53 | SIO2 sio2; 54 | System& system; 55 | 56 | private: 57 | u32 ReadIO(u32 paddr); 58 | void WriteIO(u32 paddr, u32 value); 59 | 60 | common::VirtualPageTable vtlb; 61 | Interpreter interpreter; 62 | }; 63 | 64 | } // namespace iop -------------------------------------------------------------------------------- /src/common/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using u8 = std::uint8_t; 6 | using s8 = std::int8_t; 7 | using u16 = std::uint16_t; 8 | using s16 = std::int16_t; 9 | using u32 = std::uint32_t; 10 | using s32 = std::int32_t; 11 | using u64 = std::uint64_t; 12 | using s64 = std::int64_t; 13 | using f32 = float; 14 | using f64 = double; 15 | using VirtualAddress = u32; 16 | 17 | union u128 { 18 | struct { 19 | u64 lo; 20 | u64 hi; 21 | }; 22 | 23 | u64 ud[2]; 24 | u32 uw[4]; 25 | 26 | u128() { 27 | lo = 0; 28 | hi = 0; 29 | } 30 | 31 | u128 inline operator |(u128 value) { 32 | u128 data; 33 | data.lo = lo | value.lo; 34 | data.hi = hi | value.hi; 35 | return data; 36 | } 37 | 38 | u128 inline operator &(u128 value) { 39 | u128 data; 40 | data.lo = lo & value.lo; 41 | data.hi = hi & value.hi; 42 | return data; 43 | } 44 | 45 | u128 inline operator ^(u128 value) { 46 | u128 data; 47 | data.lo = lo ^ value.lo; 48 | data.hi = hi ^ value.hi; 49 | return data; 50 | } 51 | 52 | u128 inline operator ~() { 53 | u128 data; 54 | data.lo = ~lo; 55 | data.hi = ~hi; 56 | return data; 57 | } 58 | 59 | u128& operator=(const int value) { 60 | uw[0] = value; 61 | return *this; 62 | } 63 | }; -------------------------------------------------------------------------------- /src/core/iop/cop0.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | 6 | namespace iop { 7 | 8 | struct COP0 { 9 | void Reset(); 10 | 11 | u32 GetReg(int reg); 12 | void SetReg(int reg, u32 value); 13 | 14 | union Status { 15 | struct { 16 | bool iec : 1; 17 | bool kuc : 1; 18 | bool iep : 1; 19 | bool kup : 1; 20 | bool ieo : 1; 21 | bool kuo : 1; 22 | u32 : 2; 23 | u32 im : 8; 24 | bool isc : 1; 25 | bool swc : 1; 26 | bool pz : 1; 27 | bool cm : 1; 28 | bool pe : 1; 29 | bool ts : 1; 30 | bool bev : 1; 31 | u32 : 2; 32 | bool re : 1; 33 | u32 : 2; 34 | bool cu0 : 1; 35 | bool cu1 : 1; 36 | bool cu2 : 1; 37 | bool cu3 : 1; 38 | }; 39 | 40 | u32 data; 41 | }; 42 | 43 | union Cause { 44 | struct { 45 | u32 : 2; 46 | u8 excode : 5; 47 | u32 : 1; 48 | u32 ip : 8; 49 | u32 : 12; 50 | u32 ce : 2; 51 | u32 : 1; 52 | bool bd : 1; 53 | }; 54 | 55 | u32 data; 56 | }; 57 | 58 | Status status; 59 | Cause cause; 60 | u32 epc; 61 | u32 prid; 62 | }; 63 | 64 | } // namespace iop -------------------------------------------------------------------------------- /src/core/ee/intc.cpp: -------------------------------------------------------------------------------- 1 | #include "core/ee/intc.h" 2 | #include "core/ee/context.h" 3 | 4 | namespace ee { 5 | 6 | INTC::INTC(Context& ee) : ee(ee) {} 7 | 8 | void INTC::Reset() { 9 | mask = 0; 10 | stat = 0; 11 | } 12 | 13 | u16 INTC::ReadMask() { 14 | common::Log("[ee::INTC] read mask %04x", mask); 15 | return mask; 16 | } 17 | 18 | // a bit set to 1 in stat means 19 | // an irq was raised 20 | u16 INTC::ReadStat() { 21 | common::Log("[ee::INTC] read stat %04x", stat); 22 | return stat; 23 | } 24 | 25 | // writing 1 to mask reverses a bit 26 | // while writing 0 has no effect 27 | void INTC::WriteMask(u16 data) { 28 | common::Log("[ee::INTC] write mask %04x", data); 29 | mask ^= (data & 0x7FFF); 30 | CheckInterrupts(); 31 | } 32 | 33 | // writing the bit 1 to stat clears an interrupt 34 | // while writing 0 has no effect 35 | void INTC::WriteStat(u16 data) { 36 | common::Log("[ee::INTC] write stat %04x", data); 37 | stat &= ~(data & 0x7FFF); 38 | CheckInterrupts(); 39 | } 40 | 41 | // when stat & mask is true, an int0 signal is assert into Cause.10. When Status.10 is true, an interrupt occurs and the ee jumps to 0x80000200 42 | void INTC::CheckInterrupts() { 43 | ee.RaiseInterrupt(0, stat & mask); 44 | } 45 | 46 | void INTC::RequestInterrupt(InterruptSource interrupt) { 47 | stat |= (1 << static_cast(interrupt)); 48 | CheckInterrupts(); 49 | } 50 | 51 | } // namespace ee -------------------------------------------------------------------------------- /src/core/system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common/log.h" 6 | #include "core/ee/context.h" 7 | #include "core/scheduler.h" 8 | #include "core/gif.h" 9 | #include "core/gs/context.h" 10 | #include "core/vu/vu.h" 11 | #include "core/vif/vif.h" 12 | #include "core/ipu/ipu.h" 13 | #include "core/sif/sif.h" 14 | #include "core/iop/context.h" 15 | #include "core/elf_loader.h" 16 | #include "core/spu/spu.h" 17 | 18 | enum class BootMode { 19 | Fast, 20 | BIOS, 21 | }; 22 | 23 | struct System { 24 | System(); 25 | 26 | void Reset(); 27 | void RunFrame(); 28 | void SingleStep(); 29 | void VBlankStart(); 30 | void VBlankFinish(); 31 | void SetBootParameters(BootMode boot_mode, std::string path); 32 | void LoadBIOS(); 33 | 34 | Scheduler scheduler; 35 | 36 | ee::Context ee; 37 | iop::Context iop; 38 | gs::Context gs; 39 | 40 | GIF gif; 41 | VU vu0; 42 | VU vu1; 43 | VIF vif0; 44 | VIF vif1; 45 | IPU ipu; 46 | SIF sif; 47 | ELFLoader elf_loader; 48 | 49 | // 2 spu cores 50 | SPU spu; 51 | SPU spu2; 52 | 53 | // shared between ee and iop 54 | std::unique_ptr> bios; 55 | std::unique_ptr> iop_ram; 56 | 57 | std::function VBlankStartEvent; 58 | std::function VBlankFinishEvent; 59 | 60 | BootMode boot_mode; 61 | bool fastboot_done; 62 | }; -------------------------------------------------------------------------------- /src/core/gif.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/log.h" 5 | #include "common/queue.h" 6 | #include "core/gs/context.h" 7 | 8 | // the gif, also known as the graphical interface, is a component of the ps2 9 | // which allows textures and geometry to be sent to the gs for rasterization. 10 | // there are 3 possible paths that can be used to receive data. 11 | // however only 1 path can be ran at a time. 12 | // PATH1: data is transferred via the vu1 using the xgkick instruction 13 | // PATH2: data is transferred via the vif1 14 | // PATH3: data is transferred using the ee via dmac 15 | struct GIF { 16 | GIF(gs::Context& gs); 17 | 18 | void Reset(); 19 | void SystemReset(); 20 | void Run(int cycles); 21 | 22 | u32 ReadRegister(u32 addr); 23 | void WriteRegister(u32 addr, u32 value); 24 | 25 | void WriteFIFO(u32 value); 26 | 27 | void SendPath3(u128 value); 28 | void ProcessPacked(u128 data); 29 | void ProcessImage(u128 data); 30 | 31 | private: 32 | void StartTransfer(); 33 | void ProcessTag(); 34 | 35 | u8 ctrl; 36 | u32 stat; 37 | 38 | // the path3 fifo stores up to 16 quadwords (128-bit) 39 | common::Queue fifo; 40 | 41 | struct Tag { 42 | u32 nloop; 43 | bool eop; 44 | bool prim; 45 | u32 prim_data; 46 | u32 format; 47 | u32 nregs; 48 | u64 reglist; 49 | u32 reglist_offset; 50 | int transfers_left; 51 | } current_tag; 52 | 53 | gs::Context& gs; 54 | }; -------------------------------------------------------------------------------- /src/common/filesystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common/filesystem.h" 4 | #include "common/string.h" 5 | 6 | namespace common { 7 | 8 | std::vector ScanDirectoryRecursive(const std::string& path, std::vector extensions) { 9 | std::vector files; 10 | for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) { 11 | if (!std::filesystem::is_directory(entry)) { 12 | const std::string& extension = std::filesystem::path(entry).extension(); 13 | if (std::find(extensions.begin(), extensions.end(), extension) != extensions.end()) { 14 | files.push_back(entry.path()); 15 | } 16 | } 17 | } 18 | return files; 19 | } 20 | 21 | std::string GetFormattedSize(u64 size) { 22 | int i = 0; 23 | double mantissa = size; 24 | static const char* size_types = "BKMGTPE"; 25 | 26 | while (mantissa >= 1024) { 27 | mantissa /= 1024; 28 | i++; 29 | } 30 | 31 | mantissa = std::ceil(mantissa * 10.0f) / 10.0f; 32 | std::string mantissa_str = common::Format("%.2f", mantissa); 33 | 34 | if (mantissa_str.back() == '0') { 35 | mantissa_str = mantissa_str.substr(0, mantissa_str.size() - 1); 36 | } 37 | 38 | if (mantissa_str.back() == '0') { 39 | mantissa_str = mantissa_str.substr(0, mantissa_str.size() - 2); 40 | } 41 | 42 | if (i > 0) { 43 | return common::Format("%s %cB", mantissa_str.c_str(), size_types[i]); 44 | } 45 | 46 | return common::Format("%s B", mantissa_str.c_str()); 47 | } 48 | 49 | } // namespace common -------------------------------------------------------------------------------- /src/common/emu_thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | EmuThread::EmuThread(RunFunction run_frame, UpdateFunction update_fps) : run_frame(run_frame), update_fps(update_fps) { 4 | 5 | } 6 | 7 | EmuThread::~EmuThread() { 8 | Stop(); 9 | } 10 | 11 | void EmuThread::Start() { 12 | running = true; 13 | 14 | thread = std::thread{[this]() { 15 | Run(); 16 | }}; 17 | } 18 | 19 | void EmuThread::Reset() { 20 | frames = 0; 21 | } 22 | 23 | void EmuThread::Run() { 24 | auto frame_end = std::chrono::system_clock::now() + frame{1}; 25 | auto fps_update = std::chrono::system_clock::now(); 26 | while (running) { 27 | run_frame(); 28 | frames++; 29 | 30 | if (std::chrono::system_clock::now() - fps_update >= std::chrono::milliseconds(update_interval)) { 31 | update_fps(frames * (1000.0f / update_interval)); 32 | frames = 0; 33 | fps_update = std::chrono::system_clock::now(); 34 | } 35 | 36 | if (framelimiter) { 37 | // block the execution of the emulator thread until 1 / 60 of a second has passed 38 | std::this_thread::sleep_until(frame_end); 39 | } 40 | 41 | frame_end += frame{1}; 42 | } 43 | } 44 | 45 | void EmuThread::Stop() { 46 | if (!running) { 47 | return; 48 | } 49 | 50 | // allow the emulator to first complete a frame, and then pause emulation 51 | running = false; 52 | 53 | thread.join(); 54 | } 55 | 56 | auto EmuThread::IsActive() -> bool { 57 | return running; 58 | } 59 | 60 | auto EmuThread::GetFPS() -> int { 61 | return frames; 62 | } 63 | 64 | void EmuThread::ToggleFramelimiter() { 65 | framelimiter = !framelimiter; 66 | } -------------------------------------------------------------------------------- /src/common/queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/log.h" 5 | 6 | namespace common { 7 | 8 | template 9 | class Queue { 10 | public: 11 | void Reset() { 12 | read_index = 0; 13 | write_index = 0; 14 | length = 0; 15 | } 16 | 17 | template 18 | void Push(T value) { 19 | static_assert(sizeof(T) >= sizeof(InnerT)); 20 | static_assert(sizeof(T) <= (sizeof(InnerT) * size)); 21 | int ratio = sizeof(T) / sizeof(InnerT); 22 | InnerT* data = reinterpret_cast(&value); 23 | 24 | for (int i = 0; i < ratio; i++) { 25 | buffer[write_index % size] = data[i]; 26 | write_index = (write_index + 1) % size; 27 | length++; 28 | } 29 | } 30 | 31 | template 32 | T Pop() { 33 | static_assert(sizeof(T) >= sizeof(InnerT)); 34 | static_assert(sizeof(T) <= (sizeof(InnerT) * size)); 35 | int ratio = sizeof(T) / sizeof(InnerT); 36 | T value; 37 | InnerT* data = reinterpret_cast(&value); 38 | 39 | for (int i = 0; i < ratio; i++) { 40 | data[i] = buffer[read_index % size]; 41 | read_index = (read_index + 1) % size; 42 | length--; 43 | } 44 | 45 | return value; 46 | } 47 | 48 | int GetLength() { 49 | return length; 50 | } 51 | 52 | int GetSize() { 53 | return size; 54 | } 55 | 56 | bool Empty() { 57 | return length == 0; 58 | } 59 | 60 | private: 61 | int read_index = 0; 62 | int write_index = 0; 63 | int length = 0; 64 | 65 | std::array buffer; 66 | }; 67 | 68 | } // namespace common -------------------------------------------------------------------------------- /src/core/iop/intc.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/iop/intc.h" 3 | #include "core/iop/context.h" 4 | 5 | namespace iop { 6 | 7 | INTC::INTC(Context& ctx) : ctx(ctx) {} 8 | 9 | void INTC::Reset() { 10 | interrupt_mask = 0; 11 | interrupt_status = 0; 12 | interrupt_control = 0; 13 | } 14 | 15 | u32 INTC::ReadRegister(int offset) { 16 | switch (offset) { 17 | case 0x1f801070: 18 | return interrupt_status; 19 | case 0x1f801074: 20 | return interrupt_mask; 21 | case 0x1f801078: { 22 | u32 value = interrupt_control; 23 | interrupt_control = 0; 24 | UpdateInterrupts(); 25 | return value; 26 | } 27 | default: 28 | common::Error("InterruptController: handle read offset %02x", offset); 29 | } 30 | 31 | return 0; 32 | } 33 | 34 | void INTC::WriteRegister(int offset, u32 data) { 35 | switch (offset) { 36 | case 0x1f801070: 37 | // common::Log("[IOP INTC] I_STAT write %08x", data); 38 | interrupt_status &= data & WRITE_MASK; 39 | UpdateInterrupts(); 40 | break; 41 | case 0x1f801074: 42 | // common::Log("[IOP INTC] I_MASK write %08x", data); 43 | interrupt_mask = data & WRITE_MASK; 44 | UpdateInterrupts(); 45 | break; 46 | case 0x1f801078: 47 | interrupt_control = data & 0x1; 48 | UpdateInterrupts(); 49 | break; 50 | default: 51 | common::Error("InterruptController: handle write offset %02x", offset); 52 | } 53 | } 54 | 55 | void INTC::RequestInterrupt(InterruptSource source) { 56 | interrupt_status |= 1 << static_cast(source); 57 | UpdateInterrupts(); 58 | } 59 | 60 | void INTC::UpdateInterrupts() { 61 | ctx.RaiseInterrupt(interrupt_control && (interrupt_status & interrupt_mask)); 62 | } 63 | 64 | } // namespace iop -------------------------------------------------------------------------------- /src/frontend/host_interface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/log.h" 5 | #include "common/games_list.h" 6 | #include "core/core.h" 7 | #include 8 | #include 9 | #include "imgui/imgui.h" 10 | #include "imgui/imgui_impl_sdl2.h" 11 | #include "imgui/imgui_impl_opengl3.h" 12 | #include "imgui/imfilebrowser.h" 13 | #include 14 | #include 15 | 16 | class HostInterface { 17 | public: 18 | HostInterface(); 19 | 20 | bool initialise(); 21 | void run(); 22 | void shutdown(); 23 | void TogglePause(); 24 | 25 | Core core; 26 | 27 | private: 28 | void HandleInput(); 29 | void RenderMenubar(); 30 | void Boot(const std::string& path); 31 | void RenderDisplayWindow(); 32 | void render_library_window(); 33 | 34 | void BeginFullscreenWindow(const char *name, ImVec2 padding = ImVec2(0.0f, 0.0f)); 35 | void EndFullscreenWindow(); 36 | 37 | void render_debugger_window(); 38 | void render_ee_debugger(); 39 | void render_iop_debugger(); 40 | 41 | const char* glsl_version = "#version 330"; 42 | 43 | SDL_Window* window; 44 | SDL_GLContext gl_context; 45 | 46 | ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.00f); 47 | bool running = true; 48 | ImGui::FileBrowser file_dialog; 49 | 50 | int window_width; 51 | int window_height; 52 | 53 | static constexpr int menubar_height = 18; 54 | GLuint screen_texture; 55 | 56 | enum class WindowState { 57 | Library, 58 | Display, 59 | }; 60 | 61 | WindowState window_state = WindowState::Library; 62 | common::GamesList games_list; 63 | 64 | float fps; 65 | bool m_show_debugger_window{true}; 66 | bool m_show_demo_window{false}; 67 | int m_ee_disassembly_size{15}; 68 | int m_iop_disassembly_size{15}; 69 | }; -------------------------------------------------------------------------------- /src/common/log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common/log.h" 3 | 4 | #define RED "\x1B[31m" 5 | #define GREEN "\x1B[32m" 6 | #define YELLOW "\x1B[33m" 7 | #define BLUE "\x1B[34m" 8 | #define RESET "\x1B[0m" 9 | 10 | FILE* fp = fopen("matcha.log", "w"); 11 | 12 | namespace common { 13 | 14 | void Info(const char* format, ...) { 15 | std::va_list args; 16 | va_start(args, format); 17 | std::printf("[" GREEN "INFO" RESET "] "); 18 | std::vfprintf(stdout, format, args); 19 | std::printf("\n"); 20 | std::fflush(stdout); 21 | va_end(args); 22 | } 23 | 24 | void Debug(const char* format, ...) { 25 | std::va_list args; 26 | va_start(args, format); 27 | std::printf("[" BLUE "DEBUG" RESET "] "); 28 | std::vfprintf(stdout, format, args); 29 | std::printf("\n"); 30 | std::fflush(stdout); 31 | va_end(args); 32 | } 33 | 34 | void Warn(const char* format, ...) { 35 | std::va_list args; 36 | va_start(args, format); 37 | std::printf("[" YELLOW "WARN" RESET "] "); 38 | std::vfprintf(stdout, format, args); 39 | std::printf("\n"); 40 | std::fflush(stdout); 41 | va_end(args); 42 | } 43 | 44 | void Error(const char* format, ...) { 45 | std::va_list args; 46 | va_start(args, format); 47 | std::fprintf(stderr, "[" RED "ERROR" RESET "] "); 48 | std::vfprintf(stderr, format, args); 49 | std::fprintf(stderr, "\n"); 50 | std::fflush(stderr); 51 | va_end(args); 52 | std::exit(0); 53 | } 54 | 55 | void Log(const char* format, ...) { 56 | std::va_list args; 57 | va_start(args, format); 58 | std::vfprintf(fp, format, args); 59 | std::fprintf(fp, "\n"); 60 | va_end(args); 61 | } 62 | 63 | void LogNoNewline(const char* format, ...) { 64 | std::va_list args; 65 | va_start(args, format); 66 | std::vfprintf(fp, format, args); 67 | va_end(args); 68 | } 69 | 70 | } // namespace common -------------------------------------------------------------------------------- /src/core/iop/timers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | 5 | #include "core/iop/intc.h" 6 | 7 | namespace iop { 8 | 9 | class Timers { 10 | public: 11 | Timers(INTC& intc); 12 | 13 | void reset(); 14 | void run(int cycles); 15 | u32 read(u32 addr); 16 | void write(u32 addr, u32 data); 17 | 18 | private: 19 | int calculate_channel_index(u32 addr); 20 | u32 calculate_channel_prescaler(int index); 21 | void run_channel(int index, int cycles); 22 | bool overflow_occured(int index); 23 | void raise_irq(int index); 24 | 25 | struct Channel { 26 | u64 counter; 27 | u64 cycles; 28 | u64 cycles_per_tick; 29 | 30 | union Mode { 31 | struct { 32 | bool gate_enable : 1; 33 | u32 gate_mode : 2; 34 | 35 | // Reset counter on compare interrupts. 36 | u32 zero_return : 1; 37 | 38 | bool compare_irq : 1; 39 | bool overflow_irq : 1; 40 | bool repeat_irq : 1; 41 | bool levl : 1; 42 | 43 | // If set: 44 | // Timer 0: pixel clock (13.5mhz) 45 | // Timer 1/3: hblank 46 | // Ohter: sysclock (just regular clock) 47 | bool use_external_signal : 1; 48 | 49 | bool timer2_prescaler : 1; 50 | bool irqs_enabled : 1; 51 | bool compare_irq_raised : 1; 52 | bool overflow_irq_raised : 1; 53 | u32 timer45_prescaler : 2; 54 | u32 : 17; 55 | }; 56 | 57 | u32 data; 58 | } mode; 59 | 60 | u32 target; 61 | } m_channels[6]; 62 | 63 | INTC& m_intc; 64 | 65 | static constexpr u32 EE_CLOCK = 294912000; 66 | static constexpr u32 IOP_CLOCK = EE_CLOCK / 8; 67 | }; 68 | 69 | } // namespace iop -------------------------------------------------------------------------------- /src/core/iop/dmac.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "core/iop/sio2.h" 5 | 6 | struct System; 7 | 8 | namespace iop { 9 | 10 | class DMAC { 11 | public: 12 | DMAC(System& system, SIO2& sio2); 13 | 14 | void Reset(); 15 | void Run(int cycles); 16 | u32 ReadRegister(u32 addr); 17 | u32 ReadChannel(u32 addr); 18 | void WriteRegister(u32 addr, u32 data); 19 | void WriteChannel(u32 addr, u32 data); 20 | int GetChannelIndex(u32 addr); 21 | bool GetChannelEnable(int index); 22 | void DoSIF0Transfer(); 23 | void DoSIF1Transfer(); 24 | void DoSPU2Transfer(); 25 | void DoSIO2InTransfer(); 26 | void DoSIO2OutTransfer(); 27 | void EndTransfer(int index); 28 | 29 | // dma priority/enable 30 | // used for the first 7 channels 31 | u32 dpcr; 32 | 33 | // dma priority/enable 2 34 | // used for the remaining channels 35 | u32 dpcr2; 36 | 37 | union DICR { 38 | struct { 39 | u8 completion: 7; 40 | u32 : 8; 41 | bool force_irq : 1; 42 | u8 masks : 7; 43 | bool master_interrupt_enable : 1; 44 | u8 flags : 7; 45 | bool master_interrupt_flag : 1; 46 | }; 47 | 48 | u32 data; 49 | } dicr; 50 | 51 | union DICR2 { 52 | struct { 53 | u16 tag_interrupt : 13; 54 | u8 : 2; 55 | bool force_irq : 1; 56 | u8 masks : 6; 57 | u8 : 2; 58 | u8 flags : 6; 59 | u8 : 2; 60 | }; 61 | 62 | u32 data; 63 | } dicr2; 64 | 65 | struct Channel { 66 | u32 address; 67 | u16 block_size; // in words (0 = 0x10000) 68 | u16 block_count; 69 | u32 control; 70 | u32 tag_address; 71 | bool end_transfer; 72 | } channels[13]; 73 | 74 | bool global_dma_enable; 75 | bool global_dma_interrupt_control; 76 | 77 | private: 78 | System& system; 79 | SIO2& sio2; 80 | }; 81 | 82 | } // namespace iop -------------------------------------------------------------------------------- /src/core/gs/page.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/memory.h" 5 | #include "common/log.h" 6 | 7 | namespace gs { 8 | 9 | struct Page { 10 | void Reset() { 11 | for (int block = 0; block < 32; block++) { 12 | for (int pixel = 0; pixel < 256; pixel++) { 13 | blocks[block][pixel] = 0; 14 | } 15 | } 16 | } 17 | 18 | u32 ReadPSMCT32Pixel(int x, int y) { 19 | return common::Read(GetPSMCT32Pointer(x, y)); 20 | } 21 | 22 | void WritePSMCT32Pixel(int x, int y, u32 value) { 23 | common::Write(GetPSMCT32Pointer(x, y), value); 24 | } 25 | 26 | private: 27 | u8* GetPSMCT32Pointer(int x, int y) { 28 | // get block coordinates 29 | int block_x = (x / 8) % 8; 30 | int block_y = (y / 8) % 4; 31 | 32 | constexpr static int block_configuration[4][8] = { 33 | {0, 1, 4, 5, 16, 17, 20, 21}, 34 | {2, 3, 6, 7, 18, 19, 22, 23}, 35 | {8, 9, 12, 13, 24, 25, 28, 29}, 36 | {10, 11, 14, 15, 26, 27, 30, 31}, 37 | }; 38 | 39 | // get the block to write to based on configuration within page 40 | int block = block_configuration[block_y][block_x]; 41 | 42 | constexpr static int pixel_configuration[2][8] = { 43 | {0, 1, 4, 5, 8, 9, 12, 13}, 44 | {2, 3, 6, 7, 10, 11, 14, 15}, 45 | }; 46 | 47 | // find which column we want to write to 48 | int column = (y / 2) % 4; 49 | 50 | // get pixel coordinates 51 | int pixel_x = x % 8; 52 | int pixel_y = y % 2; 53 | 54 | int pixel = pixel_configuration[pixel_y][pixel_x]; 55 | 56 | // each pixel is 32 bits, and each column is 16 pixels 57 | // hence each column is 64 bytes 58 | int offset = (column * 64) + (pixel * 4); 59 | return &blocks[block][offset]; 60 | } 61 | 62 | // each page contains 32 blocks, with each block being 256 bytes 63 | u8 blocks[32][256]; 64 | }; 65 | 66 | } // namespace gs -------------------------------------------------------------------------------- /src/core/ee/context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "common/types.h" 7 | #include "common/virtual_page_table.h" 8 | #include "core/ee/cop0.h" 9 | #include "core/ee/cop1.h" 10 | #include "core/ee/dmac.h" 11 | #include "core/ee/timers.h" 12 | #include "core/ee/intc.h" 13 | #include "core/ee/interpreter.h" 14 | 15 | struct System; 16 | 17 | namespace ee { 18 | 19 | struct Context { 20 | Context(System& system); 21 | 22 | void Reset(); 23 | void Run(int cycles); 24 | 25 | u8* rdram() { return m_rdram->data(); } 26 | u8* scratchpad() { return m_scratchpad.data(); } 27 | 28 | // credit goes to DobieStation for the elegant way of accessing 128 bit registers 29 | template 30 | T GetReg(int reg, int offset = 0) { 31 | return *(T*)&gpr[(reg * sizeof(u64) * 2) + (offset * sizeof(T))]; 32 | } 33 | 34 | template 35 | void SetReg(int reg, T value, int offset = 0) { 36 | if (reg) { 37 | *(T*)&gpr[(reg * sizeof(u64) * 2) + (offset * sizeof(T))] = value; 38 | } 39 | } 40 | 41 | template 42 | T read(VirtualAddress vaddr); 43 | 44 | template 45 | void write(VirtualAddress vaddr, T value); 46 | 47 | void RaiseInterrupt(int signal, bool value); 48 | std::string GetSyscallInfo(int index); 49 | 50 | std::array gpr; 51 | u32 pc = 0; 52 | u32 npc = 0; 53 | 54 | u64 lo = 0; 55 | u64 hi = 0; 56 | u64 lo1 = 0; 57 | u64 hi1 = 0; 58 | u64 sa = 0; 59 | 60 | COP0 cop0; 61 | COP1 cop1; 62 | DMAC dmac; 63 | Timers timers; 64 | INTC intc; 65 | System& system; 66 | 67 | private: 68 | u32 ReadIO(u32 paddr); 69 | void WriteIO(u32 paddr, u32 value); 70 | 71 | std::unique_ptr> m_rdram; 72 | std::array m_scratchpad; 73 | 74 | // rdram initialisation registers 75 | u32 mch_drd; 76 | u32 rdram_sdevid; 77 | u32 mch_ricm; 78 | 79 | common::VirtualPageTable vtlb; 80 | Interpreter interpreter; 81 | }; 82 | 83 | } // namespace ee -------------------------------------------------------------------------------- /src/core/elf_loader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "core/elf_loader.h" 3 | #include "core/system.h" 4 | 5 | ELFLoader::ELFLoader(System& system) : system(system) {} 6 | 7 | ELFLoader::~ELFLoader() { 8 | if (elf) { 9 | delete[] elf; 10 | } 11 | } 12 | 13 | void ELFLoader::Load() { 14 | std::ifstream file(path, std::ios::binary); 15 | 16 | if (!file) { 17 | common::Error("[ELFLoader] elf with path %s does not exist!", path.c_str()); 18 | } 19 | 20 | file.unsetf(std::ios::skipws); 21 | file.seekg(0, std::ios::end); 22 | size = file.tellg(); 23 | elf = new u8[size]; 24 | file.seekg(0, std::ios::beg); 25 | file.read(reinterpret_cast(elf), size); 26 | file.close(); 27 | 28 | common::Log("[ELFLoader] ELF was successfully loaded!"); 29 | common::Log("[ELFLoader] Size: %08x", size); 30 | 31 | LoadHeader(); 32 | } 33 | 34 | void ELFLoader::LoadHeader() { 35 | // check if the elf has at least the first 4 magic bytes 36 | if (elf[0] != 0x7F || elf[1] != 'E' || elf[2] != 'L' || elf[3] != 'F') { 37 | common::Error("[ELFLoader] invalid ELF! %s", path.c_str()); 38 | } 39 | 40 | memcpy(&header, &elf[0x10], sizeof(ELFHeader)); 41 | 42 | for (u32 header_offset = header.phoff; header_offset < header.phoff + (header.phnum * header.phentsize); header_offset += header.phentsize) { 43 | // now offset is at the start of a program header 44 | ProgramHeader program_header; 45 | memcpy(&program_header, &elf[header_offset], sizeof(ProgramHeader)); 46 | 47 | // now copy the segment into the EEBus word by word 48 | for (u32 program_offset = program_header.offset; program_offset < (program_header.offset + program_header.filesz); program_offset += 4) { 49 | u32 data = 0; 50 | memcpy(&data, &elf[program_offset], 4); 51 | 52 | system.ee.write(program_header.paddr, data); 53 | program_header.paddr += 4; 54 | } 55 | } 56 | 57 | common::Log("[ELFLoader] entrypoint: %08x", header.entry); 58 | 59 | system.ee.pc = header.entry; 60 | } 61 | 62 | void ELFLoader::SetPath(std::string elf_path) { 63 | path = elf_path; 64 | } -------------------------------------------------------------------------------- /src/frontend/imgui/imgui_impl_sdl2.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Platform Backend for SDL2 2 | // This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) 3 | // (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) 4 | 5 | // Implemented features: 6 | // [X] Platform: Clipboard support. 7 | // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] 8 | // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. 9 | // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. 10 | // [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!. 11 | 12 | // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. 13 | // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. 14 | // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. 15 | // Read online: https://github.com/ocornut/imgui/tree/master/docs 16 | 17 | #pragma once 18 | #include "imgui.h" // IMGUI_IMPL_API 19 | 20 | struct SDL_Window; 21 | struct SDL_Renderer; 22 | typedef union SDL_Event SDL_Event; 23 | 24 | IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context); 25 | IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window); 26 | IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window); 27 | IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window); 28 | IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer); 29 | IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); 30 | IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(); 31 | IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); 32 | 33 | #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS 34 | static inline void ImGui_ImplSDL2_NewFrame(SDL_Window*) { ImGui_ImplSDL2_NewFrame(); } // 1.84: removed unnecessary parameter 35 | #endif 36 | -------------------------------------------------------------------------------- /src/core/ee/cop0.cpp: -------------------------------------------------------------------------------- 1 | #include "core/ee/cop0.h" 2 | 3 | namespace ee { 4 | 5 | enum COP0Regs { 6 | Index = 0, 7 | Random = 1, 8 | EntryLo0 = 2, 9 | EntryLo1 = 3, 10 | Context = 4, 11 | PageMask = 5, 12 | Wired = 6, 13 | BadVAddr = 8, 14 | Count = 9, 15 | EntryHi = 10, 16 | Compare = 11, 17 | Status = 12, 18 | Cause = 13, 19 | EPC = 14, 20 | PRId = 15, 21 | Config = 16, 22 | BadPAddr = 23, 23 | Debug = 24, 24 | Perf = 25, 25 | TagLo = 28, 26 | TagHi = 29, 27 | ErrorEPC = 30, 28 | }; 29 | 30 | void COP0::Reset() { 31 | for (int i = 0; i < 32; i++) { 32 | gpr[i] = 0; 33 | } 34 | 35 | cause.data = 0; 36 | index = 0; 37 | gpr[PRId] = 0x2e20; 38 | } 39 | 40 | u32 COP0::GetReg(int reg) { 41 | switch (reg) { 42 | case 13: 43 | return cause.data; 44 | case 9: case 12: case 14: case 15: case 30: 45 | return gpr[reg]; 46 | default: 47 | common::Error("[ee::COP0] handle read r%d", reg); 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | void COP0::SetReg(int reg, u32 value) { 54 | switch (reg) { 55 | case 0: 56 | index = value; 57 | break; 58 | case 2: 59 | common::Log("[ee::COP0] entrylo0 write %08x", value); 60 | gpr[reg] = value; 61 | break; 62 | case 3: 63 | common::Log("[ee::COP0] entrylo1 write %08x", value); 64 | gpr[reg] = value; 65 | break; 66 | case 5: case 6: 67 | case 9: case 10: case 12: case 16: 68 | gpr[reg] = value; 69 | break; 70 | case 13: 71 | // cause can't be written to. 72 | // this would allow some interrupt pending bits to possibly be set, 73 | // which can result in unintentional interrupts 74 | break; 75 | case 11: 76 | // writing to compare clears timer interrupt pending bit 77 | // in cause 78 | cause.timer_pending = false; 79 | gpr[reg] = value; 80 | break; 81 | case 14: 82 | // common::Log("[COP0] write EPC %08x", value); 83 | gpr[EPC] = value; 84 | break; 85 | default: 86 | common::Error("[ee::COP0] handle write r%d = %08x", reg, value); 87 | } 88 | } 89 | 90 | void COP0::CountUp() { 91 | gpr[Count]++; 92 | 93 | if (gpr[Count] == gpr[Compare]) { 94 | cause.timer_pending = true; 95 | } 96 | } 97 | 98 | } // namespace ee -------------------------------------------------------------------------------- /src/core/iop/interpreter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common/types.h" 5 | #include "core/iop/instruction.h" 6 | #include "core/iop/executor.h" 7 | 8 | namespace iop { 9 | 10 | struct Context; 11 | 12 | class Interpreter : public Executor { 13 | public: 14 | Interpreter(Context& ctx); 15 | 16 | void Reset() override; 17 | void Run(int cycles) override; 18 | 19 | enum class InstructionTable { 20 | Primary, 21 | Secondary, 22 | RegImm, 23 | COP0, 24 | }; 25 | 26 | void DoException(Exception exception); 27 | void RaiseInterrupt(bool value); 28 | void CheckInterrupts(); 29 | 30 | private: 31 | typedef void (Interpreter::*InstructionHandler)(); 32 | void RegisterOpcode(InstructionHandler handler, int index, InstructionTable table); 33 | 34 | void UndefinedInstruction(); 35 | void SecondaryInstruction(); 36 | void COP0Instruction(); 37 | 38 | void mfc0(); 39 | void sll(); 40 | void slti(); 41 | void bne(); 42 | void lui(); 43 | void ori(); 44 | void jr(); 45 | void beq(); 46 | void lw(); 47 | void andi(); 48 | void addiu(); 49 | void addi(); 50 | void orr(); 51 | void sw(); 52 | void sb(); 53 | void mtc0(); 54 | void lb(); 55 | void jal(); 56 | void addu(); 57 | void sltu(); 58 | void lh(); 59 | void andd(); 60 | void slt(); 61 | void j(); 62 | void lbu(); 63 | void sra(); 64 | void sltiu(); 65 | void lhu(); 66 | void srl(); 67 | void subu(); 68 | void blez(); 69 | void bgtz(); 70 | void divu(); 71 | void mflo(); 72 | void sh(); 73 | void jalr(); 74 | void bcondz(); 75 | void xorr(); 76 | void sllv(); 77 | void mfhi(); 78 | void multu(); 79 | void mthi(); 80 | void mtlo(); 81 | void syscall_exception(); 82 | void rfe(); 83 | void mult(); 84 | void nor(); 85 | void srlv(); 86 | void add(); 87 | void div(); 88 | void lwl(); 89 | void lwr(); 90 | void swl(); 91 | void swr(); 92 | void srav(); 93 | 94 | void IOPPuts(); 95 | 96 | private: 97 | bool branch_delay; 98 | bool branch; 99 | 100 | Instruction inst; 101 | Context& ctx; 102 | 103 | std::array primary_table; 104 | std::array secondary_table; 105 | }; 106 | 107 | } // namespace iop -------------------------------------------------------------------------------- /src/core/scheduler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void Scheduler::Reset() { 4 | events.clear(); 5 | 6 | current_time = 0; 7 | } 8 | 9 | void Scheduler::Tick(int cycles) { 10 | current_time += cycles; 11 | } 12 | 13 | void Scheduler::ResetCurrentTime() { 14 | // this will be ran at the start of each frame 15 | current_time = 0; 16 | } 17 | 18 | u64 Scheduler::GetCurrentTime() { 19 | return current_time; 20 | } 21 | 22 | u64 Scheduler::GetEventTime() { 23 | return events[0].start_time; 24 | } 25 | 26 | int Scheduler::CalculateEventIndex(Event& new_event) { 27 | int lower_bound = 0; 28 | int upper_bound = events.size() - 1; 29 | 30 | while (lower_bound <= upper_bound) { 31 | int mid = (lower_bound + upper_bound) / 2; 32 | 33 | if (new_event.start_time > events[mid].start_time) { 34 | lower_bound = mid + 1; 35 | } else { 36 | upper_bound = mid - 1; 37 | } 38 | } 39 | 40 | return lower_bound; 41 | } 42 | 43 | void Scheduler::RunEvents() { 44 | // do any scheduler events that are meant to happen at the current moment 45 | while (events[0].start_time <= GetCurrentTime() && events.size() > 0) { 46 | // do the callback associated with that scheduler event 47 | events[0].callback(); 48 | 49 | // remove the event from the priority queue 50 | events.erase(events.begin()); 51 | } 52 | } 53 | 54 | void Scheduler::Add(u64 delay, std::function callback) { 55 | Event new_event; 56 | new_event.callback = callback; 57 | new_event.id = NoneEvent; 58 | new_event.start_time = GetCurrentTime() + delay; 59 | int index = CalculateEventIndex(new_event); 60 | 61 | events.insert(events.begin() + index, new_event); 62 | } 63 | 64 | void Scheduler::AddWithId(u64 delay, int id, std::function callback) { 65 | Event new_event; 66 | new_event.callback = callback; 67 | new_event.id = id; 68 | new_event.start_time = GetCurrentTime() + delay; 69 | int index = CalculateEventIndex(new_event); 70 | 71 | events.insert(events.begin() + index, new_event); 72 | } 73 | 74 | void Scheduler::Cancel(int id) { 75 | for (u64 i = 0; i < events.size(); i++) { 76 | if (events[i].id == id) { 77 | events.erase(events.begin() + i); 78 | } 79 | } 80 | } 81 | 82 | void Scheduler::SchedulerDebug() { 83 | for (Event event : events) { 84 | printf("start time: %ld, id: %d\n", event.start_time, event.id); 85 | } 86 | } -------------------------------------------------------------------------------- /src/core/ee/cop1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/log.h" 5 | #include "core/ee/instruction.h" 6 | 7 | namespace ee { 8 | 9 | class COP1 { 10 | public: 11 | void Reset(); 12 | 13 | u32 GetReg(int reg); 14 | void SetReg(int reg, u32 value); 15 | u32 GetControlReg(int reg); 16 | void SetControlReg(int reg, u32 value); 17 | f32 AsFloat(u32 value); 18 | 19 | bool condition() const { return fcr31.c; } 20 | 21 | void adda_s(Instruction inst); 22 | void madd_s(Instruction inst); 23 | void mov_s(Instruction inst); 24 | void abs_s(Instruction inst); 25 | void add_s(Instruction inst); 26 | void max_s(Instruction inst); 27 | void min_s(Instruction inst); 28 | void neg_s(Instruction inst); 29 | void sub_s(Instruction inst); 30 | void suba_s(Instruction inst); 31 | void c_eq_s(Instruction inst); 32 | void c_f_s(); 33 | void c_le_s(Instruction inst); 34 | void c_lt_s(Instruction inst); 35 | 36 | private: 37 | union Float { 38 | struct { 39 | u32 fraction : 23; 40 | u32 exponent : 8; 41 | u32 sign : 1; 42 | }; 43 | 44 | f32 f; 45 | u32 u; 46 | s32 s; 47 | 48 | static constexpr u32 MAX_POSITIVE = 0x7fffffff; 49 | static constexpr u32 MAX_NEGATIVE = 0xffffffff; 50 | static constexpr u32 POSITIVE_INFINITY = 0x7f800000; 51 | static constexpr u32 NEGATIVE_INFINITY = 0x7f800000; 52 | 53 | bool is_abnormal() { 54 | return u == MAX_POSITIVE || u == MAX_NEGATIVE || u == POSITIVE_INFINITY || u == NEGATIVE_INFINITY; 55 | } 56 | }; 57 | 58 | union FCR0 { 59 | struct { 60 | u32 rev : 8; 61 | u32 imp : 8; 62 | u32 : 16; 63 | }; 64 | 65 | u32 data; 66 | }; 67 | 68 | union FCR31 { 69 | struct { 70 | u32: 3; 71 | bool su : 1; 72 | bool so : 1; 73 | bool sd : 1; 74 | bool si : 1; 75 | u32 : 7; 76 | bool u : 1; 77 | bool o : 1; 78 | bool d : 1; 79 | bool i : 1; 80 | u32 : 5; 81 | bool c : 1; 82 | u32 : 8; 83 | }; 84 | 85 | u32 data; 86 | }; 87 | 88 | bool IsInfinity(const Float& fpr); 89 | void CheckOverflow(Float& fpr, bool set_flags); 90 | void CheckUnderflow(Float& fpr, bool set_flags); 91 | 92 | Float fpr[32]; 93 | Float accumulator; 94 | FCR0 fcr0; 95 | FCR31 fcr31; 96 | }; 97 | 98 | } // namespace ee -------------------------------------------------------------------------------- /src/core/sif/sif.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void SIF::Reset() { 4 | control = 0; 5 | bd6 = 0; 6 | mscom = 0; 7 | msflag = 0; 8 | smflag = 0; 9 | smcom = 0; 10 | 11 | std::queue sif0_empty; 12 | std::queue sif1_empty; 13 | 14 | sif0_fifo.swap(sif0_empty); 15 | sif1_fifo.swap(sif1_empty); 16 | } 17 | 18 | void SIF::WriteEEControl(u32 data) { 19 | if (!(data & 0x100)) { 20 | control &= ~0x100; 21 | } else { 22 | control |= 0x100; 23 | } 24 | } 25 | 26 | void SIF::WriteIOPControl(u32 data) { 27 | // not sure how this works tbh. figure out later 28 | u8 value = data & 0xF0; 29 | if (data & 0xA0) { 30 | control &= ~0xF000; 31 | control |= 0x2000; 32 | } 33 | 34 | if (control & value) { 35 | control &= ~value; 36 | } else { 37 | control |= value; 38 | } 39 | } 40 | 41 | void SIF::WriteBD6(u32 data) { 42 | // common::Log("[SIF] write bd6 %08x", data); 43 | bd6 = data; 44 | } 45 | 46 | void SIF::WriteMSCOM(u32 data) { 47 | // common::Log("[SIF] write mscom %08x", data); 48 | mscom = data; 49 | } 50 | 51 | void SIF::WriteSMCOM(u32 data) { 52 | smcom = data; 53 | } 54 | 55 | void SIF::SetMSFLAG(u32 data) { 56 | // common::Log("[SIF] write msflag %08x", data); 57 | msflag |= data; 58 | } 59 | 60 | void SIF::SetSMFLAG(u32 data) { 61 | smflag |= data; 62 | } 63 | 64 | void SIF::ResetMSFLAG(u32 data) { 65 | msflag &= ~data; 66 | } 67 | 68 | void SIF::ResetSMFLAG(u32 data) { 69 | smflag &= ~data; 70 | } 71 | 72 | u32 SIF::ReadMSFLAG() { 73 | // common::Log("[SIF] read msflag %08x", msflag); 74 | return msflag; 75 | } 76 | 77 | u32 SIF::ReadMSCOM() { 78 | // common::Log("[SIF] read mscom %08x", mscom); 79 | return mscom; 80 | } 81 | 82 | u32 SIF::ReadSMFLAG() { 83 | // common::Log("[SIF] read smflag %08x", smflag); 84 | return smflag; 85 | } 86 | 87 | u32 SIF::ReadSMCOM() { 88 | // common::Log("[SIF] read smcom %08x", smcom); 89 | return smcom; 90 | } 91 | 92 | u32 SIF::ReadControl() { 93 | return control; 94 | } 95 | 96 | void SIF::write_sif0_fifo(u32 data) { 97 | sif0_fifo.push(data); 98 | } 99 | 100 | void SIF::write_sif1_fifo(u128 data) { 101 | for (int i = 0; i < 4; i++) { 102 | sif1_fifo.push(data.uw[i]); 103 | } 104 | } 105 | 106 | u32 SIF::ReadSIF0FIFO() { 107 | u32 data = sif0_fifo.front(); 108 | sif0_fifo.pop(); 109 | 110 | return data; 111 | } 112 | 113 | u32 SIF::ReadSIF1FIFO() { 114 | u32 data = sif1_fifo.front(); 115 | sif1_fifo.pop(); 116 | 117 | return data; 118 | } 119 | 120 | int SIF::GetSIF0FIFOSize() { 121 | return sif0_fifo.size(); 122 | } 123 | 124 | int SIF::GetSIF1FIFOSize() { 125 | return sif1_fifo.size(); 126 | } -------------------------------------------------------------------------------- /src/core/ee/dmac.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | 5 | struct System; 6 | 7 | namespace ee { 8 | 9 | class DMAC { 10 | public: 11 | DMAC(System& system); 12 | 13 | void Reset(); 14 | void Run(int cycles); 15 | 16 | void WriteRegister(u32 addr, u32 data); 17 | 18 | u32 ReadChannel(u32 addr); 19 | void WriteChannel(u32 addr, u32 data); 20 | u32 ReadInterruptStatus(); 21 | void WriteControl(u32 data); 22 | u32 ReadControl(); 23 | u32 ReadPriorityControl(); 24 | u32 ReadSkipQuadword(); 25 | 26 | void Transfer(int index); 27 | 28 | void do_gif_transfer(); 29 | void do_sif0_transfer(); 30 | void do_sif1_transfer(); 31 | void do_to_spr_transfer(); 32 | 33 | void StartTransfer(int index); 34 | void EndTransfer(int index); 35 | 36 | int GetChannelIndex(u32 addr); 37 | void CheckInterruptSignal(); 38 | 39 | void DoSourceChain(int index); 40 | 41 | u32 control; 42 | u32 interrupt_status; 43 | u32 priority_control; 44 | u32 skip_quadword; 45 | u32 ringbuffer_size; 46 | u32 ringbuffer_offset; 47 | u32 disabled_status; 48 | 49 | private: 50 | // TODO: dmac specifies transfer addresses as physical addresses, skipping the TLB. 51 | // We should replace all ee memory accesses with a specific function 52 | u128 read_u128(u32 addr); 53 | void write_u128(u32 addr, u128 data); 54 | 55 | struct Channel { 56 | enum class Mode : u8 { 57 | Normal = 0, 58 | Chain = 1, 59 | Interleave = 2, 60 | }; 61 | 62 | union Control { 63 | struct { 64 | // Only used for vif1 and sif2 transfers. 65 | bool from_memory : 1; 66 | 67 | u32 : 1; 68 | Mode mode : 2; 69 | u32 address_stack_pointer : 2; 70 | 71 | // Only used in source chain mode. 72 | bool transfer_dmatag : 1; 73 | 74 | bool dmatag_irq : 1; 75 | bool busy : 1; 76 | u32 : 7; 77 | u32 dmatag_upper : 16; 78 | }; 79 | 80 | u32 data; 81 | } control; 82 | 83 | u32 address; 84 | u32 tag_address; 85 | u32 quadword_count; 86 | u32 saved_tag_address0; 87 | u32 saved_tag_address1; 88 | u32 scratchpad_address; 89 | bool end_transfer; 90 | }; 91 | 92 | enum class ChannelType : int { 93 | VIF0 = 0, 94 | VIF1 = 1, 95 | GIF = 2, 96 | IPUFrom = 3, 97 | IPUTo = 4, 98 | SIF0 = 5, 99 | SIF1 = 6, 100 | SIF2 = 7, 101 | FromSPR = 8, 102 | ToSPR = 9, 103 | }; 104 | 105 | Channel channels[10]; 106 | System& system; 107 | }; 108 | 109 | } // namespace ee -------------------------------------------------------------------------------- /src/frontend/imgui/imgui_impl_opengl3.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline 2 | // - Desktop GL: 2.x 3.x 4.x 3 | // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) 4 | // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) 5 | 6 | // Implemented features: 7 | // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! 8 | // [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only). 9 | 10 | // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. 11 | // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. 12 | // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. 13 | // Read online: https://github.com/ocornut/imgui/tree/master/docs 14 | 15 | // About GLSL version: 16 | // The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string. 17 | // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" 18 | // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. 19 | 20 | #pragma once 21 | #include "imgui.h" // IMGUI_IMPL_API 22 | 23 | // Backend API 24 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr); 25 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); 26 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); 27 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); 28 | 29 | // (Optional) Called by Init/NewFrame/Shutdown 30 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); 31 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); 32 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); 33 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); 34 | 35 | // Specific OpenGL ES versions 36 | //#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten 37 | //#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android 38 | 39 | // You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. 40 | #if !defined(IMGUI_IMPL_OPENGL_ES2) \ 41 | && !defined(IMGUI_IMPL_OPENGL_ES3) 42 | 43 | // Try to detect GLES on matching platforms 44 | #if defined(__APPLE__) 45 | #include 46 | #endif 47 | #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) 48 | #define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" 49 | #elif defined(__EMSCRIPTEN__) || defined(__amigaos4__) 50 | #define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" 51 | #else 52 | // Otherwise imgui_impl_opengl3_loader.h will be used. 53 | #endif 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/core/system.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | System::System() : ee(*this), iop(*this), gs(*this), gif(gs), elf_loader(*this) { 4 | bios = std::make_unique>(); 5 | iop_ram = std::make_unique>(); 6 | VBlankStartEvent = std::bind(&System::VBlankStart, this); 7 | VBlankFinishEvent = std::bind(&System::VBlankFinish, this); 8 | } 9 | 10 | // credit goes to pcsx2 11 | // NTSC Interlaced Timings 12 | #define CYCLES_PER_FRAME 4920115 // 4920115.2 EE cycles to be exact FPS of 59.94005994005994hz 13 | #define VBLANK_START_CYCLES 4489019 // 4489019.391883126 Guess, exactly 23 HBLANK's before the end 14 | #define HBLANK_CYCLES 18742 15 | #define GS_VBLANK_DELAY 65622 // CSR FIELD swap/vblank happens ~65622 cycles after the INTC VBLANK_START event 16 | 17 | // clockrates 18 | #define EE_CLOCK_SPEED 294912000 19 | #define BUS_CLOCK_SPEED EE_CLOCK_SPEED / 2 20 | #define IOP_CLOCK_SPEED EE_CLOCK_SPEED / 8 21 | 22 | void System::Reset() { 23 | scheduler.Reset(); 24 | ee.Reset(); 25 | iop.Reset(); 26 | gs.Reset(); 27 | gif.Reset(); 28 | vu0.Reset(); 29 | vu1.Reset(); 30 | vif0.Reset(); 31 | vif1.Reset(); 32 | ipu.Reset(); 33 | sif.Reset(); 34 | spu.Reset(); 35 | spu2.Reset(); 36 | 37 | iop_ram->fill(0); 38 | bios->fill(0); 39 | LoadBIOS(); 40 | 41 | fastboot_done = false; 42 | } 43 | 44 | void System::RunFrame() { 45 | u64 end_timestamp = scheduler.GetCurrentTime() + CYCLES_PER_FRAME; 46 | int cycles = 32; 47 | scheduler.Add(VBLANK_START_CYCLES, VBlankStartEvent); 48 | scheduler.Add(CYCLES_PER_FRAME, VBlankFinishEvent); 49 | 50 | while (scheduler.GetCurrentTime() < end_timestamp) { 51 | ee.Run(cycles); 52 | 53 | // these components run at bus speed (1 / 2 speed of ee) 54 | gif.Run(cycles / 2); 55 | 56 | // iop runs at 1 / 8 speed of the ee 57 | iop.Run(cycles / 8); 58 | 59 | scheduler.Tick(cycles); 60 | scheduler.RunEvents(); 61 | } 62 | } 63 | 64 | // implement later 65 | void System::SingleStep() { 66 | ee.Run(1); 67 | } 68 | 69 | void System::VBlankStart() { 70 | gs.RenderCRTC(); 71 | ee.intc.RequestInterrupt(ee::InterruptSource::VBlankStart); 72 | iop.intc.RequestInterrupt(iop::InterruptSource::VBlankStart); 73 | } 74 | 75 | void System::VBlankFinish() { 76 | ee.intc.RequestInterrupt(ee::InterruptSource::VBlankFinish); 77 | iop.intc.RequestInterrupt(iop::InterruptSource::VBlankFinish); 78 | } 79 | 80 | void System::SetBootParameters(BootMode boot_mode, std::string path = "") { 81 | if (boot_mode == BootMode::Fast) { 82 | elf_loader.SetPath(path); 83 | } 84 | 85 | this->boot_mode = boot_mode; 86 | } 87 | 88 | void System::LoadBIOS() { 89 | std::ifstream file("../bios/bios.bin", std::fstream::in | std::fstream::binary); 90 | 91 | if (!file) { 92 | common::Error("[System] bios does not exist!"); 93 | } 94 | 95 | file.unsetf(std::ios::skipws); 96 | file.read(reinterpret_cast(bios.get()), 0x400000); 97 | file.close(); 98 | 99 | common::Info("[System] bios was successfully loaded!"); 100 | } -------------------------------------------------------------------------------- /src/core/iop/sio2.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/iop/sio2.h" 3 | 4 | namespace iop { 5 | 6 | SIO2::SIO2(INTC& intc) : intc(intc) {} 7 | 8 | void SIO2::Reset() { 9 | control = 0; 10 | send1.fill(0); 11 | send2.fill(0); 12 | send3.fill(0); 13 | fifo.Reset(); 14 | m_command_length = 0; 15 | m_command_index = 0; 16 | m_peripheral_type = PeripheralType::None; 17 | } 18 | 19 | u32 SIO2::ReadRegister(u32 addr) { 20 | switch (addr) { 21 | case 0x1f808264: 22 | // fifo out 23 | common::Log("[iop::SIO2] fifo read %08x", 0); 24 | return fifo.Pop(); 25 | case 0x1f808268: 26 | common::Log("[iop::SIO2] control read %08x", control); 27 | return control; 28 | case 0x1f80826c: 29 | // response status 1 30 | // For now just return disconnected peripheral status 31 | common::Log("[iop::SIO2] recv1 read %08x", 0x1d100); 32 | return 0x1d100; 33 | case 0x1f808270: 34 | // response status 2 35 | common::Log("[iop::SIO2] recv2 read %08x", 0xf); 36 | return 0xf; 37 | case 0x1f808274: 38 | // response status 3 39 | common::Log("[iop::SIO2] recv3 read %08x", 0); 40 | return 0; 41 | case 0x1f808280: 42 | // istat 43 | return 0; 44 | default: 45 | LOG_TODO("[iop::SIO2] handle read %08x", addr); 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | void SIO2::WriteRegister(u32 addr, u32 value) { 52 | if (addr >= 0x1f808200 && addr < 0x1f808240) { 53 | int index = (addr - 0x1f808200) / 4; 54 | common::Log("[iop::SIO2] send3[%d] write %08x", index, value); 55 | send3[index] = value; 56 | return; 57 | } 58 | 59 | if (addr >= 0x1f808240 && addr < 0x1f808260) { 60 | int index = (addr - 0x1f808240) / 8; 61 | if (addr & 0x4) { 62 | common::Log("[iop::SIO2] send2[%d] write %08x", index, value); 63 | send2[index] = value; 64 | } else { 65 | common::Log("[iop::SIO2] send1[%d] write %08x", index, value); 66 | send1[index] = value; 67 | } 68 | return; 69 | } 70 | 71 | switch (addr) { 72 | case 0x1f808260: 73 | upload_command(value); 74 | break; 75 | case 0x1f808268: 76 | control = value; 77 | 78 | if (value & 0x1) { 79 | intc.RequestInterrupt(InterruptSource::SIO2); 80 | control &= ~0x1; 81 | } 82 | 83 | if (value & 0xc) { 84 | // TODO: reset state here for next transfer 85 | m_command_length = 0; 86 | m_command_index = 0; 87 | m_peripheral_type = PeripheralType::None; 88 | common::Log("[iop::SIO2] reset sio2"); 89 | } 90 | 91 | break; 92 | case 0x1f808280: 93 | break; 94 | default: 95 | LOG_TODO("[iop::SIO2] handle write %08x = %08x", addr, value); 96 | } 97 | } 98 | 99 | u8 SIO2::ReadDMA() { 100 | u8 data = fifo.Pop(); 101 | common::Log("[iop::SIO2] dma read %02x", data); 102 | return data; 103 | } 104 | 105 | void SIO2::WriteDMA(u8 data) { 106 | common::Log("[iop::SIO2] dma data write %02x", data); 107 | upload_command(data); 108 | } 109 | 110 | void SIO2::upload_command(u8 data) { 111 | if (m_command_length == 0) { 112 | // Start new transfer. 113 | switch (data) { 114 | case 0x00: 115 | m_peripheral_type = PeripheralType::None; 116 | break; 117 | case 0x01: 118 | m_peripheral_type = PeripheralType::Controller; 119 | break; 120 | case 0x81: 121 | m_peripheral_type = PeripheralType::Memcard; 122 | break; 123 | default: 124 | LOG_TODO("start new transfer with peripheral byte %02x", data); 125 | } 126 | 127 | u32 params = send3[m_command_index]; 128 | common::Log("get params %08x from index in send3 %d", params, m_command_index); 129 | if (params == 0) { 130 | common::Log("[iop::sio2] params is empty!"); 131 | } 132 | 133 | m_command_index++; 134 | m_command_length = (params >> 18) & 0x7f; 135 | common::Log("[iop::sio2] start transfer with length %d", m_command_length); 136 | } 137 | 138 | m_command_length--; 139 | 140 | switch (m_peripheral_type) { 141 | case PeripheralType::Controller: 142 | // Just stub reply for now. 143 | fifo.Push(0x00); 144 | break; 145 | case PeripheralType::Memcard: 146 | // Just stub reply for now. 147 | fifo.Push(0xff); 148 | break; 149 | default: 150 | common::Log("[iop::sio2] handle non-controller transfer"); 151 | } 152 | } 153 | 154 | } // namespace iop -------------------------------------------------------------------------------- /src/core/gif.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/gif.h" 3 | #include "core/system.h" 4 | 5 | GIF::GIF(gs::Context& gs) : gs(gs) {} 6 | 7 | void GIF::Reset() { 8 | ctrl = 0; 9 | fifo.Reset(); 10 | 11 | current_tag.nloop = 0; 12 | current_tag.eop = false; 13 | current_tag.prim = false; 14 | current_tag.prim_data = 0; 15 | current_tag.format = 0; 16 | current_tag.nregs = 0; 17 | current_tag.reglist = 0; 18 | current_tag.reglist_offset = 0; 19 | current_tag.transfers_left = 0; 20 | } 21 | 22 | void GIF::SystemReset() { 23 | common::Log("[GIF] reset gif state"); 24 | } 25 | 26 | void GIF::Run(int cycles) { 27 | while (!fifo.Empty() && cycles--) { 28 | if (current_tag.transfers_left) { 29 | ProcessTag(); 30 | } else { 31 | StartTransfer(); 32 | } 33 | } 34 | } 35 | 36 | u32 GIF::ReadRegister(u32 addr) { 37 | switch (addr) { 38 | case 0x10003020: 39 | common::Log("[GIF] read stat %08x", stat); 40 | return stat; 41 | default: 42 | common::Error("[GIF] handle read %08x", addr); 43 | } 44 | 45 | return 0; 46 | } 47 | 48 | void GIF::WriteRegister(u32 addr, u32 value) { 49 | if (addr >= 0x10006000 && addr < 0x10006010) { 50 | WriteFIFO(value); 51 | return; 52 | } 53 | 54 | switch (addr) { 55 | case 0x10003000: 56 | common::Log("[GIF] write ctrl %02x", value); 57 | 58 | if (value & 0x1) { 59 | SystemReset(); 60 | } 61 | 62 | ctrl = value & 0x9; 63 | break; 64 | default: 65 | common::Error("[GIF] handle write %08x = %08x", addr, value); 66 | } 67 | } 68 | 69 | void GIF::WriteFIFO(u32 value) { 70 | fifo.Push(value); 71 | // common::Log("[GIF] push to fifo %08x", value); 72 | if (fifo.GetLength() == fifo.GetSize()) { 73 | common::Error("[GIF] no more space left in fifo"); 74 | } 75 | } 76 | 77 | void GIF::SendPath3(u128 value) { 78 | // common::Log("[GIF] send path3 %016lx%016lx format %d", value.hi, value.lo); 79 | fifo.Push(value); 80 | } 81 | 82 | void GIF::ProcessPacked(u128 data) { 83 | u8 reg = (current_tag.reglist >> (current_tag.reglist_offset * 4)) & 0xf; 84 | 85 | switch (reg) { 86 | case 0x0: 87 | gs.WriteRegister(0x00, data.uw[0] & 0x7ff); 88 | break; 89 | case 0x1: 90 | common::Log("[GIF] write rgbaq"); 91 | break; 92 | case 0xa: 93 | common::Log("[GIF] write fog"); 94 | break; 95 | case 0xe: 96 | gs.WriteRegister(data.hi & 0xFF, data.lo); 97 | break; 98 | default: 99 | common::Error("[GIF] handle register %02x", reg); 100 | } 101 | 102 | current_tag.reglist_offset++; 103 | 104 | if (current_tag.reglist_offset == current_tag.nregs) { 105 | current_tag.reglist_offset = 0; 106 | } 107 | } 108 | 109 | void GIF::ProcessImage(u128 data) { 110 | gs.WriteHWReg(data.lo); 111 | gs.WriteHWReg(data.hi); 112 | } 113 | 114 | void GIF::StartTransfer() { 115 | // read a new giftag from the fifo 116 | u128 data = fifo.Pop(); 117 | current_tag.nloop = data.lo & 0x7fff; 118 | current_tag.eop = (data.lo >> 15) & 0x1; 119 | current_tag.prim = (data.lo >> 46) & 0x1; 120 | current_tag.prim_data = (data.lo >> 47) & 0x7ff; 121 | current_tag.format = (data.lo >> 58) & 0x3; 122 | current_tag.nregs = (data.lo >> 60) & 0xf; 123 | current_tag.reglist = data.hi; 124 | current_tag.reglist_offset = 0; 125 | 126 | // common::Log("[GIF] start transfer %016lx%016lx format %d", data.hi, data.lo, current_tag.format); 127 | 128 | if (!current_tag.nregs) { 129 | current_tag.nregs = 16; 130 | } 131 | 132 | if (current_tag.prim) { 133 | gs.prim.data = current_tag.prim_data; 134 | } 135 | 136 | gs.rgbaq.q = 1.0f; 137 | 138 | switch (current_tag.format) { 139 | case 0: 140 | current_tag.transfers_left = current_tag.nloop * current_tag.nregs; 141 | break; 142 | case 2: 143 | current_tag.transfers_left = current_tag.nloop; 144 | break; 145 | default: 146 | common::Error("[GIF] handle giftag format start of transfer %d", current_tag.format); 147 | } 148 | } 149 | 150 | void GIF::ProcessTag() { 151 | u128 data = fifo.Pop(); 152 | // common::Log("[GIF] receive giftag in transfer %016lx%016lx", data.hi, data.lo); 153 | switch (current_tag.format) { 154 | case 0: 155 | ProcessPacked(data); 156 | break; 157 | case 2: 158 | ProcessImage(data); 159 | break; 160 | default: 161 | common::Error("[GIF] handle giftag in transfer format %d", current_tag.format); 162 | } 163 | 164 | current_tag.transfers_left--; 165 | } -------------------------------------------------------------------------------- /src/frontend/debugger.cpp: -------------------------------------------------------------------------------- 1 | #include "host_interface.h" 2 | #include "core/core.h" 3 | #include "core/ee/context.h" 4 | #include "core/ee/disassembler.h" 5 | #include "core/iop/context.h" 6 | #include "core/iop/disassembler.h" 7 | 8 | void HostInterface::render_debugger_window() { 9 | ImGui::Begin("Debugger"); 10 | 11 | ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; 12 | if (ImGui::BeginTabBar("DebuggerTabs", tab_bar_flags)) { 13 | if (ImGui::BeginTabItem("EE")) { 14 | render_ee_debugger(); 15 | ImGui::EndTabItem(); 16 | } 17 | 18 | if (ImGui::BeginTabItem("IOP")) { 19 | render_iop_debugger(); 20 | ImGui::EndTabItem(); 21 | } 22 | 23 | if (ImGui::BeginTabItem("Library")) { 24 | render_library_window(); 25 | ImGui::EndTabItem(); 26 | } 27 | 28 | ImGui::EndTabBar(); 29 | } 30 | 31 | ImGui::End(); 32 | } 33 | 34 | void HostInterface::render_ee_debugger() { 35 | ee::Context& ee = core.system.ee; 36 | 37 | for (int i = 0; i < 16; i++) { 38 | int reg = i * 2; 39 | 40 | ImGui::Text("%s", ee::GetRegisterName(reg).c_str()); 41 | ImGui::SameLine(90); 42 | 43 | u128 first = ee.GetReg(reg); 44 | 45 | ImGui::Text("%016llx%016llx", first.hi, first.lo); 46 | ImGui::SameLine(350); 47 | 48 | ImGui::Text("%s", ee::GetRegisterName(reg + 1).c_str()); 49 | ImGui::SameLine(440); 50 | 51 | u128 second = ee.GetReg(reg + 1); 52 | 53 | ImGui::Text("%016llx%016llx", second.hi, second.lo); 54 | } 55 | 56 | ImGui::Text("pc"); 57 | ImGui::SameLine(90); 58 | ImGui::Text("%08x", ee.pc); 59 | 60 | ImGui::Text("npc"); 61 | ImGui::SameLine(90); 62 | ImGui::Text("%08x", ee.npc); 63 | 64 | ImGui::Text("lo"); 65 | ImGui::SameLine(90); 66 | ImGui::Text("%016llx", ee.lo); 67 | 68 | ImGui::Text("hi"); 69 | ImGui::SameLine(90); 70 | ImGui::Text("%016llx", ee.hi); 71 | 72 | ImGui::Text("lo1"); 73 | ImGui::SameLine(90); 74 | ImGui::Text("%016llx", ee.lo1); 75 | 76 | ImGui::Text("hi1"); 77 | ImGui::SameLine(90); 78 | ImGui::Text("%016llx", ee.hi1); 79 | 80 | ImGui::Text("sa"); 81 | ImGui::SameLine(90); 82 | ImGui::Text("%016llx", ee.sa); 83 | 84 | ImGui::Separator(); 85 | 86 | u32 pc = ee.pc; 87 | u32 addr = pc - ((m_ee_disassembly_size - 1) / 2) * 4; 88 | 89 | for (int i = 0; i < m_ee_disassembly_size; i++) { 90 | ee::Instruction inst = ee.read(addr); 91 | 92 | // Address must be a code address. 93 | if ((addr >= 0x00000000 && addr < 0x2000000) || (addr >= 0x1fc00000 && addr < 0x20000000)) { 94 | if (addr == pc) { 95 | ImGui::TextColored(ImVec4(0, 1, 0, 1), "%08x: %08x %s", addr, inst.data, ee::DisassembleInstruction(inst, addr).c_str()); 96 | } else { 97 | ImGui::Text("%08x: %08x %s", addr, inst.data, ee::DisassembleInstruction(inst, addr).c_str()); 98 | } 99 | } else { 100 | ImGui::Text("%08x: n/a n/a", addr); 101 | } 102 | 103 | addr += 4; 104 | } 105 | } 106 | 107 | void HostInterface::render_iop_debugger() { 108 | iop::Context& iop = core.system.iop; 109 | 110 | for (int i = 0; i < 32; i++) { 111 | ImGui::Text("%s", iop::GetRegisterName(i).c_str()); 112 | ImGui::SameLine(90); 113 | ImGui::Text("%08x", iop.GetReg(i)); 114 | } 115 | 116 | ImGui::Text("pc"); 117 | ImGui::SameLine(90); 118 | ImGui::Text("%08x", iop.pc); 119 | 120 | ImGui::Text("npc"); 121 | ImGui::SameLine(90); 122 | ImGui::Text("%08x", iop.npc); 123 | 124 | ImGui::Separator(); 125 | 126 | ImGui::Text("status: %08x", iop.cop0.status.data); 127 | ImGui::Text("cause: %08x", iop.cop0.cause.data); 128 | ImGui::Text("epc: %08x", iop.cop0.epc); 129 | ImGui::Text("prid: %08x", iop.cop0.prid); 130 | 131 | ImGui::Separator(); 132 | 133 | u32 pc = iop.pc; 134 | u32 addr = pc - ((m_iop_disassembly_size - 1) / 2) * 4; 135 | 136 | for (int i = 0; i < m_iop_disassembly_size; i++) { 137 | iop::Instruction inst = iop.Read(addr); 138 | 139 | // Address must be a code address. 140 | if ((addr >= 0x00000000 && addr < 0x200000) || (addr >= 0x1fc00000 && addr < 0x20000000)) { 141 | if (addr == pc) { 142 | ImGui::TextColored(ImVec4(0, 1, 0, 1), "%08x: %08x %s", addr, inst.data, iop::DisassembleInstruction(inst, addr).c_str()); 143 | } else { 144 | ImGui::Text("%08x: %08x %s", addr, inst.data, iop::DisassembleInstruction(inst, addr).c_str()); 145 | } 146 | } else { 147 | ImGui::Text("%08x: n/a n/a", addr); 148 | } 149 | 150 | 151 | 152 | addr += 4; 153 | } 154 | } -------------------------------------------------------------------------------- /src/core/ee/interpreter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/ee/decoder.h" 4 | #include "core/ee/executor.h" 5 | 6 | namespace ee { 7 | 8 | struct Context; 9 | 10 | struct Interpreter : public Executor { 11 | Interpreter(Context& ctx); 12 | 13 | void Reset(); 14 | void Run(int cycles); 15 | 16 | void DoException(u32 target, ExceptionType exception); 17 | void RaiseInterrupt(int signal, bool value); 18 | void CheckInterrupts(); 19 | bool InterruptsEnabled(); 20 | void LogState(); 21 | std::string GetSyscallInfo(int index); 22 | void LogInstruction(); 23 | 24 | // TODO: separate instruction handlers into separate files for organisation 25 | void mfc0(); 26 | void sll(); 27 | void slti(); 28 | void bne(); 29 | void lui(); 30 | void ori(); 31 | void jr(); 32 | void mtc0(); 33 | void sync(); 34 | void addiu(); 35 | void sw(); 36 | void tlbwi(); 37 | void jalr(); 38 | void sd(); 39 | void daddu(); 40 | void jal(); 41 | void andi(); 42 | void beq(); 43 | void orr(); 44 | void mult(); 45 | void divu(); 46 | void beql(); 47 | void break_exception(); 48 | void mflo(); 49 | void sltiu(); 50 | void bnel(); 51 | void srl(); 52 | void lb(); 53 | void swc1(); 54 | void lbu(); 55 | void sra(); 56 | void ld(); 57 | void j(); 58 | void lw(); 59 | void sb(); 60 | void blez(); 61 | void slt(); 62 | void addu(); 63 | void sltu(); 64 | void andd(); 65 | void bgez(); 66 | void lhu(); 67 | void movn(); 68 | void subu(); 69 | void bltz(); 70 | void div(); 71 | void mfhi(); 72 | void bgtz(); 73 | void sh(); 74 | void divu1(); 75 | void mflo1(); 76 | void dsrav(); 77 | void dsll32(); 78 | void dsra32(); 79 | void xori(); 80 | void mult1(); 81 | void movz(); 82 | void dsllv(); 83 | void daddiu(); 84 | void sq(); 85 | void lq(); 86 | void lh(); 87 | void por(); 88 | void cache(); 89 | void sllv(); 90 | void dsll(); 91 | void srav(); 92 | void nor(); 93 | void cfc2(); 94 | void ctc2(); 95 | void lwu(); 96 | void ldl(); 97 | void ldr(); 98 | void sdl(); 99 | void sdr(); 100 | void dsrl(); 101 | void srlv(); 102 | void dsrl32(); 103 | void di(); 104 | void eret(); 105 | void syscall_exception(); 106 | void bltzl(); 107 | void bgezl(); 108 | void ei(); 109 | void dsubu(); 110 | void plzcw(); 111 | void mtc1(); 112 | void adda_s(); 113 | void ctc1(); 114 | void mfhi1(); 115 | void mfsa(); 116 | void mthi(); 117 | void mthi1(); 118 | void mtlo(); 119 | void mtlo1(); 120 | void mtsa(); 121 | void cfc1(); 122 | void madd_s(); 123 | void lwc1(); 124 | void dsra(); 125 | void dsrlv(); 126 | void xorr(); 127 | void bgezal(); 128 | void bgezall(); 129 | void bgtzl(); 130 | void blezl(); 131 | void bltzal(); 132 | void bltzall(); 133 | void lwl(); 134 | void lwr(); 135 | void swl(); 136 | void swr(); 137 | void pref(); 138 | void multu(); 139 | void pcpyld(); 140 | void pnor(); 141 | void pand(); 142 | void pcpyud(); 143 | void pcpyh(); 144 | void div1(); 145 | void madd(); 146 | void maddu(); 147 | void madd1(); 148 | void maddu1(); 149 | void multu1(); 150 | void mov_s(); 151 | void abs_s(); 152 | void add_s(); 153 | void max_s(); 154 | void min_s(); 155 | void neg_s(); 156 | void sub_s(); 157 | void suba_s(); 158 | void pabsh(); 159 | void pabsw(); 160 | void paddb(); 161 | void paddh(); 162 | void paddsb(); 163 | void paddsh(); 164 | void paddsw(); 165 | void paddub(); 166 | void padduh(); 167 | void padduw(); 168 | void paddw(); 169 | void padsbh(); 170 | void pmaxh(); 171 | void pmaxw(); 172 | void pminh(); 173 | void pminw(); 174 | void psubb(); 175 | void psubh(); 176 | void psubsb(); 177 | void psubsh(); 178 | void psubsw(); 179 | void psubub(); 180 | void psubuh(); 181 | void psubuw(); 182 | void psubw(); 183 | void pceqb(); 184 | void pceqh(); 185 | void pceqw(); 186 | void pcgtb(); 187 | void pcgth(); 188 | void pcgtw(); 189 | void pxor(); 190 | void bc1f(); 191 | void c_eq_s(); 192 | void c_f_s(); 193 | void bc1fl(); 194 | void bc1t(); 195 | void bc1tl(); 196 | void c_le_s(); 197 | void c_lt_s(); 198 | void illegal_instruction(); 199 | void stub_instruction(); 200 | 201 | private: 202 | void Branch(bool cond); 203 | void BranchLikely(bool cond); 204 | void Jump(u32 target); 205 | 206 | bool branch_delay; 207 | bool branch; 208 | 209 | Decoder decoder; 210 | Instruction inst; 211 | Context& ctx; 212 | }; 213 | 214 | } // namespace ee -------------------------------------------------------------------------------- /src/core/ee/timers.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/ee/timers.h" 3 | 4 | namespace ee { 5 | 6 | Timers::Timers(INTC& intc) : intc(intc) {} 7 | 8 | void Timers::Reset() { 9 | for (int i = 0; i < 4; i++) { 10 | channels[i].counter = 0; 11 | channels[i].control = 0; 12 | channels[i].compare = 0; 13 | channels[i].hold = 0; 14 | channels[i].cycles = 0; 15 | channels[i].cycles_per_tick = 0; 16 | } 17 | } 18 | 19 | void Timers::Run(int cycles) { 20 | for (int i = 0; i < 4; i++) { 21 | if (channels[i].control & (1 << 7)) { 22 | channels[i].cycles += cycles; 23 | Increment(i); 24 | } 25 | } 26 | } 27 | 28 | u32 Timers::ReadRegister(u32 addr) { 29 | int index = (addr >> 11) & 0x3; 30 | 31 | switch (addr & 0xFF) { 32 | case 0x10: 33 | return channels[index].control; 34 | case 0x14: 35 | return 0; 36 | default: 37 | common::Error("[Timers] handle %02x", addr & 0xFF); 38 | } 39 | 40 | return 0; 41 | } 42 | 43 | void Timers::WriteRegister(u32 addr, u32 data) { 44 | int index = (addr >> 11) & 0x3; 45 | 46 | switch (addr & 0xFF) { 47 | case 0x00: 48 | common::Log("[Timer] T%d TN_COUNT write %04x", index, data); 49 | channels[index].counter = data; 50 | break; 51 | case 0x4: 52 | break; 53 | case 0x10: 54 | common::Log("[Timer] T%d TN_MODE write %04x", index, data); 55 | channels[index].control = data; 56 | 57 | // writing 1 to bit 10 or 11 clears them 58 | if (data & (1 << 10)) { 59 | channels[index].control &= ~(1 << 10); 60 | } 61 | 62 | if (data & (1 << 11)) { 63 | channels[index].control &= ~(1 << 11); 64 | } 65 | 66 | // update how many timer cycles are required to increment the corresponding channel 67 | // counter by 1 68 | switch (channels[index].control & 0x3) { 69 | case 0: 70 | // bus clock 71 | channels[index].cycles_per_tick = 1; 72 | break; 73 | case 1: 74 | // bus clock / 16 75 | channels[index].cycles_per_tick = 16; 76 | break; 77 | case 2: 78 | // bus block / 256 79 | channels[index].cycles_per_tick = 256; 80 | break; 81 | case 3: 82 | // hblank 83 | channels[index].cycles_per_tick = 9370; 84 | break; 85 | } 86 | 87 | break; 88 | case 0x14: 89 | break; 90 | case 0x20: 91 | common::Log("[Timer] T%d TN_COMP write %04x", index, data); 92 | channels[index].compare = data; 93 | break; 94 | case 0x30: 95 | common::Log("[Timer] T%d TN_HOLD write %04x", index, data); 96 | channels[index].hold = data; 97 | break; 98 | default: 99 | common::Error("[Timer] handle address %08x", addr); 100 | } 101 | } 102 | 103 | u32 Timers::ReadChannel(u32 addr) { 104 | int reg = (addr >> 4) & 0xF; 105 | 106 | switch (reg) { 107 | default: 108 | common::Error("handle reg %d", reg); 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | void Timers::Increment(int index) { 115 | // if enough ticks have passed then we can increment the timer counter by 1 116 | if (channels[index].cycles >= channels[index].cycles_per_tick) { 117 | channels[index].cycles -= channels[index].cycles_per_tick; 118 | } else { 119 | // don't do any further calculations as 120 | // counter wouldn't have changed 121 | return; 122 | } 123 | 124 | channels[index].counter++; 125 | 126 | if (channels[index].counter == channels[index].compare) { 127 | if (channels[index].control & (1 << 6)) { 128 | channels[index].counter = 0; 129 | } 130 | 131 | if (channels[index].control & (1 << 8)) { 132 | common::Error("handle timer interrupt with compare"); 133 | } 134 | } 135 | 136 | if (channels[index].counter > 0xFFFF) { 137 | channels[index].counter = 0; 138 | 139 | // timer interrupts are edge triggered, 140 | // meaning they can only be requested 141 | // if either interrupt bit goes from 0 to 1 142 | if ((channels[index].control & (1 << 9)) && !(channels[index].control & (1 << 11))) { 143 | // set the overflow interrupt flag and request a timer interrupt 144 | channels[index].control |= (1 << 11); 145 | 146 | common::Log("[Timer] T%d request overflow interrupt", index); 147 | 148 | switch (index) { 149 | case 0: 150 | intc.RequestInterrupt(InterruptSource::Timer0); 151 | break; 152 | case 1: 153 | intc.RequestInterrupt(InterruptSource::Timer1); 154 | break; 155 | case 2: 156 | intc.RequestInterrupt(InterruptSource::Timer2); 157 | break; 158 | case 3: 159 | intc.RequestInterrupt(InterruptSource::Timer3); 160 | break; 161 | } 162 | } 163 | } 164 | } 165 | 166 | } // namespace ee -------------------------------------------------------------------------------- /src/core/ee/cop1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common/log.h" 3 | #include "common/bits.h" 4 | #include "core/ee/cop1.h" 5 | 6 | namespace ee { 7 | 8 | void COP1::Reset() { 9 | for (int i = 0; i < 32; i++) { 10 | fpr[i].u = 0; 11 | } 12 | 13 | accumulator.u = 0; 14 | fcr0.data = 0x2e00; 15 | fcr31.data = 0x1000001; 16 | } 17 | 18 | u32 COP1::GetReg(int reg) { 19 | return fpr[reg].u; 20 | } 21 | 22 | void COP1::SetReg(int reg, u32 value) { 23 | fpr[reg].u = value; 24 | } 25 | 26 | u32 COP1::GetControlReg(int reg) { 27 | switch (reg) { 28 | case 0: 29 | return fcr0.data; 30 | case 31: 31 | return fcr31.data | 0x1000001; 32 | default: 33 | common::Error("[ee::COP1] handle control reg %d", reg); 34 | } 35 | 36 | return 0; 37 | } 38 | 39 | void COP1::SetControlReg(int reg, u32 value) { 40 | switch (reg) { 41 | case 0: 42 | fcr0.data = value; 43 | break; 44 | case 31: 45 | fcr31.data = value | 0x1000001; 46 | break; 47 | default: 48 | common::Error("[ee::COP1] handle control reg %d", reg); 49 | } 50 | } 51 | 52 | // converts a u32 representation of a ps2 float 53 | // to one that conforms to IEEE 754 floats 54 | f32 COP1::AsFloat(u32 value) { 55 | // we must handle the cases when exponent == 255 or 0 56 | switch (value & 0x7f800000) { 57 | case 0: 58 | // denormals automatically get set to 0, while preserving their sign 59 | value &= 0x80000000; 60 | return common::BitCast(value); 61 | case 0x7f800000: 62 | // ps2 floats with the exponent as 255 can have numbers larger than what is possible 63 | // with IEEE 754, so we just set to the highest possible float 64 | value = (value & 0x80000000) | 0x7f7fffff; 65 | return common::BitCast(value); 66 | default: 67 | return common::BitCast(value); 68 | } 69 | } 70 | 71 | bool COP1::IsInfinity(const Float& fpr) { 72 | return (fpr.exponent == 0xff) && (fpr.fraction == 0); 73 | } 74 | 75 | void COP1::CheckOverflow(Float& fpr, bool set_flags) { 76 | if (IsInfinity(fpr)) { 77 | fpr.u = (fpr.u & 0x80000000) | 0x7f7fffff; 78 | 79 | if (set_flags) { 80 | fcr31.o = true; 81 | fcr31.so = true; 82 | } 83 | } else { 84 | if (set_flags) { 85 | fcr31.o = false; 86 | } 87 | } 88 | } 89 | 90 | void COP1::CheckUnderflow(Float& fpr, bool set_flags) { 91 | if ((fpr.exponent == 0) && (fpr.fraction != 0)) { 92 | fpr.u &= 0x80000000; 93 | if (set_flags) { 94 | fcr31.u = true; 95 | fcr31.su = true; 96 | } 97 | } else { 98 | if (set_flags) { 99 | fcr31.u = false; 100 | } 101 | } 102 | } 103 | 104 | void COP1::adda_s(Instruction inst) { 105 | accumulator.f = AsFloat(fpr[inst.fs].u) + AsFloat(fpr[inst.ft].u); 106 | CheckOverflow(accumulator, true); 107 | CheckUnderflow(accumulator, true); 108 | } 109 | 110 | void COP1::madd_s(Instruction inst) { 111 | fpr[inst.fd].f = AsFloat(accumulator.u) + (AsFloat(fpr[inst.fs].u) * AsFloat(fpr[inst.ft].u)); 112 | CheckOverflow(fpr[inst.fd], true); 113 | CheckUnderflow(fpr[inst.fd], true); 114 | } 115 | 116 | void COP1::mov_s(Instruction inst) { 117 | fpr[inst.fd].u = fpr[inst.fs].u; 118 | } 119 | 120 | void COP1::abs_s(Instruction inst) { 121 | // forcibly make positive sign 122 | fpr[inst.fd].u = fpr[inst.fs].u; 123 | fpr[inst.fd].sign = 0; 124 | fcr31.o = false; 125 | fcr31.u = false; 126 | } 127 | 128 | void COP1::add_s(Instruction inst) { 129 | fpr[inst.fd].f = AsFloat(fpr[inst.fs].u) + AsFloat(fpr[inst.ft].u); 130 | CheckOverflow(fpr[inst.fd], true); 131 | CheckUnderflow(fpr[inst.fd], true); 132 | } 133 | 134 | void COP1::max_s(Instruction inst) { 135 | const auto lhs = fpr[inst.fs].s; 136 | const auto rhs = fpr[inst.ft].s; 137 | 138 | if (lhs < 0 && rhs < 0) { 139 | fpr[inst.fd].s = std::min(lhs, rhs); 140 | } else { 141 | fpr[inst.fd].s = std::max(lhs, rhs); 142 | } 143 | 144 | fcr31.o = false; 145 | fcr31.u = false; 146 | } 147 | 148 | void COP1::min_s(Instruction inst) { 149 | const auto lhs = fpr[inst.fs].s; 150 | const auto rhs = fpr[inst.ft].s; 151 | 152 | if (lhs < 0 && rhs < 0) { 153 | fpr[inst.fd].s = std::max(lhs, rhs); 154 | } else { 155 | fpr[inst.fd].s = std::min(lhs, rhs); 156 | } 157 | 158 | fcr31.o = false; 159 | fcr31.u = false; 160 | } 161 | 162 | void COP1::neg_s(Instruction inst) { 163 | fpr[inst.fd].f = -fpr[inst.fs].f; 164 | fcr31.o = false; 165 | fcr31.u = false; 166 | } 167 | 168 | void COP1::sub_s(Instruction inst) { 169 | fpr[inst.fd].f = AsFloat(fpr[inst.fs].u) - AsFloat(fpr[inst.ft].u); 170 | CheckOverflow(fpr[inst.fd], true); 171 | CheckUnderflow(fpr[inst.fd], true); 172 | } 173 | 174 | void COP1::suba_s(Instruction inst) { 175 | accumulator.f = AsFloat(fpr[inst.fs].u) - AsFloat(fpr[inst.ft].u); 176 | CheckOverflow(accumulator, true); 177 | CheckUnderflow(accumulator, true); 178 | } 179 | 180 | void COP1::c_eq_s(Instruction inst) { 181 | fcr31.c = AsFloat(fpr[inst.fs].u) == AsFloat(fpr[inst.ft].u); 182 | } 183 | 184 | void COP1::c_f_s() { 185 | fcr31.c = false; 186 | } 187 | 188 | void COP1::c_le_s(Instruction inst) { 189 | fcr31.c = AsFloat(fpr[inst.fs].u) <= AsFloat(fpr[inst.ft].u); 190 | } 191 | 192 | void COP1::c_lt_s(Instruction inst) { 193 | fcr31.c = AsFloat(fpr[inst.fs].u) < AsFloat(fpr[inst.ft].u); 194 | } 195 | 196 | } // namespace ee -------------------------------------------------------------------------------- /src/core/iop/timers.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "core/iop/timers.h" 3 | 4 | namespace iop { 5 | 6 | Timers::Timers(INTC& intc) : m_intc(intc) {} 7 | 8 | void Timers::reset() { 9 | for (int i = 0; i < 6; i++) { 10 | m_channels[i].counter = 0; 11 | m_channels[i].cycles = 0; 12 | m_channels[i].cycles_per_tick = 1; 13 | m_channels[i].mode.data = 0; 14 | m_channels[i].target = 0; 15 | } 16 | } 17 | 18 | // TODO: put timers on scheduler for performance 19 | void Timers::run(int cycles) { 20 | for (int i = 0; i < 6; i++) { 21 | run_channel(i, cycles); 22 | } 23 | } 24 | 25 | u32 Timers::read(u32 addr) { 26 | int index = calculate_channel_index(addr); 27 | auto& channel = m_channels[index]; 28 | 29 | switch (addr & 0xf) { 30 | case 0x0: 31 | common::Log("[iop::Timers] channel %d counter read %08x", index, static_cast(channel.counter)); 32 | return channel.counter; 33 | case 0x4: 34 | // reads clear the two raised interrupt flags 35 | channel.mode.compare_irq_raised = false; 36 | channel.mode.overflow_irq_raised = false; 37 | 38 | common::Log("[iop::Timers] channel %d mode read %08x", index, channel.mode.data); 39 | return channel.mode.data; 40 | case 0x8: 41 | common::Log("[iop::Timers] channel %d target read %08x", index, channel.target); 42 | return channel.target; 43 | default: 44 | LOG_TODO("unknown timer read %08x", addr); 45 | } 46 | } 47 | 48 | void Timers::write(u32 addr, u32 data) { 49 | int index = calculate_channel_index(addr); 50 | auto& channel = m_channels[index]; 51 | 52 | switch (addr & 0xf) { 53 | case 0x0: 54 | common::Log("[iop::Timers] channel %d counter write %08x", index, data); 55 | channel.counter = data; 56 | break; 57 | case 0x4: { 58 | common::Log("[iop::Timers] channel %d mode write %08x", index, data); 59 | channel.mode.data = data; 60 | 61 | if (channel.mode.gate_enable) { 62 | LOG_TODO_NO_ARGS("handle gate enabled"); 63 | } 64 | 65 | // Enable interrupts. 66 | channel.mode.irqs_enabled = true; 67 | 68 | // Calculate clock source. 69 | // TODO: write hardware test for this later. 70 | if (channel.mode.use_external_signal) { 71 | switch (index) { 72 | case 0: 73 | LOG_TODO_NO_ARGS("handle pixel clock source"); 74 | case 1: 75 | case 3: 76 | // TODO: is hblank every 2350 cycles? 77 | channel.cycles_per_tick = 2350; 78 | break; 79 | default: 80 | channel.cycles_per_tick = 1; 81 | } 82 | } 83 | 84 | // Get prescaler. 85 | u32 prescaler = calculate_channel_prescaler(index); 86 | 87 | // Multiply cycles per tick with prescaler. 88 | channel.cycles_per_tick *= prescaler; 89 | 90 | common::Log("timer %d prescaler set to %d cycles per tick %d", index, prescaler, channel.cycles_per_tick); 91 | 92 | channel.counter = 0; 93 | channel.cycles = 0; 94 | break; 95 | } 96 | case 0x8: 97 | common::Log("[iop::Timers] channel %d target write %08x", index, data); 98 | channel.target = data; 99 | 100 | if (index < 4) { 101 | // First 3 channels only use first 16 bits of target. 102 | channel.target &= 0xffff; 103 | } 104 | 105 | // If bit 7 of mode is not set, 106 | // then writes to target set bit 10 of 107 | // mode to 1. 108 | if (!channel.mode.levl) { 109 | channel.mode.irqs_enabled = true; 110 | } 111 | 112 | break; 113 | default: 114 | LOG_TODO("unknown timer write %08x", addr); 115 | } 116 | } 117 | 118 | int Timers::calculate_channel_index(u32 addr) { 119 | int index = (addr >> 4) & 0xf; 120 | if (index >= 8) { 121 | return index - 5; 122 | } else { 123 | return index; 124 | } 125 | } 126 | 127 | u32 Timers::calculate_channel_prescaler(int index) { 128 | auto& channel = m_channels[index]; 129 | 130 | if (index == 2 && channel.mode.timer2_prescaler) { 131 | return 8; 132 | } else if (index >= 4) { 133 | switch (channel.mode.timer45_prescaler) { 134 | case 0: 135 | return 1; 136 | case 1: 137 | return 8; 138 | case 2: 139 | return 16; 140 | case 3: 141 | return 256; 142 | default: 143 | return 1; 144 | } 145 | } else { 146 | return 1; 147 | } 148 | } 149 | 150 | void Timers::run_channel(int index, int cycles) { 151 | auto& channel = m_channels[index]; 152 | channel.cycles += cycles; 153 | 154 | while (channel.cycles >= channel.cycles_per_tick) { 155 | channel.cycles -= channel.cycles_per_tick; 156 | channel.counter++; 157 | 158 | // Check for timer overflows. 159 | if (overflow_occured(index) && channel.mode.overflow_irq && !channel.mode.overflow_irq_raised) { 160 | common::Log("[iop::Timers] overflow irq occured for channel %d with counter %08x", index, static_cast(channel.counter)); 161 | raise_irq(index); 162 | channel.mode.overflow_irq_raised = true; 163 | } 164 | 165 | // Mask counter and check for compare interrupts. 166 | if (index < 3) { 167 | channel.counter &= 0xffff; 168 | } else { 169 | channel.counter &= 0xffffffff; 170 | } 171 | 172 | if (channel.counter == channel.target && channel.mode.compare_irq && !channel.mode.compare_irq_raised) { 173 | common::Log("[iop::Timers] compare irq occured for channel %d with counter %08x", index, static_cast(channel.counter)); 174 | raise_irq(index); 175 | channel.mode.compare_irq_raised = true; 176 | 177 | if (channel.mode.zero_return) { 178 | channel.counter = 0; 179 | } 180 | } 181 | } 182 | } 183 | 184 | bool Timers::overflow_occured(int index) { 185 | auto& channel = m_channels[index]; 186 | if (index < 3) { 187 | // Channels 0..2 use 16-bit counter. 188 | return channel.counter > 0xffff; 189 | } else { 190 | // Channels 3..5 use 32-bit counter. 191 | return channel.counter > 0xffffffff; 192 | } 193 | } 194 | 195 | void Timers::raise_irq(int index) { 196 | auto& channel = m_channels[index]; 197 | int irq = index < 3 ? 4 + index : 11 + index; 198 | 199 | m_intc.RequestInterrupt(static_cast(irq)); 200 | 201 | if (!channel.mode.repeat_irq) { 202 | common::Log("[iop::Timers] repeat irq disabled for channel %d, so disable irq", index); 203 | channel.mode.irqs_enabled = false; 204 | } else if (channel.mode.levl) { 205 | common::Log("[iop::Timers] toggle irq enabled for channel %d, so toggle irq", index); 206 | channel.mode.irqs_enabled ^= true; 207 | } 208 | } 209 | 210 | } // namespace iop -------------------------------------------------------------------------------- /src/core/iop/context.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "common/memory.h" 3 | #include "core/iop/context.h" 4 | #include "core/system.h" 5 | 6 | namespace iop { 7 | 8 | Context::Context(System& system) : dmac(system, sio2), intc(*this), timers(intc), sio2(intc), system(system), interpreter(*this) {} 9 | 10 | void Context::Reset() { 11 | gpr.fill(0); 12 | pc = 0xbfc00000; 13 | npc = 0; 14 | hi = 0; 15 | lo = 0; 16 | 17 | cop0.Reset(); 18 | cdvd.Reset(); 19 | dmac.Reset(); 20 | intc.Reset(); 21 | timers.reset(); 22 | sio2.Reset(); 23 | interpreter.Reset(); 24 | 25 | // do initial hardcoded mappings 26 | vtlb.Reset(); 27 | vtlb.Map(system.iop_ram->data(), 0x00000000, 0x200000, 0x1fffff); 28 | vtlb.Map(system.iop_ram->data(), 0x80000000, 0x200000, 0x1fffff); 29 | vtlb.Map(system.bios->data(), 0x9fc00000, 0x400000, 0x3fffff); 30 | vtlb.Map(system.iop_ram->data(), 0xa0000000, 0x200000, 0x1fffff); 31 | vtlb.Map(system.bios->data(), 0xbfc00000, 0x400000, 0x3fffff); 32 | } 33 | 34 | void Context::Run(int cycles) { 35 | interpreter.Run(cycles); 36 | dmac.Run(cycles); 37 | timers.run(cycles); 38 | } 39 | 40 | template u8 Context::Read(VirtualAddress vaddr); 41 | template u16 Context::Read(VirtualAddress vaddr); 42 | template u32 Context::Read(VirtualAddress vaddr); 43 | template 44 | T Context::Read(VirtualAddress vaddr) { 45 | auto pointer = vtlb.Lookup(vaddr); 46 | if (pointer) { 47 | return common::Read(pointer); 48 | } else { 49 | return ReadIO(vaddr & 0x1fffffff); 50 | } 51 | } 52 | 53 | template void Context::Write(VirtualAddress vaddr, u8 value); 54 | template void Context::Write(VirtualAddress vaddr, u16 value); 55 | template void Context::Write(VirtualAddress vaddr, u32 value); 56 | template 57 | void Context::Write(VirtualAddress vaddr, T value) { 58 | auto pointer = vtlb.Lookup(vaddr); 59 | if (pointer) { 60 | return common::Write(pointer, value); 61 | } else { 62 | WriteIO(vaddr & 0x1fffffff, value); 63 | } 64 | } 65 | 66 | void Context::RaiseInterrupt(bool value) { 67 | interpreter.RaiseInterrupt(value); 68 | if (value) { 69 | cop0.cause.data |= 1 << 10; 70 | } else { 71 | cop0.cause.data &= ~(1 << 10); 72 | } 73 | } 74 | 75 | u32 Context::ReadIO(u32 paddr) { 76 | if (paddr >= 0x1f402004 && paddr < 0x1f402019) { 77 | return cdvd.ReadRegister(paddr); 78 | } else if (paddr >= 0x1f801070 && paddr < 0x1f801079) { 79 | return intc.ReadRegister(paddr); 80 | } else if (paddr >= 0x1f801080 && paddr < 0x1f801100) { 81 | return dmac.ReadRegister(paddr); 82 | } else if (paddr >= 0x1f801100 && paddr < 0x1f801130) { 83 | return timers.read(paddr); 84 | } else if (paddr >= 0x1f801480 && paddr < 0x1f8014b0) { 85 | return timers.read(paddr); 86 | } else if (paddr >= 0x1f801500 && paddr < 0x1f80155f) { 87 | return dmac.ReadRegister(paddr); 88 | } else if (paddr >= 0x1f801570 && paddr < 0x1f80157f) { 89 | return dmac.ReadRegister(paddr); 90 | } else if ((paddr >> 24) == 0x1e) { 91 | // not sure what this is 92 | return 0; 93 | } else if (paddr >= 0x1f900000 && paddr < 0x1f900400) { 94 | return 0; 95 | } else if (paddr >= 0x1f900760 && paddr < 0x1f900770) { 96 | return 0; 97 | } else if (paddr >= 0x1f900400 && paddr < 0x1f900800) { 98 | return system.spu2.ReadRegister(paddr); 99 | } else if (paddr >= 0x1f808200 && paddr < 0x1f808284) { 100 | return sio2.ReadRegister(paddr); 101 | } 102 | 103 | switch (paddr) { 104 | case 0x1f80100c: 105 | case 0x1f801400: 106 | case 0x1f801010: 107 | case 0x1f801450: 108 | case 0x1ffe0130: 109 | case 0x1f801414: 110 | return 0; 111 | case 0x1d000010: 112 | return system.sif.ReadSMCOM(); 113 | case 0x1d000020: 114 | return system.sif.ReadMSFLAG(); 115 | case 0x1d000030: 116 | return system.sif.ReadSMFLAG(); 117 | case 0x1d000040: 118 | return system.sif.ReadControl(); 119 | case 0x1d000060: 120 | return system.sif.bd6; 121 | default: 122 | common::Log("[iop::Context] handle io read %08x", paddr); 123 | } 124 | 125 | return 0; 126 | } 127 | 128 | void Context::WriteIO(u32 paddr, u32 value) { 129 | if (paddr >= 0x1f801080 && paddr < 0x1f801100) { 130 | dmac.WriteRegister(paddr, value); 131 | return; 132 | } else if (paddr >= 0x1f801100 && paddr < 0x1f801130) { 133 | timers.write(paddr, value); 134 | return; 135 | } else if (paddr >= 0x1f801480 && paddr < 0x1f8014b0) { 136 | timers.write(paddr, value); 137 | return; 138 | } else if (paddr >= 0x1f801500 && paddr < 0x1f80155f) { 139 | dmac.WriteRegister(paddr, value); 140 | return; 141 | } else if (paddr >= 0x1f801570 && paddr < 0x1f80157f) { 142 | dmac.WriteRegister(paddr, value); 143 | return; 144 | } else if (paddr >= 0x1f402004 && paddr < 0x1f402019) { 145 | cdvd.WriteRegister(paddr, value); 146 | return; 147 | } else if (paddr >= 0x1f801070 && paddr < 0x1f801079) { 148 | intc.WriteRegister(paddr, value); 149 | return; 150 | } else if (paddr >= 0x1f900000 && paddr < 0x1f900400) { 151 | system.spu.WriteRegister(paddr, value); 152 | return; 153 | } else if (paddr >= 0x1f900760 && paddr < 0x1f900770) { 154 | system.spu.WriteRegister(paddr, value); 155 | return; 156 | } else if (paddr >= 0x1f900400 && paddr < 0x1f900800) { 157 | system.spu2.WriteRegister(paddr, value); 158 | return; 159 | } else if (paddr >= 0x1f808200 && paddr < 0x1f808284) { 160 | sio2.WriteRegister(paddr, value); 161 | return; 162 | } 163 | 164 | switch (paddr) { 165 | case 0x1f801010: 166 | // sif2/gpu ssbus 167 | break; 168 | case 0x1f801450: 169 | // random config register 170 | break; 171 | case 0x1f801004: 172 | case 0x1f80100c: 173 | case 0x1f801014: 174 | case 0x1f801018: 175 | case 0x1f80101c: 176 | case 0x1f801020: 177 | case 0x1f801400: 178 | case 0x1f801404: 179 | case 0x1f801408: 180 | case 0x1f80140c: 181 | case 0x1f801410: 182 | case 0x1f801414: 183 | case 0x1f801418: 184 | case 0x1f80141c: 185 | case 0x1f801420: 186 | case 0x1f802070: 187 | case 0x1f801060: 188 | case 0x1f801560: 189 | case 0x1f801564: 190 | case 0x1f801568: 191 | case 0x1ffe0130: 192 | case 0x1ffe0140: 193 | case 0x1ffe0144: 194 | case 0x1f8015f0: 195 | // undocumented 196 | break; 197 | case 0x1d000010: 198 | system.sif.WriteSMCOM(value); 199 | break; 200 | case 0x1d000020: 201 | system.sif.ResetMSFLAG(value); 202 | break; 203 | case 0x1d000030: 204 | system.sif.SetSMFLAG(value); 205 | break; 206 | case 0x1d000040: 207 | system.sif.WriteIOPControl(value); 208 | break; 209 | case 0x1f900b60: 210 | case 0x1f900b62: 211 | // are these spu related? 212 | break; 213 | default: 214 | common::Log("[iop::Context] handle io write %08x = %08x", paddr, value); 215 | } 216 | } 217 | 218 | } // namespace iop -------------------------------------------------------------------------------- /src/core/gs/context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common/queue.h" 6 | #include "common/types.h" 7 | #include "core/gs/page.h" 8 | 9 | struct System; 10 | 11 | namespace gs { 12 | 13 | // vram notes: 14 | // vram is a 4mb linear array, which consists of pages, blocks and columns 15 | // vram contains 512 pages 16 | // 1 page = 32 blocks 17 | // 1 block = 4 columns 18 | // 1 column contains pixels 19 | 20 | // crtc notes: 21 | // the crtc is responsible for outputting the final image to the tv, 22 | // which it does by reading vram and can be configured through various registers 23 | // process: 24 | // read 2 rectangles from vram called output circuit 1 and 2 25 | // these then get merged together into the merge circuit which gets outputted to your screen 26 | // dispfb register (for output circuit 1 and 2): 27 | // fbp: is used to specify the base in vram 28 | // fbw: specifies the framebuffer width 29 | // dbx: x offset from the frame 30 | // dby: y offset from the frame 31 | // display register (for output circuit 1 and 2): 32 | // dx: x offset on screen 33 | // dy: y offset on screen 34 | // dw + 1: width on screen 35 | // dy + 1: height on screen 36 | 37 | // vertex kick is done when writes to specific gs registers are done. this will cause a vertex to be 38 | // added to the vertex queue 39 | 40 | // drawing kick is done when writes to specific gs registers are done. this will cause all 41 | // the vertices in the vertex queue to be combined together to draw a primitive 42 | 43 | struct Framebuffer { 44 | u8* data; 45 | int width; 46 | int height; 47 | }; 48 | 49 | class Context { 50 | public: 51 | Context(System& system); 52 | 53 | u32 ReadRegisterPrivileged(u32 addr); 54 | void WriteRegisterPrivileged(u32 addr, u32 value); 55 | void WriteRegister(u32 addr, u64 value); 56 | void WriteHWReg(u64 value); 57 | 58 | void RenderCRTC(); 59 | 60 | void Reset(); 61 | void SystemReset(); 62 | 63 | Framebuffer GetFramebuffer(); 64 | 65 | union RGBAQ { 66 | struct { 67 | u8 r; 68 | u8 g; 69 | u8 b; 70 | u8 a; 71 | f32 q; 72 | }; 73 | 74 | u64 data; 75 | }; 76 | 77 | union TRXREG { 78 | struct { 79 | u32 width : 12; 80 | u32 : 20; 81 | u32 height : 12; 82 | u32 : 20; 83 | }; 84 | 85 | u64 data; 86 | }; 87 | 88 | union BITBLTBUF { 89 | struct { 90 | u32 src_base : 14; 91 | u32 : 2; 92 | u32 src_width : 6; 93 | u32 : 2; 94 | u32 src_format : 6; 95 | u32 : 2; 96 | u32 dst_base : 14; 97 | u32 : 2; 98 | u32 dst_width : 6; 99 | u32 : 2; 100 | u32 dst_format : 6; 101 | u32 : 2; 102 | }; 103 | 104 | u64 data; 105 | }; 106 | 107 | union TRXPOS { 108 | struct { 109 | u32 src_x : 11; 110 | u32 : 5; 111 | u32 src_y : 11; 112 | u32 : 5; 113 | u32 dst_x : 11; 114 | u32 : 5; 115 | u32 dst_y : 11; 116 | u32 order : 2; 117 | u32 : 3; 118 | }; 119 | 120 | u64 data; 121 | }; 122 | 123 | union PMODE { 124 | struct { 125 | bool en1 : 1; 126 | bool en2 : 1; 127 | u32 crtmd : 3; 128 | bool mmod : 1; 129 | bool amod : 1; 130 | bool slbg : 1; 131 | u8 alp : 8; 132 | u32 : 16; 133 | }; 134 | 135 | u32 data; 136 | }; 137 | 138 | union DISPLAY { 139 | struct { 140 | u32 dx : 12; 141 | u32 dy : 11; 142 | u32 magh : 4; 143 | u32 magv : 2; 144 | u32 : 3; 145 | u32 dw : 12; 146 | u32 dh : 11; 147 | u32 : 9; 148 | }; 149 | 150 | u64 data; 151 | }; 152 | 153 | union DISPFB { 154 | struct { 155 | u32 fbp : 9; 156 | u32 fbw : 6; 157 | u32 psm : 5; 158 | u32 : 12; 159 | u32 dbx : 11; 160 | u32 dby : 11; 161 | u32 : 10; 162 | }; 163 | 164 | u64 data; 165 | }; 166 | 167 | enum PrimitiveType : int { 168 | Point = 0, 169 | Line = 1, 170 | LineStrip = 2, 171 | Triangle = 3, 172 | TriangleStrip = 4, 173 | TriangleFan = 5, 174 | Sprite = 6, 175 | }; 176 | 177 | union PRIM { 178 | struct { 179 | u32 prim : 3; 180 | bool iip : 1; 181 | bool tme : 1; 182 | bool fge : 1; 183 | bool abe : 1; 184 | bool aa1 : 1; 185 | bool fst : 1; 186 | bool ctxt : 1; 187 | bool fix : 1; 188 | u32 : 21; 189 | }; 190 | 191 | u32 data; 192 | }; 193 | 194 | union CSR { 195 | struct { 196 | bool signal : 1; 197 | bool finish : 1; 198 | bool hsint : 1; 199 | bool vsint : 1; 200 | bool edwint : 1; 201 | u32 : 3; 202 | bool flush : 1; 203 | bool reset : 1; 204 | u32 : 2; 205 | bool nfield : 1; 206 | bool field : 1; 207 | u32 fifo : 2; 208 | u32 rev : 8; 209 | u32 id : 8; 210 | }; 211 | 212 | u32 data; 213 | }; 214 | 215 | CSR csr; 216 | 217 | // these registers seem to be undocumented 218 | u64 smode1; 219 | u64 synch1; 220 | u64 synch2; 221 | u64 syncv; 222 | u64 srfsh; 223 | u32 imr; 224 | 225 | u8 smode2; 226 | PMODE pmode; 227 | DISPFB dispfb2; 228 | DISPLAY display2; 229 | u32 bgcolour; 230 | PRIM prim; 231 | std::array frame; 232 | std::array xyoffset; 233 | std::array scissor; 234 | RGBAQ rgbaq; 235 | BITBLTBUF bitbltbuf; 236 | TRXPOS trxpos; 237 | TRXREG trxreg; 238 | u8 trxdir; 239 | u64 prmodecont; 240 | u64 prmode; 241 | u64 fog; 242 | u64 st; 243 | u64 uv; 244 | u64 scanmsk; 245 | std::array tex0; 246 | std::array clamp; 247 | std::array tex1; 248 | std::array tex2; 249 | u64 texclut; 250 | std::array miptbp1; 251 | std::array miptbp2; 252 | u64 texa; 253 | u64 fogcol; 254 | u64 texflush; 255 | std::array alpha; 256 | std::array test; 257 | u64 pabe; 258 | u64 dimx; 259 | u64 dthe; 260 | u64 colclamp; 261 | std::array fba; 262 | std::array zbuf; 263 | 264 | private: 265 | enum class PixelFormat : int { 266 | PSMCT32 = 0x00, 267 | PSMCT24 = 0x01, 268 | PSMCT16 = 0x02, 269 | PSMCT16S = 0x0A, 270 | PSMCT8 = 0x13, 271 | PSMCT4 = 0x14, 272 | PSMCT8H = 0x1b, 273 | PSMCT4HL = 0x24, 274 | PSMCT4HH = 0x2c, 275 | PSMZ32 = 0x30, 276 | PSMZ24 = 0x31, 277 | PSMZ16 = 0x32, 278 | PSMZ16S = 0x3a, 279 | }; 280 | 281 | struct Vertex { 282 | // these are fixed point integers, 283 | // with 12 bits for integer and 4 bits for decimal 284 | u16 x; 285 | u16 y; 286 | 287 | u32 z; 288 | 289 | // colour values 290 | u8 r; 291 | u8 g; 292 | u8 b; 293 | u8 a; 294 | f32 q; 295 | 296 | u8 fog; 297 | }; 298 | 299 | int GetPixelsToTransfer(PixelFormat format); 300 | u32 GetCRTCPixel(u32 base, int x, int y, u32 width, PixelFormat format); 301 | int GetCRTCWidth(); 302 | int GetCRTCHeight(); 303 | u32 ReadPSMCT32Pixel(u32 base, int x, int y, u32 width); 304 | void WritePSMCT32Pixel(u32 base, int x, int y, u32 width, u32 value); 305 | 306 | void VertexKick(); 307 | void DrawingKick(); 308 | 309 | int pixels_transferred; 310 | 311 | std::array vram; 312 | u32 framebuffer[480][640]; 313 | Vertex current_vertex; 314 | common::Queue vertex_queue; 315 | System& system; 316 | }; 317 | 318 | } // namespace gs -------------------------------------------------------------------------------- /src/frontend/imgui/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // COMPILE-TIME OPTIONS FOR DEAR IMGUI 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) 7 | // B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. 8 | //----------------------------------------------------------------------------- 9 | // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp 10 | // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 11 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 12 | // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. 13 | //----------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | //---- Define assertion handler. Defaults to calling assert(). 18 | // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. 19 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 20 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 21 | 22 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 23 | // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. 24 | // DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() 25 | // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. 26 | //#define IMGUI_API __declspec( dllexport ) 27 | //#define IMGUI_API __declspec( dllimport ) 28 | 29 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 30 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 31 | //#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions. 32 | 33 | //---- Disable all of Dear ImGui or don't implement standard windows/tools. 34 | // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. 35 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 36 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. 37 | //#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88). 38 | 39 | //---- Don't implement some functions to reduce linkage requirements. 40 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) 41 | //#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) 42 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) 43 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). 44 | //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). 45 | //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) 46 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 47 | //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) 48 | //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. 49 | //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). 50 | //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available 51 | 52 | //---- Include imgui_user.h at the end of imgui.h as a convenience 53 | //#define IMGUI_INCLUDE_IMGUI_USER_H 54 | 55 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 56 | //#define IMGUI_USE_BGRA_PACKED_COLOR 57 | 58 | //---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) 59 | //#define IMGUI_USE_WCHAR32 60 | 61 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 62 | // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. 63 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 64 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 65 | //#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if enabled 66 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 67 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 68 | 69 | //---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) 70 | // Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. 71 | //#define IMGUI_USE_STB_SPRINTF 72 | 73 | //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) 74 | // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). 75 | // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. 76 | //#define IMGUI_ENABLE_FREETYPE 77 | 78 | //---- Use stb_truetype to build and rasterize the font atlas (default) 79 | // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. 80 | //#define IMGUI_ENABLE_STB_TRUETYPE 81 | 82 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 83 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 84 | /* 85 | #define IM_VEC2_CLASS_EXTRA \ 86 | constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ 87 | operator MyVec2() const { return MyVec2(x,y); } 88 | 89 | #define IM_VEC4_CLASS_EXTRA \ 90 | constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ 91 | operator MyVec4() const { return MyVec4(x,y,z,w); } 92 | */ 93 | //---- ...Or use Dear ImGui's own very basic math operators. 94 | //#define IMGUI_DEFINE_MATH_OPERATORS 95 | 96 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 97 | // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). 98 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 99 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 100 | //#define ImDrawIdx unsigned int 101 | 102 | //---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) 103 | //struct ImDrawList; 104 | //struct ImDrawCmd; 105 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 106 | //#define ImDrawCallback MyImDrawCallback 107 | 108 | //---- Debug Tools: Macro to break in Debugger 109 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 110 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 111 | //#define IM_DEBUG_BREAK __debugbreak() 112 | 113 | //---- Debug Tools: Enable slower asserts 114 | //#define IMGUI_DEBUG_PARANOID 115 | 116 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 117 | /* 118 | namespace ImGui 119 | { 120 | void MyFunction(const char* name, const MyMatrix44& v); 121 | } 122 | */ 123 | -------------------------------------------------------------------------------- /src/core/iop/dmac.cpp: -------------------------------------------------------------------------------- 1 | #include "common/log.h" 2 | #include "common/log.h" 3 | #include "core/iop/dmac.h" 4 | #include "core/system.h" 5 | 6 | namespace iop { 7 | 8 | DMAC::DMAC(System& system, SIO2& sio2) : system(system), sio2(sio2) {} 9 | 10 | void DMAC::Reset() { 11 | for (int i = 0; i < 13; i++) { 12 | channels[i].address = 0; 13 | channels[i].block_size = 0; 14 | channels[i].block_count = 0; 15 | channels[i].control = 0; 16 | channels[i].tag_address = 0; 17 | channels[i].end_transfer = false; 18 | } 19 | 20 | dpcr = 0x07777777; 21 | dpcr2 = 0x07777777; 22 | dicr.data = 0; 23 | dicr2.data = 0; 24 | global_dma_enable = false; 25 | global_dma_interrupt_control = false; 26 | } 27 | 28 | void DMAC::Run(int cycles) { 29 | for (int i = 7; i < 13; i++) { 30 | if (GetChannelEnable(i) && (channels[i].control & (1 << 24))) { 31 | switch (i) { 32 | case 7: 33 | DoSPU2Transfer(); 34 | break; 35 | case 9: 36 | DoSIF0Transfer(); 37 | break; 38 | case 10: 39 | DoSIF1Transfer(); 40 | break; 41 | case 11: 42 | DoSIO2InTransfer(); 43 | break; 44 | case 12: 45 | DoSIO2OutTransfer(); 46 | break; 47 | default: 48 | common::Error("[iop::DMAC] handle transfer for channel %d", i); 49 | } 50 | } 51 | } 52 | } 53 | 54 | u32 DMAC::ReadRegister(u32 addr) { 55 | switch (addr) { 56 | case 0x1F8010F0: 57 | return dpcr; 58 | case 0x1F8010F4: 59 | common::Log("[iop::DMAC] dicr read %08x", dicr.data); 60 | return dicr.data; 61 | case 0x1F801570: 62 | return dpcr2; 63 | case 0x1F801574: 64 | common::Log("[iop::DMAC] dicr2 read %08x", dicr2.data); 65 | return dicr2.data; 66 | case 0x1F801578: 67 | return global_dma_enable; 68 | case 0x1F80157C: 69 | return global_dma_interrupt_control; 70 | default: 71 | return ReadChannel(addr); 72 | } 73 | } 74 | 75 | u32 DMAC::ReadChannel(u32 addr) { 76 | int channel = GetChannelIndex(addr); 77 | int index = addr & 0xF; 78 | 79 | switch (index) { 80 | case 0x0: 81 | common::Log("[iop::DMAC %d] Dn_MADR read %08x", channel, channels[channel].address); 82 | return channels[channel].address; 83 | case 0x4: 84 | common::Log("[iop::DMAC %d] Dn_BCR read %08x", channel, (channels[channel].block_count << 16) | (channels[channel].block_size)); 85 | return (channels[channel].block_count << 16) | (channels[channel].block_size); 86 | case 0x8: 87 | common::Log("[iop::DMAC %d] Dn_CHCR read %08x", channel, channels[channel].control); 88 | return channels[channel].control; 89 | case 0xC: 90 | common::Log("[iop::DMAC %d] Dn_TADR read %08x", channel, channels[channel].tag_address); 91 | return channels[channel].tag_address; 92 | default: 93 | common::Error("[iop::DMAC] %08x", index); 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | void DMAC::WriteRegister(u32 addr, u32 data) { 100 | switch (addr) { 101 | case 0x1F8010F0: 102 | common::Log("[iop::DMAC] dpcr write %08x", data); 103 | dpcr = data; 104 | break; 105 | case 0x1f8010f4: { 106 | common::Log("[iop::DMAC] dicr write %08x", data); 107 | u8 flags = dicr.flags; 108 | dicr.data = data; 109 | 110 | // writing 1 to the flag bits clears them 111 | dicr.flags = flags & ~((data >> 24) & 0x7f); 112 | 113 | // update dicr master flag 114 | dicr.master_interrupt_flag = dicr.force_irq || (dicr.master_interrupt_enable && (dicr.masks & dicr.flags)); 115 | 116 | if (dicr.force_irq) { 117 | common::Error("force irq dicr"); 118 | } 119 | break; 120 | } 121 | case 0x1f801570: 122 | dpcr2 = data; 123 | break; 124 | case 0x1f801574: { 125 | common::Log("[iop::DMAC] dicr2 write %08x", data); 126 | u8 flags = dicr2.flags; 127 | dicr2.data = data; 128 | 129 | // writing 1 to the flag bits clears them 130 | dicr2.flags = flags & ~((data >> 24) & 0x7f); 131 | 132 | if (dicr2.force_irq) { 133 | common::Error("force irq dicr2"); 134 | } 135 | break; 136 | } 137 | case 0x1F801578: 138 | global_dma_enable = data & 0x1; 139 | break; 140 | case 0x1F80157C: 141 | global_dma_interrupt_control = data & 0x1; 142 | break; 143 | default: 144 | WriteChannel(addr, data); 145 | break; 146 | } 147 | } 148 | 149 | int DMAC::GetChannelIndex(u32 addr) { 150 | int channel = (addr >> 4) & 0xF; 151 | 152 | // this allows us to map the 2nd nibble to channel index 153 | if (channel >= 8) { 154 | channel -= 8; 155 | } else { 156 | channel += 7; 157 | } 158 | 159 | return channel; 160 | } 161 | 162 | bool DMAC::GetChannelEnable(int index) { 163 | return (dpcr2 >> (3 + ((index - 7) * 4))) & 0x1; 164 | } 165 | 166 | void DMAC::WriteChannel(u32 addr, u32 data) { 167 | int channel = GetChannelIndex(addr); 168 | int index = addr & 0xF; 169 | 170 | switch (index) { 171 | case 0x0: 172 | common::Log("[iop::DMAC %d] address write %08x", channel, data); 173 | channels[channel].address = data & 0xFFFFFF; 174 | break; 175 | case 0x4: 176 | common::Log("[iop::DMAC %d] block size and count write %08x", channel, data); 177 | channels[channel].block_size = data & 0xFFFF; 178 | channels[channel].block_count = (data >> 16) & 0xFFFF; 179 | break; 180 | case 0x6: 181 | common::Log("[iop::DMAC %d] block count write %08x", channel, data); 182 | channels[channel].block_count = data & 0xFFFF; 183 | break; 184 | case 0x8: 185 | common::Log("[iop::DMAC %d] control write %08x", channel, data); 186 | channels[channel].control = data; 187 | 188 | if (data & (1 << 24)) { 189 | common::Log("[iop::DMAC %d] transfer started", channel); 190 | } 191 | 192 | break; 193 | case 0xC: 194 | common::Log("[iop::DMAC %d] tag address %08x", channel, data); 195 | channels[channel].tag_address = data; 196 | break; 197 | default: 198 | common::Error("handle %02x", index); 199 | } 200 | } 201 | 202 | void DMAC::DoSIF0Transfer() { 203 | Channel& channel = channels[9]; 204 | 205 | if (channel.block_count) { 206 | // read data from iop ram and push to the sif0 fifo 207 | system.sif.write_sif0_fifo(system.iop.Read(channel.address)); 208 | 209 | channel.address += 4; 210 | channel.block_count--; 211 | } else if (channel.end_transfer) { 212 | EndTransfer(9); 213 | } else { 214 | u32 data = system.iop.Read(channel.tag_address); 215 | u32 block_count = system.iop.Read(channel.tag_address + 4); 216 | 217 | common::Log("[iop::DMAC] SIF0 read DMATag %016lx", ((u64)block_count << 32) | data); 218 | 219 | system.sif.write_sif0_fifo(system.iop.Read(channel.tag_address + 8)); 220 | system.sif.write_sif0_fifo(system.iop.Read(channel.tag_address + 12)); 221 | 222 | // common::Log("[iop::DMAC] read sif0 dmatag %016lx", ((u64)block_count << 32) | data); 223 | 224 | // round to the nearest 4 225 | channel.block_count = (block_count + 3) & 0xFFFFFFFC; 226 | channel.address = data & 0xFFFFFF; 227 | 228 | channel.tag_address += 16; 229 | 230 | bool irq = (data >> 30) & 0x1; 231 | bool end_transfer = (data >> 31) & 0x1; 232 | 233 | if (irq || end_transfer) { 234 | channel.end_transfer = true; 235 | } 236 | } 237 | } 238 | 239 | void DMAC::DoSIF1Transfer() { 240 | Channel& channel = channels[10]; 241 | 242 | if (channel.block_count) { 243 | // transfer data from the sif1 fifo to iop ram 244 | if (system.sif.GetSIF1FIFOSize() > 0) { 245 | u32 data = system.sif.ReadSIF1FIFO(); 246 | 247 | system.iop.Write(channel.address, data); 248 | channel.address += 4; 249 | channel.block_count--; 250 | } 251 | } else if (channel.end_transfer) { 252 | EndTransfer(10); 253 | } else { 254 | if (system.sif.GetSIF1FIFOSize() >= 4) { 255 | u64 dma_tag = 0; 256 | 257 | dma_tag |= system.sif.ReadSIF1FIFO(); 258 | dma_tag |= (u64)system.sif.ReadSIF1FIFO() << 32; 259 | 260 | common::Log("[iop::DMAC] SIF1 read DMATag %016lx", dma_tag); 261 | 262 | channel.address = dma_tag & 0xFFFFFF; 263 | channel.block_count = dma_tag >> 32; 264 | 265 | // since the ee would've pushed quads one at a time we need to remove the upper 2 words 266 | system.sif.ReadSIF1FIFO(); 267 | system.sif.ReadSIF1FIFO(); 268 | 269 | bool irq = (dma_tag >> 30) & 0x1; 270 | bool end_transfer = (dma_tag >> 31) & 0x1; 271 | 272 | if (irq || end_transfer) { 273 | channel.end_transfer = true; 274 | } 275 | } 276 | } 277 | } 278 | 279 | // TODO: handle spu2 chain mode 280 | void DMAC::DoSPU2Transfer() { 281 | Channel& channel = channels[7]; 282 | 283 | if (channel.block_count) { 284 | // ignore the spu2 for now 285 | channel.block_count--; 286 | } else { 287 | EndTransfer(7); 288 | } 289 | } 290 | 291 | void DMAC::DoSIO2InTransfer() { 292 | Channel& channel = channels[11]; 293 | int length = 4; 294 | 295 | for (int i = 0; i < length; i++) { 296 | u8 data = system.iop.Read(channel.address); 297 | sio2.WriteDMA(data); 298 | channel.address++; 299 | } 300 | 301 | channel.block_count--; 302 | if (channel.block_count == 0) { 303 | EndTransfer(11); 304 | } 305 | 306 | common::Log("[iop::DMAC sio2in] end transfer flags %08x masks %08x", dicr2.flags, dicr2.masks); 307 | } 308 | 309 | void DMAC::DoSIO2OutTransfer() { 310 | Channel& channel = channels[12]; 311 | int length = 4; 312 | 313 | for (int i = 0; i < length; i++) { 314 | u8 data = sio2.ReadDMA(); 315 | system.iop.Write(channel.address, data); 316 | channel.address++; 317 | } 318 | 319 | channel.block_count--; 320 | if (channel.block_count == 0) { 321 | EndTransfer(12); 322 | } 323 | 324 | common::Log("[iop::DMAC sio2out] end transfer flags %08x masks %08x", dicr2.flags, dicr2.masks); 325 | } 326 | 327 | void DMAC::EndTransfer(int index) { 328 | common::Log("[iop::DMAC %d] end transfer", index); 329 | 330 | if (index < 7) { 331 | common::Error("[iop::DMAC] handle index < 7, %d", index); 332 | } 333 | 334 | // hack for now for spu2 status register to be updated 335 | if (index == 7) { 336 | system.spu2.RequestInterrupt(); 337 | } 338 | 339 | channels[index].end_transfer = false; 340 | channels[index].control &= ~(1 << 24); 341 | 342 | // raise an interrupt in dicr2 343 | dicr2.flags |= (1 << (index - 7)); 344 | 345 | if (dicr2.flags & dicr2.masks) { 346 | common::Log("[iop::DMAC %d] interrupt was requested", index); 347 | system.iop.intc.RequestInterrupt(InterruptSource::DMA); 348 | } 349 | } 350 | 351 | } // namespace iop -------------------------------------------------------------------------------- /src/frontend/host_interface.cpp: -------------------------------------------------------------------------------- 1 | #include "common/string.h" 2 | #include "frontend/host_interface.h" 3 | #include "imgui/imgui_internal.h" 4 | 5 | HostInterface::HostInterface() : 6 | core([this](float fps) { 7 | this->fps = fps; 8 | }) { 9 | } 10 | 11 | bool HostInterface::initialise() { 12 | // initialise sdl 13 | if (SDL_Init(SDL_INIT_VIDEO) > 0) { 14 | common::Warn("error initialising SDL"); 15 | return false; 16 | } 17 | 18 | // decide gl + glsl versions 19 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); 20 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 21 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 22 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); 23 | 24 | // create window with graphics context 25 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 26 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 27 | SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); 28 | // SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 29 | window = SDL_CreateWindow( 30 | "matcha", 31 | SDL_WINDOWPOS_CENTERED, 32 | SDL_WINDOWPOS_CENTERED, 33 | 1280, 34 | 720, 35 | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE 36 | ); 37 | 38 | SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); 39 | 40 | gl_context = SDL_GL_CreateContext(window); 41 | SDL_GL_MakeCurrent(window, gl_context); 42 | SDL_GL_SetSwapInterval(1); // Enable vsync 43 | 44 | SDL_GetWindowSize(window, &window_width, &window_height); 45 | 46 | // setup imgui context 47 | IMGUI_CHECKVERSION(); 48 | ImGui::CreateContext(); 49 | ImGuiIO& io = ImGui::GetIO(); (void)io; 50 | //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls 51 | //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls 52 | 53 | // setup sdl and opengl3 backend 54 | ImGui_ImplSDL2_InitForOpenGL(window, gl_context); 55 | ImGui_ImplOpenGL3_Init(glsl_version); 56 | 57 | // initialise texture stuff 58 | glGenTextures(1, &screen_texture); 59 | glBindTexture(GL_TEXTURE_2D, screen_texture); 60 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 61 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 62 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 63 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 64 | 65 | games_list.Initialise(); 66 | 67 | return true; 68 | } 69 | 70 | void HostInterface::run() { 71 | while (running) { 72 | // poll events 73 | HandleInput(); 74 | 75 | // start imgui frame 76 | ImGui_ImplOpenGL3_NewFrame(); 77 | ImGui_ImplSDL2_NewFrame(); 78 | ImGui::NewFrame(); 79 | 80 | RenderMenubar(); 81 | 82 | switch (window_state) { 83 | case WindowState::Library: 84 | // RenderLibraryWindow(); 85 | break; 86 | case WindowState::Display: 87 | RenderDisplayWindow(); 88 | break; 89 | } 90 | 91 | if (m_show_debugger_window) { 92 | render_debugger_window(); 93 | } 94 | 95 | if (m_show_demo_window) { 96 | ImGui::ShowDemoWindow(&m_show_demo_window); 97 | } 98 | 99 | // rendering 100 | ImGui::Render(); 101 | glViewport(0, 0, 1280, 720); 102 | glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); 103 | glClear(GL_COLOR_BUFFER_BIT); 104 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 105 | SDL_GL_SwapWindow(window); 106 | } 107 | } 108 | 109 | void HostInterface::shutdown() { 110 | ImGui_ImplOpenGL3_Shutdown(); 111 | ImGui_ImplSDL2_Shutdown(); 112 | ImGui::DestroyContext(); 113 | 114 | SDL_GL_DeleteContext(gl_context); 115 | SDL_DestroyWindow(window); 116 | SDL_Quit(); 117 | } 118 | 119 | void HostInterface::HandleInput() { 120 | SDL_Event event; 121 | while (SDL_PollEvent(&event)) { 122 | ImGui_ImplSDL2_ProcessEvent(&event); 123 | 124 | if (event.type == SDL_QUIT) { 125 | running = false; 126 | } 127 | } 128 | } 129 | 130 | void HostInterface::RenderMenubar() { 131 | ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 6.0f)); 132 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f, 6.0f)); 133 | if (ImGui::BeginMainMenuBar()) { 134 | if (ImGui::BeginMenu("File")) { 135 | if (ImGui::MenuItem("Load ROM")) { 136 | file_dialog.Open(); 137 | } 138 | 139 | if (ImGui::MenuItem("Boot BIOS")) { 140 | core.SetBootParameters(BootMode::BIOS); 141 | core.Boot(); 142 | window_state = WindowState::Display; 143 | } 144 | 145 | if (ImGui::MenuItem("Power Off")) { 146 | window_state = WindowState::Library; 147 | core.SetState(CoreState::Idle); 148 | } 149 | 150 | if (ImGui::MenuItem("Quit")) { 151 | running = false; 152 | } 153 | ImGui::EndMenu(); 154 | } 155 | 156 | if (ImGui::BeginMenu("Emulator")) { 157 | if (ImGui::MenuItem(core.GetState() == CoreState::Running ? "Pause" : "Resume")) { 158 | TogglePause(); 159 | } 160 | 161 | if (ImGui::MenuItem("Restart")) { 162 | if (core.GetState() != CoreState::Idle) { 163 | core.Boot(); 164 | } 165 | } 166 | 167 | ImGui::EndMenu(); 168 | } 169 | 170 | ImGui::MenuItem("Debugger", nullptr, &m_show_debugger_window); 171 | ImGui::MenuItem("Demo", nullptr, &m_show_demo_window); 172 | 173 | if (core.GetState() == CoreState::Running && fps != 0.0f) { 174 | std::string fps_string = common::Format("%.0f FPS | %.2f ms", fps, 1000.0f / fps); 175 | auto pos = window_width - ImGui::CalcTextSize(fps_string.c_str()).x - ImGui::GetStyle().ItemSpacing.x; 176 | 177 | ImGui::SetCursorPosX(pos); 178 | ImGui::Text("%s", fps_string.c_str()); 179 | } else if (core.GetState() == CoreState::Paused) { 180 | std::string fps_string = "Paused"; 181 | 182 | auto pos = window_width - ImGui::CalcTextSize(fps_string.c_str()).x - ImGui::GetStyle().ItemSpacing.x; 183 | 184 | ImGui::SetCursorPosX(pos); 185 | ImGui::Text("%s", fps_string.c_str()); 186 | } 187 | 188 | ImGui::EndMainMenuBar(); 189 | } 190 | 191 | ImGui::PopStyleVar(2); 192 | 193 | file_dialog.Display(); 194 | if (file_dialog.HasSelected()) { 195 | core.SetBootParameters(BootMode::Fast, file_dialog.GetSelected().string()); 196 | core.Boot(); 197 | window_state = WindowState::Display; 198 | file_dialog.ClearSelected(); 199 | } 200 | } 201 | 202 | void HostInterface::TogglePause() { 203 | if (core.GetState() == CoreState::Running) { 204 | core.SetState(CoreState::Paused); 205 | } else if (core.GetState() == CoreState::Paused) { 206 | core.SetState(CoreState::Running); 207 | } 208 | } 209 | 210 | void HostInterface::RenderDisplayWindow() { 211 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); 212 | ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); 213 | 214 | gs::Framebuffer framebuffer = core.system.gs.GetFramebuffer(); 215 | 216 | glBindTexture(GL_TEXTURE_2D, screen_texture); 217 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 218 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 219 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 220 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 221 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, framebuffer.width, framebuffer.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer.data); 222 | 223 | const double scale_x = static_cast(window_width) / framebuffer.width; 224 | const double scale_y = static_cast(window_height - menubar_height) / framebuffer.height; 225 | const double scale = scale_x < scale_y ? scale_x : scale_y; 226 | 227 | ImVec2 scaled_dimensions = ImVec2(framebuffer.width * scale, framebuffer.height * scale); 228 | ImVec2 center_pos = ImVec2( 229 | (static_cast(window_width) - scaled_dimensions.x) / 2, 230 | (static_cast(window_height - menubar_height) - scaled_dimensions.y) / 2 231 | ); 232 | 233 | ImGui::GetBackgroundDrawList()->AddImage( 234 | (void*)(intptr_t)screen_texture, 235 | ImVec2(center_pos.x, menubar_height + center_pos.y), 236 | ImVec2(center_pos.x + scaled_dimensions.x, menubar_height + center_pos.y + scaled_dimensions.y), 237 | ImVec2(0, 0), 238 | ImVec2(1, 1), 239 | IM_COL32_WHITE 240 | ); 241 | 242 | ImGui::PopStyleVar(); 243 | ImGui::PopStyleVar(); 244 | } 245 | 246 | void HostInterface::render_library_window() { 247 | static ImGuiTableFlags flags = 248 | ImGuiTableFlags_Resizable 249 | | ImGuiTableFlags_RowBg 250 | | ImGuiTableFlags_BordersOuterV 251 | | ImGuiTableFlags_SizingStretchProp; 252 | 253 | float min_row_height = 20.0f; 254 | 255 | if (ImGui::BeginTable("Library", 3, flags)) { 256 | ImGui::TableSetupColumn("Title"); 257 | ImGui::TableSetupColumn("Type"); 258 | ImGui::TableSetupColumn("Size"); 259 | ImGui::TableHeadersRow(); 260 | 261 | int row = 0; 262 | for (const common::GamesList::Entry& entry : games_list.GetEntries()) { 263 | ImGui::TableNextRow(ImGuiTableRowFlags_None); 264 | ImGui::PushID(row); 265 | ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.0f, 0.5f)); 266 | ImGui::TableSetColumnIndex(0); 267 | 268 | if (ImGui::Selectable(entry.name.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0.0f, min_row_height))) { 269 | core.SetBootParameters(BootMode::Fast, entry.path); 270 | core.Boot(); 271 | window_state = WindowState::Display; 272 | } 273 | 274 | ImGui::TableSetColumnIndex(1); 275 | 276 | if (ImGui::Selectable(entry.type.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0.0f, min_row_height))) { 277 | core.SetBootParameters(BootMode::Fast, entry.path); 278 | core.Boot(); 279 | window_state = WindowState::Display; 280 | } 281 | 282 | ImGui::TableSetColumnIndex(2); 283 | 284 | if (ImGui::Selectable(entry.size.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0.0f, min_row_height))) { 285 | core.SetBootParameters(BootMode::Fast, entry.path); 286 | core.Boot(); 287 | window_state = WindowState::Display; 288 | } 289 | 290 | ImGui::PopStyleVar(); 291 | ImGui::PopID(); 292 | row++; 293 | } 294 | 295 | ImGui::EndTable(); 296 | } 297 | } 298 | 299 | void HostInterface::BeginFullscreenWindow(const char *name, ImVec2 padding) { 300 | ImGui::SetNextWindowPos(ImVec2(0, menubar_height)); 301 | ImGui::SetNextWindowSize(ImVec2(window_width, window_height - menubar_height)); 302 | 303 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding); 304 | ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); 305 | 306 | ImGui::Begin( 307 | name, 308 | nullptr, 309 | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | 310 | ImGuiWindowFlags_NoBringToFrontOnFocus 311 | ); 312 | } 313 | 314 | void HostInterface::EndFullscreenWindow() { 315 | ImGui::End(); 316 | ImGui::PopStyleVar(); 317 | ImGui::PopStyleVar(); 318 | } -------------------------------------------------------------------------------- /src/core/ee/context.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common/log.h" 4 | #include "common/memory.h" 5 | #include "core/ee/context.h" 6 | #include "core/ee/disassembler.h" 7 | #include "core/system.h" 8 | 9 | namespace ee { 10 | 11 | static std::array syscall_info = { 12 | "RFU000_FullReset", "ResetEE", "SetGsCrt", "RFU003", 13 | "Exit", "RFU005", "LoadExecPS2", "ExecPS2", 14 | "RFU008", "RFU009", "AddSbusIntcHandler", "RemoveSbusIntcHandler", 15 | "Interrupt2Iop", "SetVTLBRefillHandler", "SetVCommonHandler", "SetVInterruptHandler", 16 | "AddIntcHandler", "RemoveIntcHandler", "AddDmacHandler", "RemoveDmacHandler", 17 | "_EnableIntc", "_DisableIntc", "_EnableDmac", "_DisableDmac", 18 | "_SetAlarm", "_ReleaseAlarm", "_iEnableIntc", "_iDisableIntc", 19 | "_iEnableDmac", "_iDisableDmac", "_iSetAlarm", "_iReleaseAlarm", 20 | "CreateThread", "DeleteThread", "StartThread", "ExitThread", 21 | "ExitDeleteThread", "TerminateThread", "iTerminateThread", "DisableDispatchThread", 22 | "EnableDispatchThread", "ChangeThreadPriority", "iChangeThreadPriority", "RotateThreadReadyQueue", 23 | "iRotateThreadReadyQueue", "ReleaseWaitThread", "iReleaseWaitThread", "GetThreadId", 24 | "ReferThreadStatus", "iReferThreadStatus", "SleepThread", "WakeupThread", 25 | "_iWakeupThread", "CancelWakeupThread", "iCancelWakeupThread", "SuspendThread", 26 | "iSuspendThread", "ResumeThread", "iResumeThread", "JoinThread", 27 | "RFU060", "RFU061", "EndOfHeap", "RFU063", 28 | "CreateSema", "DeleteSema", "SignalSema", "iSignalSema", 29 | "WaitSema", "PollSema", "iPollSema", "ReferSemaStatus", 30 | "iReferSemaStatus", "RFU073", "SetOsdConfigParam", "GetOsdConfigParam", 31 | "GetGsHParam", "GetGsVParam", "SetGsHParam", "SetGsVParam", 32 | "RFU080_CreateEventFlag", "RFU081_DeleteEventFlag", 33 | "RFU082_SetEventFlag", "RFU083_iSetEventFlag", 34 | "RFU084_ClearEventFlag", "RFU085_iClearEventFlag", 35 | "RFU086_WaitEventFlag", "RFU087_PollEventFlag", 36 | "RFU088_iPollEventFlag", "RFU089_ReferEventFlagStatus", 37 | "RFU090_iReferEventFlagStatus", "RFU091_GetEntryAddress", 38 | "EnableIntcHandler_iEnableIntcHandler", 39 | "DisableIntcHandler_iDisableIntcHandler", 40 | "EnableDmacHandler_iEnableDmacHandler", 41 | "DisableDmacHandler_iDisableDmacHandler", 42 | "KSeg0", "EnableCache", "DisableCache", "GetCop0", 43 | "FlushCache", "RFU101", "CpuConfig", "iGetCop0", 44 | "iFlushCache", "RFU105", "iCpuConfig", "sceSifStopDma", 45 | "SetCPUTimerHandler", "SetCPUTimer", "SetOsdConfigParam2", "SetOsdConfigParam2", 46 | "GsGetIMR_iGsGetIMR", "GsGetIMR_iGsPutIMR", "SetPgifHandler", "SetVSyncFlag", 47 | "RFU116", "print", "sceSifDmaStat", "sceSifSetDma", 48 | "sceSifSetDChain", "sceSifSetReg", "sceSifGetReg", "ExecOSD", 49 | "Deci2Call", "PSMode", "MachineType", "GetMemorySize", 50 | }; 51 | 52 | Context::Context(System& system) : dmac(system), timers(intc), intc(*this), system(system), interpreter(*this) { 53 | m_rdram = std::make_unique>(); 54 | } 55 | 56 | void Context::Reset() { 57 | gpr.fill(0); 58 | pc = 0xbfc00000; 59 | npc = 0; 60 | hi = 0; 61 | lo = 0; 62 | hi1 = 0; 63 | lo1 = 0; 64 | sa = 0; 65 | 66 | m_rdram->fill(0); 67 | m_scratchpad.fill(0); 68 | 69 | mch_drd = 0; 70 | rdram_sdevid = 0; 71 | mch_ricm = 0; 72 | 73 | cop0.Reset(); 74 | cop1.Reset(); 75 | dmac.Reset(); 76 | timers.Reset(); 77 | intc.Reset(); 78 | interpreter.Reset(); 79 | 80 | // do initial hardcoded mappings 81 | vtlb.Reset(); 82 | vtlb.Map(m_rdram->data(), 0x00000000, 0x2000000, 0x1ffffff); 83 | vtlb.Map(m_rdram->data(), 0x20000000, 0x2000000, 0x1ffffff); 84 | vtlb.Map(m_rdram->data(), 0x30100000, 0x2000000, 0x1ffffff); 85 | vtlb.Map(m_scratchpad.data(), 0x70000000, 0x4000, 0x3fff); 86 | vtlb.Map(m_rdram->data(), 0x80000000, 0x2000000, 0x1ffffff); 87 | vtlb.Map(system.bios->data(), 0x9fc00000, 0x400000, 0x3fffff); 88 | vtlb.Map(m_rdram->data(), 0xa0000000, 0x2000000, 0x1ffffff); 89 | vtlb.Map(system.bios->data(), 0xbfc00000, 0x400000, 0x3fffff); 90 | 91 | // deci2call tlb region which gets mapped in the bios 92 | // later when we handle the tlb we can remove this mapping 93 | vtlb.Map(m_rdram->data(), 0xffff8000, 0x8000, 0x7ffff); 94 | } 95 | 96 | void Context::Run(int cycles) { 97 | interpreter.Run(cycles); 98 | 99 | // timers and dmac run at half the speed of the ee (bus speed) 100 | timers.Run(cycles / 2); 101 | dmac.Run(cycles / 2); 102 | } 103 | 104 | template u8 Context::read(VirtualAddress vaddr); 105 | template u16 Context::read(VirtualAddress vaddr); 106 | template u32 Context::read(VirtualAddress vaddr); 107 | template 108 | T Context::read(VirtualAddress vaddr) { 109 | auto pointer = vtlb.Lookup(vaddr); 110 | if (pointer) { 111 | return common::Read(pointer); 112 | } else { 113 | return ReadIO(vaddr & 0x1fffffff); 114 | } 115 | } 116 | 117 | template <> 118 | u64 Context::read(VirtualAddress vaddr) { 119 | auto pointer = vtlb.Lookup(vaddr); 120 | if (pointer) { 121 | return common::Read(pointer); 122 | } else { 123 | u32 paddr = vaddr & 0x1fffffff; 124 | return (static_cast(ReadIO(paddr + 4)) << 32) | ReadIO(paddr); 125 | } 126 | } 127 | 128 | template <> 129 | u128 Context::read(VirtualAddress vaddr) { 130 | auto pointer = vtlb.Lookup(vaddr); 131 | if (pointer) { 132 | return common::Read(pointer); 133 | } else { 134 | u32 paddr = vaddr & 0x1fffffff; 135 | u128 value; 136 | for (int i = 0; i < 4; i++) { 137 | value.uw[i] = ReadIO(paddr + (i * 4)); 138 | } 139 | 140 | return value; 141 | } 142 | } 143 | 144 | template void Context::write(VirtualAddress vaddr, u8 value); 145 | template void Context::write(VirtualAddress vaddr, u16 value); 146 | template void Context::write(VirtualAddress vaddr, u32 value); 147 | template 148 | void Context::write(VirtualAddress vaddr, T value) { 149 | auto pointer = vtlb.Lookup(vaddr); 150 | if (pointer) { 151 | return common::Write(pointer, value); 152 | } else { 153 | WriteIO(vaddr & 0x1fffffff, value); 154 | } 155 | } 156 | 157 | template <> 158 | void Context::write(VirtualAddress vaddr, u64 value) { 159 | auto pointer = vtlb.Lookup(vaddr); 160 | if (pointer) { 161 | return common::Write(pointer, value); 162 | } else { 163 | u32 paddr = vaddr & 0x1fffffff; 164 | WriteIO(paddr, value & 0xffffffff); 165 | WriteIO(paddr + 4, value >> 32); 166 | } 167 | } 168 | 169 | template <> 170 | void Context::write(VirtualAddress vaddr, u128 value) { 171 | auto pointer = vtlb.Lookup(vaddr); 172 | if (pointer) { 173 | return common::Write(pointer, value); 174 | } else { 175 | u32 paddr = vaddr & 0x1fffffff; 176 | for (int i = 0; i < 4; i++) { 177 | WriteIO(paddr + (i * 4), value.uw[i]); 178 | } 179 | } 180 | } 181 | 182 | u32 Context::ReadIO(u32 paddr) { 183 | if (paddr >= 0x10000000 && paddr < 0x10001840) { 184 | return timers.ReadRegister(paddr); 185 | } else if (paddr >= 0x12000000 && paddr < 0x12001084) { 186 | return system.gs.ReadRegisterPrivileged(paddr); 187 | } else if (paddr >= 0x10008000 && paddr < 0x1000e000) { 188 | return dmac.ReadChannel(paddr); 189 | } else if (paddr >= 0x10003000 && paddr < 0x100030a4) { 190 | return system.gif.ReadRegister(paddr); 191 | } 192 | 193 | switch (paddr) { 194 | case 0x10002010: 195 | return system.ipu.ReadControl(); 196 | case 0x1000E000: 197 | return dmac.ReadControl(); 198 | case 0x1000E010: 199 | return dmac.ReadInterruptStatus(); 200 | case 0x1000E020: 201 | return dmac.ReadPriorityControl(); 202 | case 0x1000E030: 203 | return dmac.ReadPriorityControl(); 204 | case 0x1000F000: 205 | return intc.ReadStat(); 206 | case 0x1000F010: 207 | return intc.ReadMask(); 208 | case 0x1000F130: 209 | return 0; 210 | case 0x1000F200: 211 | return system.sif.ReadMSCOM(); 212 | case 0x1000F210: 213 | return system.sif.ReadSMCOM(); 214 | case 0x1000F220: 215 | return system.sif.ReadMSFLAG(); 216 | case 0x1000F230: 217 | return system.sif.ReadSMFLAG(); 218 | case 0x1000f430: 219 | return 0; 220 | case 0x1000F440: 221 | if (!((mch_ricm >> 6) & 0xF)) { 222 | switch ((mch_ricm >> 16) & 0xFFF) { 223 | case 0x21: 224 | if (rdram_sdevid < 2) { 225 | rdram_sdevid++; 226 | return 0x1F; 227 | } 228 | return 0; 229 | case 0x23: 230 | return 0x0D0D; 231 | case 0x24: 232 | return 0x0090; 233 | case 0x40: 234 | return mch_ricm & 0x1F; 235 | } 236 | } 237 | 238 | break; 239 | case 0x1000f520: 240 | return dmac.disabled_status; 241 | default: 242 | common::Log("[ee::Context] handle io read %08x", paddr); 243 | } 244 | 245 | return 0; 246 | } 247 | 248 | void Context::WriteIO(u32 paddr, u32 value) { 249 | if (paddr >= 0x10000000 && paddr < 0x10001840) { 250 | timers.WriteRegister(paddr, value); 251 | return; 252 | } else if (paddr >= 0x12000000 && paddr < 0x12001084) { 253 | system.gs.WriteRegisterPrivileged(paddr, value); 254 | return; 255 | } else if (paddr >= 0x10008000 && paddr < 0x1000e054) { 256 | dmac.WriteRegister(paddr, value); 257 | return; 258 | } else if (paddr >= 0x1000f520 && paddr < 0x1000f594) { 259 | dmac.WriteRegister(paddr, value); 260 | return; 261 | } else if (paddr >= 0x11000000 && paddr < 0x11001000) { 262 | system.vu0.WriteCodeMemory(paddr, value); 263 | return; 264 | } else if (paddr >= 0x11004000 && paddr < 0x11005000) { 265 | system.vu0.WriteDataMemory(paddr, value); 266 | return; 267 | } else if (paddr >= 0x11008000 && paddr < 0x1100c000) { 268 | system.vu1.WriteCodeMemory(paddr, value); 269 | return; 270 | } else if (paddr >= 0x1100c000 && paddr < 0x11010000) { 271 | system.vu1.WriteDataMemory(paddr, value); 272 | return; 273 | } else if (paddr >= 0x10003000 && paddr < 0x100030a4) { 274 | system.gif.WriteRegister(paddr, value); 275 | return; 276 | } else if (paddr >= 0x10006000 && paddr < 0x10006010) { 277 | system.gif.WriteRegister(paddr, value); 278 | return; 279 | } 280 | 281 | switch (paddr) { 282 | case 0x10002000: 283 | system.ipu.WriteCommand(value); 284 | break; 285 | case 0x10002010: 286 | system.ipu.WriteControl(value); 287 | break; 288 | case 0x10003810: 289 | system.vif0.WriteFBRST(value); 290 | break; 291 | case 0x10003820: 292 | system.vif0.WriteERR(value); 293 | break; 294 | case 0x10003830: 295 | system.vif0.WriteMark(value); 296 | break; 297 | case 0x10003C00: 298 | system.vif1.WriteStat(value); 299 | break; 300 | case 0x10003C10: 301 | system.vif1.WriteFBRST(value); 302 | break; 303 | case 0x1000F000: 304 | intc.WriteStat(value); 305 | break; 306 | case 0x1000F010: 307 | intc.WriteMask(value); 308 | break; 309 | case 0x1000F180: 310 | // kputchar 311 | common::LogNoNewline("%c", value); 312 | break; 313 | case 0x1000F200: 314 | system.sif.WriteMSCOM(value); 315 | break; 316 | case 0x1000F220: 317 | system.sif.SetMSFLAG(value); 318 | break; 319 | case 0x1000F230: 320 | system.sif.SetSMFLAG(value); 321 | break; 322 | case 0x1000F240: 323 | system.sif.WriteEEControl(value); 324 | break; 325 | case 0x1000F260: 326 | system.sif.WriteBD6(value); 327 | break; 328 | case 0x1000F430: 329 | if ((((value >> 16) & 0xFFF) == 0x21) && (((value >> 6) & 0xF) == 1) && (((mch_drd >> 7) & 1) == 0)) { 330 | rdram_sdevid = 0; 331 | } 332 | 333 | mch_ricm = value & ~0x80000000; 334 | break; 335 | case 0x1000F440: 336 | mch_drd = value; 337 | break; 338 | default: 339 | common::Log("[ee::Context] handle io write %08x = %08x", paddr, value); 340 | } 341 | } 342 | 343 | void Context::RaiseInterrupt(int signal, bool value) { 344 | interpreter.RaiseInterrupt(signal, value); 345 | } 346 | 347 | std::string Context::GetSyscallInfo(int index) { 348 | index = (s8)(u8)index; 349 | 350 | if (index < 0) { 351 | index = -index; 352 | } 353 | 354 | return syscall_info[index]; 355 | } 356 | 357 | } // namespace ee -------------------------------------------------------------------------------- /src/core/iop/disassembler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "core/iop/disassembler.h" 6 | 7 | namespace iop { 8 | 9 | static std::array reg_names = { 10 | "zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", 11 | "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", 12 | "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", 13 | "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra", 14 | }; 15 | 16 | enum class InstructionType { 17 | Immediate, 18 | Jump, 19 | Register, 20 | None, 21 | }; 22 | 23 | struct DisassemblyInfo { 24 | std::string format; 25 | InstructionType type; 26 | }; 27 | 28 | static std::array primary_table = { 29 | DisassemblyInfo{"secondary", InstructionType::None}, 30 | DisassemblyInfo{"bcondz", InstructionType::None}, 31 | DisassemblyInfo{"j $target", InstructionType::Jump}, 32 | DisassemblyInfo{"jal $target", InstructionType::Jump}, 33 | DisassemblyInfo{"beq $rs, $rt, $offset", InstructionType::Immediate}, 34 | DisassemblyInfo{"bne $rs, $rt, $offset", InstructionType::Immediate}, 35 | DisassemblyInfo{"blez $rs, $offset", InstructionType::Immediate}, 36 | DisassemblyInfo{"bgtz $rs, $offset", InstructionType::Immediate}, 37 | DisassemblyInfo{"addi $rt, $rs, $imm", InstructionType::Immediate}, 38 | DisassemblyInfo{"addiu $rt, $rs, $imm", InstructionType::Immediate}, 39 | DisassemblyInfo{"slti $rt, $rs, $imm", InstructionType::Immediate}, 40 | DisassemblyInfo{"sltiu $rt, $rs, $imm", InstructionType::Immediate}, 41 | DisassemblyInfo{"andi $rt, $rs, $imm", InstructionType::Immediate}, 42 | DisassemblyInfo{"ori $rt, $rs, $imm", InstructionType::Immediate}, 43 | DisassemblyInfo{"xori $rt, $rs, $imm", InstructionType::Immediate}, 44 | DisassemblyInfo{"lui $rt, $imm", InstructionType::Immediate}, 45 | DisassemblyInfo{"cop0", InstructionType::None}, 46 | DisassemblyInfo{"cop1", InstructionType::None}, 47 | DisassemblyInfo{"cop2", InstructionType::None}, 48 | DisassemblyInfo{"cop3", InstructionType::None}, 49 | DisassemblyInfo{"illegal", InstructionType::None}, 50 | DisassemblyInfo{"illegal", InstructionType::None}, 51 | DisassemblyInfo{"illegal", InstructionType::None}, 52 | DisassemblyInfo{"illegal", InstructionType::None}, 53 | DisassemblyInfo{"illegal", InstructionType::None}, 54 | DisassemblyInfo{"illegal", InstructionType::None}, 55 | DisassemblyInfo{"illegal", InstructionType::None}, 56 | DisassemblyInfo{"illegal", InstructionType::None}, 57 | DisassemblyInfo{"illegal", InstructionType::None}, 58 | DisassemblyInfo{"illegal", InstructionType::None}, 59 | DisassemblyInfo{"illegal", InstructionType::None}, 60 | DisassemblyInfo{"illegal", InstructionType::None}, 61 | DisassemblyInfo{"lb $rt, $imm($rs)", InstructionType::Immediate}, 62 | DisassemblyInfo{"lh $rt, $imm($rs)", InstructionType::Immediate}, 63 | DisassemblyInfo{"lwl $rt, $imm($rs)", InstructionType::Immediate}, 64 | DisassemblyInfo{"lw $rt, $imm($rs)", InstructionType::Immediate}, 65 | DisassemblyInfo{"lbu $rt, $imm($rs)", InstructionType::Immediate}, 66 | DisassemblyInfo{"lhu $rt, $imm($rs)", InstructionType::Immediate}, 67 | DisassemblyInfo{"lwr $rt, $imm($rs)", InstructionType::Immediate}, 68 | DisassemblyInfo{"illegal", InstructionType::None}, 69 | DisassemblyInfo{"sb $rt, $imm($rs)", InstructionType::Immediate}, 70 | DisassemblyInfo{"sh $rt, $imm($rs)", InstructionType::Immediate}, 71 | DisassemblyInfo{"swl $rt, $imm($rs)", InstructionType::Immediate}, 72 | DisassemblyInfo{"sw $rt, $imm($rs)", InstructionType::Immediate}, 73 | DisassemblyInfo{"illegal", InstructionType::None}, 74 | DisassemblyInfo{"illegal", InstructionType::None}, 75 | DisassemblyInfo{"swr $rt, $imm($rs)", InstructionType::Immediate}, 76 | DisassemblyInfo{"illegal", InstructionType::None}, 77 | DisassemblyInfo{"lwc0", InstructionType::None}, 78 | DisassemblyInfo{"lwc1", InstructionType::None}, 79 | DisassemblyInfo{"lwc2", InstructionType::None}, 80 | DisassemblyInfo{"lwc3", InstructionType::None}, 81 | DisassemblyInfo{"illegal", InstructionType::None}, 82 | DisassemblyInfo{"illegal", InstructionType::None}, 83 | DisassemblyInfo{"illegal", InstructionType::None}, 84 | DisassemblyInfo{"illegal", InstructionType::None}, 85 | DisassemblyInfo{"swc0", InstructionType::None}, 86 | DisassemblyInfo{"swc1", InstructionType::None}, 87 | DisassemblyInfo{"swc2", InstructionType::None}, 88 | DisassemblyInfo{"swc3", InstructionType::None}, 89 | DisassemblyInfo{"illegal", InstructionType::None}, 90 | DisassemblyInfo{"illegal", InstructionType::None}, 91 | DisassemblyInfo{"illegal", InstructionType::None}, 92 | DisassemblyInfo{"illegal", InstructionType::None}, 93 | }; 94 | 95 | static std::array secondary_table = { 96 | DisassemblyInfo{"sll $rd, $rt, $sa", InstructionType::Register}, 97 | DisassemblyInfo{"illegal", InstructionType::None}, 98 | DisassemblyInfo{"srl $rd, $rt, $sa", InstructionType::Register}, 99 | DisassemblyInfo{"sra $rd, $rt, $sa", InstructionType::Register}, 100 | DisassemblyInfo{"sllv $rd, $rt, $rs", InstructionType::Register}, 101 | DisassemblyInfo{"illegal", InstructionType::None}, 102 | DisassemblyInfo{"srlv $rd, $rt, $rs", InstructionType::Register}, 103 | DisassemblyInfo{"srav $rd, $rt, $rs", InstructionType::Register}, 104 | DisassemblyInfo{"jr $rs", InstructionType::Immediate}, 105 | DisassemblyInfo{"jalr $rd, $rs", InstructionType::Register}, 106 | DisassemblyInfo{"movz $rd, $rs, $rt", InstructionType::Register}, 107 | DisassemblyInfo{"movn $rd, $rs, $rt", InstructionType::Register}, 108 | DisassemblyInfo{"syscall", InstructionType::None}, 109 | DisassemblyInfo{"break", InstructionType::None}, 110 | DisassemblyInfo{"illegal", InstructionType::None}, 111 | DisassemblyInfo{"sync $sa", InstructionType::Register}, 112 | DisassemblyInfo{"mfhi $rd", InstructionType::Register}, 113 | DisassemblyInfo{"mthi $rs", InstructionType::Register}, 114 | DisassemblyInfo{"mflo $rd", InstructionType::Register}, 115 | DisassemblyInfo{"mtlo $rs", InstructionType::Register}, 116 | DisassemblyInfo{"dsllv $rd, $rt, $rs", InstructionType::Register}, 117 | DisassemblyInfo{"illegal", InstructionType::None}, 118 | DisassemblyInfo{"dsrlv $rd, $rt, $rs", InstructionType::Register}, 119 | DisassemblyInfo{"dsrav $rd, $rt, $rs", InstructionType::Register}, 120 | DisassemblyInfo{"mult $rd, $rs, $rt", InstructionType::Register}, 121 | DisassemblyInfo{"multu $rd, $rs, $rt", InstructionType::Register}, 122 | DisassemblyInfo{"div $rs, $rt", InstructionType::Register}, 123 | DisassemblyInfo{"divu $rs, $rt", InstructionType::Register}, 124 | DisassemblyInfo{"illegal", InstructionType::None}, 125 | DisassemblyInfo{"illegal", InstructionType::None}, 126 | DisassemblyInfo{"illegal", InstructionType::None}, 127 | DisassemblyInfo{"illegal", InstructionType::None}, 128 | DisassemblyInfo{"add $rd, $rs, $rt", InstructionType::Register}, 129 | DisassemblyInfo{"addu $rd, $rs, $rt", InstructionType::Register}, 130 | DisassemblyInfo{"sub $rd, $rs, $rt", InstructionType::Register}, 131 | DisassemblyInfo{"subu $rd, $rs, $rt", InstructionType::Register}, 132 | DisassemblyInfo{"and $rd, $rs, $rt", InstructionType::Register}, 133 | DisassemblyInfo{"or $rd, $rs, $rt", InstructionType::Register}, 134 | DisassemblyInfo{"xor $rd, $rs, $rt", InstructionType::Register}, 135 | DisassemblyInfo{"nor $rd, $rs, $rt", InstructionType::Register}, 136 | DisassemblyInfo{"mfsa $rd", InstructionType::Register}, 137 | DisassemblyInfo{"mtsa $rs", InstructionType::Register}, 138 | DisassemblyInfo{"slt $rd, $rs, $rt", InstructionType::Register}, 139 | DisassemblyInfo{"sltu $rd, $rs, $rt", InstructionType::Register}, 140 | DisassemblyInfo{"dadd $rd, $rs, $rt", InstructionType::Register}, 141 | DisassemblyInfo{"daddu $rd, $rs, $rt", InstructionType::Register}, 142 | DisassemblyInfo{"dsub $rd, $rs, $rt", InstructionType::Register}, 143 | DisassemblyInfo{"dsubu $rd, $rs, $rt", InstructionType::Register}, 144 | DisassemblyInfo{"tge $rs, $rt", InstructionType::Register}, 145 | DisassemblyInfo{"tgeu $rs, $rt", InstructionType::Register}, 146 | DisassemblyInfo{"tlt $rs, $rt", InstructionType::Register}, 147 | DisassemblyInfo{"tltu $rs, $rt", InstructionType::Register}, 148 | DisassemblyInfo{"teq $rs, $rt", InstructionType::Register}, 149 | DisassemblyInfo{"illegal", InstructionType::None}, 150 | DisassemblyInfo{"tne $rs, $rt", InstructionType::Register}, 151 | DisassemblyInfo{"illegal", InstructionType::None}, 152 | DisassemblyInfo{"dsll $rd, $rt, $sa", InstructionType::Register}, 153 | DisassemblyInfo{"illegal", InstructionType::None}, 154 | DisassemblyInfo{"dsrl $rd, $rt, $sa", InstructionType::Register}, 155 | DisassemblyInfo{"dsra $rd, $rt, $sa", InstructionType::Register}, 156 | DisassemblyInfo{"dsll32 $rd, $rt, $sa", InstructionType::Register}, 157 | DisassemblyInfo{"illegal", InstructionType::None}, 158 | DisassemblyInfo{"dsrl32 $rd, $rt, $sa", InstructionType::Register}, 159 | DisassemblyInfo{"dsra32 $rd, $rt, $sa", InstructionType::Register}, 160 | }; 161 | 162 | static std::map cop0_table = { 163 | {0, DisassemblyInfo{"mfc0 $rt, $cd", InstructionType::Register}}, 164 | {4, DisassemblyInfo{"mtc0 $rt, $cd", InstructionType::Register}}, 165 | }; 166 | 167 | static std::map cop0_names = { 168 | {0, "Index"}, 169 | {1, "Random"}, 170 | {2, "EntryLo0"}, 171 | {3, "EntryLo1"}, 172 | {4, "Context"}, 173 | {5, "PageMask"}, 174 | {6, "Wired"}, 175 | {8, "BadVAddr"}, 176 | {9, "Count"}, 177 | {10, "EntryHi"}, 178 | {11, "Compare"}, 179 | {12, "Status"}, 180 | {13, "Cause"}, 181 | {14, "EPC"}, 182 | {15, "PRId"}, 183 | {16, "Config"}, 184 | {23, "BadPAddr"}, 185 | {24, "Debug"}, 186 | {25, "Perf"}, 187 | {28, "TagLo"}, 188 | {29, "TagHi"}, 189 | {30, "ErrorEPC"}, 190 | }; 191 | 192 | template 193 | static std::string ConvertHex(T data) { 194 | std::stringstream stream; 195 | 196 | stream << "0x" << std::hex << data; 197 | 198 | return stream.str(); 199 | } 200 | 201 | static std::string DisassembleImmediate(Instruction inst, u32 pc, std::string format) { 202 | std::string disassembled; 203 | u64 i = 0; 204 | 205 | while (i < format.length()) { 206 | if (format.compare(i, 3, "$rt") == 0) { 207 | disassembled += "$" + reg_names[inst.rt]; 208 | i += 3; 209 | } else if (format.compare(i, 3, "$rs") == 0) { 210 | disassembled += "$" + reg_names[inst.rs]; 211 | i += 3; 212 | } else if (format.compare(i, 4, "$imm") == 0) { 213 | disassembled += ConvertHex(inst.imm); 214 | i += 4; 215 | } else if (format.compare(i, 7, "$offset") == 0) { 216 | disassembled += ConvertHex(pc + (((s16)inst.imm) << 2) + 4); 217 | i += 7; 218 | } else { 219 | disassembled += format[i]; 220 | i++; 221 | } 222 | } 223 | 224 | return disassembled; 225 | } 226 | 227 | static std::string DisassembleJump(Instruction inst, u32 pc, std::string format) { 228 | std::string disassembled; 229 | u64 i = 0; 230 | 231 | while (i < format.length()) { 232 | if (format.compare(i, 7, "$target") == 0) { 233 | disassembled += ConvertHex(((pc + 4) & 0xF0000000) + (inst.offset << 2)); 234 | i += 7; 235 | } else { 236 | disassembled += format[i]; 237 | i++; 238 | } 239 | } 240 | 241 | return disassembled; 242 | } 243 | 244 | static std::string DisassembleRegister(Instruction inst, u32 pc, std::string format) { 245 | std::string disassembled; 246 | u64 i = 0; 247 | 248 | while (i < format.length()) { 249 | if (format.compare(i, 3, "$rt") == 0) { 250 | disassembled += "$" + reg_names[inst.rt]; 251 | i += 3; 252 | } else if (format.compare(i, 3, "$rd") == 0) { 253 | disassembled += "$" + reg_names[inst.rd]; 254 | i += 3; 255 | } else if (format.compare(i, 3, "$cd") == 0) { 256 | disassembled += cop0_names[inst.rd]; 257 | i += 3; 258 | } else if (format.compare(i, 3, "$rs") == 0) { 259 | disassembled += "$" + reg_names[inst.rs]; 260 | i += 3; 261 | } else if (format.compare(i, 3, "$sa") == 0) { 262 | disassembled += ConvertHex(inst.imm5); 263 | i += 3; 264 | } else { 265 | disassembled += format[i]; 266 | i++; 267 | } 268 | } 269 | 270 | return disassembled; 271 | } 272 | 273 | std::string DisassembleInstruction(Instruction inst, u32 pc) { 274 | std::string disassembled; 275 | 276 | DisassemblyInfo info = primary_table[inst.opcode]; 277 | 278 | if (info.format.compare("secondary") == 0) { 279 | info = secondary_table[inst.func]; 280 | } else if (info.format.compare("cop0") == 0) { 281 | info = cop0_table[inst.rs]; 282 | } 283 | 284 | switch (info.type) { 285 | case InstructionType::Immediate: 286 | disassembled = DisassembleImmediate(inst, pc, info.format); 287 | break; 288 | case InstructionType::Jump: 289 | disassembled = DisassembleJump(inst, pc, info.format); 290 | break; 291 | case InstructionType::Register: 292 | disassembled = DisassembleRegister(inst, pc, info.format); 293 | break; 294 | case InstructionType::None: 295 | disassembled = info.format; 296 | break; 297 | } 298 | 299 | return disassembled; 300 | } 301 | 302 | std::string GetRegisterName(int reg) { 303 | return reg_names[reg]; 304 | } 305 | 306 | } // namespace iop -------------------------------------------------------------------------------- /src/core/ee/dmac.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common/log.h" 3 | #include "common/memory.h" 4 | #include "core/ee/dmac.h" 5 | #include "core/system.h" 6 | 7 | static const char* channel_names[10] = { 8 | "VIF0", "VIF1", "GIF", "IPU_FROM", "IPU_TO", 9 | "SIF0", "SIF1", "SIF2", "SPR_FROM", "SPR_TO", 10 | }; 11 | 12 | namespace ee { 13 | 14 | DMAC::DMAC(System& system) : system(system) {} 15 | 16 | void DMAC::Reset() { 17 | control = 0; 18 | interrupt_status = 0; 19 | priority_control = 0; 20 | skip_quadword = 0; 21 | ringbuffer_size = 0; 22 | ringbuffer_offset = 0; 23 | disabled_status = 0x1201; 24 | 25 | for (int i = 0; i < 10; i++) { 26 | channels[i].control.data = 0; 27 | channels[i].address = 0; 28 | channels[i].tag_address = 0; 29 | channels[i].quadword_count = 0; 30 | channels[i].saved_tag_address0 = 0; 31 | channels[i].saved_tag_address1 = 0; 32 | channels[i].scratchpad_address = 0; 33 | channels[i].end_transfer = false; 34 | } 35 | } 36 | 37 | u32 DMAC::ReadChannel(u32 addr) { 38 | int index = GetChannelIndex(addr); 39 | 40 | switch (addr & 0xFF) { 41 | case 0x00: 42 | // common::Log("[DMAC %d] control read %08x", index, channels[index].control); 43 | return channels[index].control.data; 44 | case 0x10: 45 | return channels[index].address; 46 | case 0x20: 47 | return channels[index].quadword_count; 48 | case 0x30: 49 | return channels[index].tag_address; 50 | default: 51 | common::Error("[ee::DMAC] Handle %02x", addr & 0xFF); 52 | } 53 | 54 | return 0; 55 | } 56 | 57 | void DMAC::WriteRegister(u32 addr, u32 data) { 58 | switch (addr) { 59 | case 0x1000e000: 60 | common::Log("[ee::DMAC] D_CTRL write %08x", data); 61 | control = data; 62 | break; 63 | case 0x1000e010: 64 | common::Log("[ee::DMAC] D_STAT write %08x", data); 65 | 66 | // for bits (0..15) they get cleared if 1 is written 67 | interrupt_status &= ~(data & 0xffff); 68 | 69 | // for bits (16..31) they get reversed if 1 is written 70 | interrupt_status ^= (data & 0xffff0000); 71 | 72 | CheckInterruptSignal(); 73 | break; 74 | case 0x1000e020: 75 | common::Log("[ee::DMAC] D_PCR write %08x", data); 76 | priority_control = data; 77 | break; 78 | case 0x1000e030: 79 | common::Log("[ee::DMAC] D_SQWC write %08x", data); 80 | skip_quadword = data; 81 | break; 82 | case 0x1000e040: 83 | common::Log("[ee::DMAC] D_RBSR write %08x", data); 84 | ringbuffer_size = data; 85 | break; 86 | case 0x1000e050: 87 | common::Log("[ee::DMAC] D_RBOR write %08x", data); 88 | ringbuffer_offset = data; 89 | break; 90 | case 0x1000f590: 91 | common::Log("[ee::DMAC] D_ENABLE write %08x", data); 92 | disabled_status = data; 93 | break; 94 | default: 95 | if (addr >= 0x1000e000) { 96 | common::Error("[ee::DMAC] handle write %08x = %08x", addr, data); 97 | } else { 98 | WriteChannel(addr, data); 99 | } 100 | 101 | break; 102 | } 103 | } 104 | 105 | void DMAC::WriteChannel(u32 addr, u32 data) { 106 | int index = GetChannelIndex(addr); 107 | const char* channel_name = channel_names[index]; 108 | 109 | switch (addr & 0xff) { 110 | case 0x00: 111 | common::Log("[ee::DMAC] %s Dn_CHCR write %08x", channel_name, data); 112 | channels[index].control.data = data; 113 | 114 | StartTransfer(index); 115 | break; 116 | case 0x10: 117 | common::Log("[ee::DMAC] %s Dn_MADR write %08x", channel_name, data); 118 | channels[index].address = data & ~0xf; 119 | break; 120 | case 0x20: 121 | // In normal and interleaved mode, the transfer ends when QWC reaches zero. Chain mode behaves differently 122 | common::Log("[ee::DMAC] %s Dn_QWC write %08x", channel_name, data); 123 | channels[index].quadword_count = data & 0xffff; 124 | break; 125 | case 0x30: 126 | common::Log("[ee::DMAC] %s Dn_TADR write %08x", channel_name, data); 127 | channels[index].tag_address = data & ~0xf; 128 | break; 129 | case 0x40: 130 | common::Log("[ee::DMAC] %s Dn_ASR0 write %08x", channel_name, data); 131 | channels[index].saved_tag_address0 = data & ~0xf; 132 | break; 133 | case 0x50: 134 | common::Log("[ee::DMAC] %s Dn_ASR1 write %08x", channel_name, data); 135 | channels[index].saved_tag_address1 = data & ~0xf; 136 | break; 137 | case 0x80: 138 | common::Log("[ee::DMAC] %s Dn_SADR write %08x", channel_name, data); 139 | channels[index].scratchpad_address = data & ~0xf; 140 | break; 141 | default: 142 | common::Error("[ee::DMAC] Handle channel with identifier %02x and data %08x", addr & 0xff, data); 143 | } 144 | } 145 | 146 | int DMAC::GetChannelIndex(u32 addr) { 147 | auto channel = (addr >> 8) & 0xff; 148 | switch (channel) { 149 | case 0x80: 150 | return static_cast(ChannelType::VIF0); 151 | case 0x90: 152 | return static_cast(ChannelType::VIF1); 153 | case 0xA0: 154 | return static_cast(ChannelType::GIF); 155 | case 0xB0: 156 | return static_cast(ChannelType::IPUFrom); 157 | case 0xB4: 158 | return static_cast(ChannelType::IPUTo); 159 | case 0xC0: 160 | return static_cast(ChannelType::SIF0); 161 | case 0xC4: 162 | return static_cast(ChannelType::SIF1); 163 | case 0xC8: 164 | return static_cast(ChannelType::SIF2); 165 | case 0xD0: 166 | return static_cast(ChannelType::FromSPR); 167 | case 0xD4: 168 | return static_cast(ChannelType::ToSPR); 169 | default: 170 | common::Error("[ee::DMAC] Random behaviour!"); 171 | } 172 | 173 | return 0; 174 | } 175 | 176 | u32 DMAC::ReadInterruptStatus() { 177 | common::Log("[ee::DMAC] D_STAT read %08x", interrupt_status); 178 | return interrupt_status; 179 | } 180 | 181 | u32 DMAC::ReadControl() { 182 | common::Log("[ee::DMAC] read control %08x", control); 183 | return control; 184 | } 185 | 186 | u32 DMAC::ReadPriorityControl() { 187 | common::Log("[ee::DMAC] read priority control %08x", priority_control); 188 | return priority_control; 189 | } 190 | 191 | u32 DMAC::ReadSkipQuadword() { 192 | common::Log("[ee::DMAC] read skip quadword %08x", skip_quadword); 193 | return skip_quadword; 194 | } 195 | 196 | void DMAC::CheckInterruptSignal() { 197 | bool irq = false; 198 | 199 | for (int i = 0; i < 10; i++) { 200 | if ((interrupt_status & (1 << i)) && (interrupt_status & (1 << (16 + i)))) { 201 | common::Log("[ee::DMAC] %s interrupt sent", channel_names[i]); 202 | irq = true; 203 | break; 204 | } 205 | } 206 | 207 | system.ee.RaiseInterrupt(1, irq); 208 | } 209 | 210 | // note: 211 | // dmac can transfer one quadword (16 bytes / 128 bits) per bus cycle 212 | void DMAC::Run(int cycles) { 213 | if (!(control & 0x1) || (disabled_status & (1 << 16))) { 214 | return; 215 | } 216 | 217 | while (cycles--) { 218 | for (int i = 0; i < 10; i++) { 219 | auto& channel = channels[i]; 220 | if (channel.control.busy) { 221 | Transfer(i); 222 | } 223 | } 224 | } 225 | } 226 | 227 | void DMAC::Transfer(int index) { 228 | switch (static_cast(index)) { 229 | case ChannelType::GIF: 230 | do_gif_transfer(); 231 | break; 232 | case ChannelType::SIF0: 233 | do_sif0_transfer(); 234 | break; 235 | case ChannelType::SIF1: 236 | do_sif1_transfer(); 237 | break; 238 | case ChannelType::ToSPR: 239 | do_to_spr_transfer(); 240 | break; 241 | default: 242 | common::Error("handle dma transfer with channel %d", index); 243 | } 244 | } 245 | 246 | void DMAC::do_gif_transfer() { 247 | auto& channel = channels[2]; 248 | 249 | if (channel.quadword_count) { 250 | u128 data = system.ee.read(channel.address); 251 | 252 | system.gif.SendPath3(data); 253 | channel.address += 16; 254 | channel.quadword_count--; 255 | } else { 256 | EndTransfer(2); 257 | } 258 | } 259 | 260 | void DMAC::do_sif0_transfer() { 261 | auto& channel = channels[5]; 262 | 263 | if (channel.quadword_count) { 264 | // dmac can only transfer a quadword at a time 265 | if (system.sif.GetSIF0FIFOSize() >= 4) { 266 | for (int i = 0; i < 4; i++) { 267 | u32 data = system.sif.ReadSIF0FIFO(); 268 | 269 | common::Log("[ee::DMAC] SIF0 reading data from fifo %08x", data); 270 | 271 | system.ee.write(channel.address, data); 272 | channel.address += 4; 273 | } 274 | 275 | channel.quadword_count--; 276 | } 277 | } else if (channel.end_transfer) { 278 | EndTransfer(5); 279 | } else { 280 | if (system.sif.GetSIF0FIFOSize() >= 2) { 281 | // form a dmatag 282 | u64 dma_tag = 0; 283 | 284 | dma_tag |= system.sif.ReadSIF0FIFO(); 285 | dma_tag |= (u64)system.sif.ReadSIF0FIFO() << 32; 286 | 287 | common::Log("[ee::DMAC] SIF0 read DMATag %016lx", dma_tag); 288 | 289 | channel.quadword_count = dma_tag & 0xFFFF; 290 | channel.address = (dma_tag >> 32) & 0xFFFFFFF0; 291 | channel.tag_address += 16; 292 | 293 | // Update upper 16 bits of control with upper 16 bits of dma tag. 294 | channel.control.dmatag_upper = (dma_tag >> 16) & 0xffff; 295 | 296 | bool irq = (dma_tag >> 31) & 0x1; 297 | if (irq && channel.control.dmatag_irq) { 298 | channel.end_transfer = true; 299 | } 300 | } 301 | } 302 | } 303 | 304 | void DMAC::do_sif1_transfer() { 305 | auto& channel = channels[6]; 306 | if (channel.quadword_count) { 307 | // push data to the sif1 fifo 308 | u128 data = system.ee.read(channel.address); 309 | 310 | common::Log("[ee::DMAC] SIF1 Fifo write %016lx%016lx dstat %08x", data.hi, data.lo, interrupt_status); 311 | 312 | system.sif.write_sif1_fifo(data); 313 | 314 | // madr and qwc must be updated as the transfer proceeds 315 | channel.address += 16; 316 | channel.quadword_count--; 317 | } else if (channel.end_transfer) { 318 | EndTransfer(6); 319 | } else { 320 | DoSourceChain(6); 321 | } 322 | } 323 | 324 | void DMAC::do_to_spr_transfer() { 325 | // This channel will transfer in a burst. 326 | // It can also use source chain and interleaving. 327 | // TODO: do we need to handle cycle stealing? 328 | 329 | // Scratchpad is considered a peripheral, so if chain mode is enabled 330 | // it's source chain mode. 331 | auto& channel = channels[9]; 332 | 333 | assert(channel.control.mode == Channel::Mode::Normal); 334 | assert(!channel.control.from_memory); 335 | 336 | if (channel.control.mode != Channel::Mode::Normal) { 337 | LOG_TODO_NO_ARGS("handle non-normal mode for to spr transfer"); 338 | } 339 | 340 | if (channel.quadword_count > 0) { 341 | for (u32 i = 0; i < channel.quadword_count; i++) { 342 | // Ensure the transfer address is in the physical address range. 343 | u128 data = read_u128(channel.address & 0x7fffffff); 344 | 345 | // Select writes to scratchpad. 346 | write_u128(channel.scratchpad_address | (1 << 31), data); 347 | 348 | // Update channel registers 349 | channel.address += 16; 350 | channel.scratchpad_address += 16; 351 | channel.quadword_count--; 352 | } 353 | 354 | EndTransfer(9); 355 | } else { 356 | LOG_TODO_NO_ARGS("handle to spr transfer with no quadword count"); 357 | } 358 | } 359 | 360 | void DMAC::StartTransfer(int index) { 361 | common::Log("[ee::DMAC] %s start transfer", channel_names[index]); 362 | 363 | // in normal mode we shouldn't worry about dmatag reading 364 | channels[index].end_transfer = channels[index].control.mode == Channel::Mode::Normal; 365 | } 366 | 367 | void DMAC::EndTransfer(int index) { 368 | common::Log("[ee::DMAC] %s end transfer", channel_names[index]); 369 | 370 | channels[index].end_transfer = false; 371 | channels[index].control.busy = false; 372 | 373 | // raise the stat flag in the stat register 374 | interrupt_status |= (1 << index); 375 | 376 | CheckInterruptSignal(); 377 | } 378 | 379 | void DMAC::DoSourceChain(int index) { 380 | auto& channel = channels[index]; 381 | u128 data = system.ee.read(channel.tag_address); 382 | 383 | // TODO: create a union type for dma tag to easily extract fields 384 | u64 dma_tag = data.lo; 385 | 386 | common::Log("[ee::DMAC] %s read DMATag %016lx d stat %08x", channel_names[index], dma_tag, interrupt_status); 387 | 388 | channel.quadword_count = dma_tag & 0xFFFF; 389 | 390 | // Update upper 16 bits of control with upper 16 bits of dma tag. 391 | channel.control.dmatag_upper = (dma_tag >> 16) & 0xffff; 392 | 393 | u8 id = (dma_tag >> 28) & 0x7; 394 | 395 | // lower 4 bits must be 0 396 | u32 addr = (dma_tag >> 32) & 0xFFFFFFF0; 397 | 398 | switch (id) { 399 | case 0: 400 | // MADR=DMAtag.ADDR 401 | // TADR+=16 402 | // tag_end=true 403 | channel.address = addr; 404 | channel.tag_address += 16; 405 | channel.end_transfer = true; 406 | break; 407 | case 2: 408 | // MADR=TADR+16 409 | // TADR=DMAtag.ADDR 410 | channel.address = channel.tag_address + 16; 411 | channel.tag_address = addr; 412 | break; 413 | case 3: 414 | // MADR=DMAtag.ADDR 415 | // TADR+=16 416 | channel.address = addr; 417 | channel.tag_address += 16; 418 | break; 419 | default: 420 | common::Error("[ee::DMAC] %s handle DMATag id %d", channel_names[index], id); 421 | } 422 | 423 | bool irq = (dma_tag >> 31) & 0x1; 424 | if (irq && channel.control.dmatag_irq) { 425 | channel.end_transfer = true; 426 | } 427 | } 428 | 429 | u128 DMAC::read_u128(u32 addr) { 430 | if (addr < 0x2000000) { 431 | return common::Read(system.ee.rdram(), addr); 432 | } 433 | 434 | LOG_TODO("handle dma 128-bit read with address %08x", addr); 435 | } 436 | 437 | void DMAC::write_u128(u32 addr, u128 data) { 438 | if ((addr & (1 << 31)) || (addr & 0x07000000) == 0x07000000) { 439 | u32 masked_addr = addr & 0x3ff0; 440 | common::Write(system.ee.scratchpad(), data, masked_addr); 441 | return; 442 | } 443 | 444 | LOG_TODO("handle dma 128-bit write with address %08x", addr); 445 | } 446 | 447 | } // namespace ee --------------------------------------------------------------------------------