Something went wrong. Try again.
nixos/home-manager config
Something went wrong. Try again.
7.7 kB · 197 lines
C++
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198#include <mia/mia.hpp>#include <nall/main.hpp>#include <nall/gdb/server.hpp>#include <chrono>
namespace ares::Nintendo64 { auto load(Node::System& node, string name) -> bool; auto option(string name, string value) -> bool;}
struct Headless : ares::Platform { ares::Node::System root; std::shared_ptr<mia::Pak> system; std::shared_ptr<mia::Pak> game; u64 frames = 0; bool quit = false;
auto pak(ares::Node::Object node) -> std::shared_ptr<vfs::directory> override { if(node->name() == "Nintendo 64") return system->pak; if(node->name() == "Nintendo 64 Cartridge") return game->pak; return {}; }
auto status(string_view message) -> void override { fprintf(stderr, "[ares] %.*s\n", (int)message.size(), message.data()); }
auto event(ares::Event event) -> void override { if(event == ares::Event::Shutdown) quit = true; }
auto log(ares::Node::Debugger::Tracer::Tracer node, string_view message) -> void override { if(!node->terminal()) return; fwrite(message.data(), 1, message.size(), stdout); if(node->autoLineBreak()) fputc('\n', stdout); fflush(stdout); }
bool mash = false; u64 mashUntil = ~0ull; struct Press { u64 from, to; string button; }; std::vector<Press> presses; struct Stick { u64 from, to; s32 x, y; }; std::vector<Stick> sticks; struct Shot { u64 frame; string path; }; std::vector<Shot> shots;
// --mash alternates between pressing A and Start every few frames; --press holds a button over a frame range. auto input(ares::Node::Input::Input node) -> void override { if(auto axis = node->cast<ares::Node::Input::Axis>()) { s32 value = 0; for(auto& s : sticks) if(frames >= s.from && frames < s.to) value = node->name() == "X-Axis" ? s.x : s.y; axis->setValue(value); return; } auto button = node->cast<ares::Node::Input::Button>(); if(!button) return; bool value = false; if(mash && frames < mashUntil) { u64 phase = frames / 8 % 4; if(node->name() == "A") value = phase == 0; if(node->name() == "Start") value = phase == 2; } for(auto& p : presses) if(node->name() == p.button && frames >= p.from && frames < p.to) value = true; button->setValue(value); }
std::vector<u32> lastFrame; u32 lastWidth = 0, lastHeight = 0;
auto video(ares::Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void override { frames++; for(auto& shot : shots) if(shot.frame == frames) { lastWidth = width; lastHeight = height; lastFrame.resize(width * height); for(u32 y : range(height)) memcpy(&lastFrame[y * width], data + y * (pitch / 4), width * 4); writeScreenshot(shot.path); } lastWidth = width; lastHeight = height; lastFrame.resize(width * height); for(u32 y : range(height)) memcpy(&lastFrame[y * width], data + y * (pitch / 4), width * 4); }
auto writeScreenshot(const string& path) -> void { FILE* f = fopen(path, "wb"); if(!f) return; fprintf(f, "P6\n%u %u\n255\n", lastWidth, lastHeight); for(u32 pixel : lastFrame) { u8 rgb[3] = {u8(pixel >> 16), u8(pixel >> 8), u8(pixel)}; fwrite(rgb, 1, 3, f); } fclose(f); }};
auto nall::main(Arguments arguments) -> void { u64 maxFrames = 600; bool gpu = arguments.take("--gpu"); bool mash = arguments.take("--mash"); string framesArg; if(arguments.take("--frames", framesArg)) maxFrames = framesArg.natural(); string screenshotPath; arguments.take("--screenshot", screenshotPath); std::vector<string> peeks; string peek; while(arguments.take("--peek", peek)) peeks.push_back(peek); string mashUntilArg; u64 mashUntil = ~0ull; if(arguments.take("--mash-until", mashUntilArg)) mashUntil = mashUntilArg.natural(); std::vector<string> pressArgs, stickArgs, shotArgs; string arg; while(arguments.take("--press", arg)) pressArgs.push_back(arg); while(arguments.take("--stick", arg)) stickArgs.push_back(arg); while(arguments.take("--shot", arg)) shotArgs.push_back(arg); string gdbPort; arguments.take("--gdb", gdbPort); string romPath = arguments.take(); if(!romPath) { print("usage: ares-headless [--gpu] [--frames N] [--screenshot PATH] [--peek ADDR:LEN]...\n" " [--mash] [--mash-until FRAME] [--press FROM:TO:BUTTON]...\n" " [--stick FROM:TO:X:Y]... [--shot FRAME:PATH]... [--gdb PORT] rom.z64\n"); exit(2); }
mia::setHomeLocation([] { return string{Path::temporary(), "ares-headless/"}; }); mia::setSaveLocation([] { return string{Path::temporary(), "ares-headless/saves/"}; });
Headless headless; ares::platform = &headless; headless.mash = mash; headless.mashUntil = mashUntil; // FROM:TO:BUTTON, e.g. 1000:1004:C-Up for(auto& a : pressArgs) { auto p = nall::split(a, ":"); headless.presses.push_back({p[0].natural(), p[1].natural(), p[2]}); } // FROM:TO:X:Y, with X and Y in -32768..32767 for(auto& a : stickArgs) { auto p = nall::split(a, ":"); headless.sticks.push_back({p[0].natural(), p[1].natural(), (s32)p[2].integer(), (s32)p[3].integer()}); } // FRAME:PATH for(auto& a : shotArgs) { auto p = nall::split(a, ":", 1); headless.shots.push_back({p[0].natural(), p[1]}); }
headless.system = mia::System::create("Nintendo 64"); if(headless.system->load() != successful) { print("failed to load system\n"); exit(1); } headless.game = mia::Medium::create("Nintendo 64"); if(headless.game->load(romPath) != successful) { print("failed to load rom\n"); exit(1); }
ares::Nintendo64::option("Enable GPU acceleration", gpu ? "true" : "false"); ares::Nintendo64::option("Homebrew Mode", "true"); // GDB watchpoints only see memory accesses made by the interpreter ares::Nintendo64::option("Recompiler", gdbPort ? "false" : "true"); ares::Nintendo64::option("Expansion Pak", "true");
if(!ares::Nintendo64::load(headless.root, "[Nintendo] Nintendo 64 (NTSC)")) { print("failed to load core\n"); exit(1); } if(auto port = headless.root->find<ares::Node::Port>("Cartridge Slot")) { port->allocate(); port->connect(); } if(auto port = headless.root->find<ares::Node::Port>("Controller Port 1")) { port->allocate("Gamepad"); port->connect(); } headless.root->power(false);
if(gdbPort) { nall::GDB::server.open(gdbPort.natural(), true); fprintf(stderr, "[ares-headless] waiting for GDB on port %s\n", (const char*)gdbPort); while(!nall::GDB::server.hasClient()) { nall::GDB::server.updateLoop(); usleep(10000); } }
auto start = std::chrono::steady_clock::now(); while(!headless.quit && headless.frames < maxFrames) { if(gdbPort) { nall::GDB::server.updateLoop(); if(nall::GDB::server.isHalted()) continue; } headless.root->run(); } double seconds = std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
fprintf(stderr, "[ares-headless] %llu frames in %.2fs (%.1f fps)\n", (unsigned long long)headless.frames, seconds, headless.frames / seconds); if(screenshotPath) headless.writeScreenshot(screenshotPath); ares::Node::Debugger::Memory ram; for(auto& memory : headless.root->find<ares::Node::Debugger::Memory>()) { if(memory->name() == "RDRAM") ram = memory; } if(ram) { for(auto& spec : peeks) { auto parts = nall::split(spec, ":"); u32 address = parts[0].hex() & 0x1fffffff; u32 length = parts.size() > 1 ? parts[1].natural() : 16; printf("%08x:", address | 0x80000000); for(u32 i : range(length)) printf(" %02x", (u32)ram->read(address + i)); printf("\n"); } } if(gdbPort) nall::GDB::server.close(); headless.root->unload(); exit(headless.quit ? 0 : 3);}