mirror of
https://github.com/elmasas/lcs-recomp
synced 2026-09-26 08:41:05 -04:00
143 lines
6.4 KiB
C++
143 lines
6.4 KiB
C++
#include "psprecomp/common.hpp"
|
|
#include "psprecomp/elf32.hpp"
|
|
#include "psprecomp/runtime.hpp"
|
|
#include "lcs_profile.hpp"
|
|
#include "display_window.hpp"
|
|
#include "lcs_audio_output.hpp"
|
|
#include "ge_gpu_backend.hpp"
|
|
#include "ge_renderer.hpp"
|
|
#include "lcs_render_config.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <limits>
|
|
|
|
#ifndef LCS_NO_GENERATED
|
|
namespace psprecomp {
|
|
void register_generated_functions(Runtime &runtime);
|
|
} // namespace psprecomp
|
|
#endif
|
|
|
|
int main(int argc, char **argv) {
|
|
std::filesystem::path elf_path = "game/EBOOT.ELF";
|
|
std::filesystem::path game_root = "game";
|
|
std::uint64_t max_dispatches = std::numeric_limits<std::uint64_t>::max();
|
|
double max_seconds = 0.0;
|
|
for (int i = 1; i < argc; ++i) {
|
|
const std::string_view arg = argv[i];
|
|
if (arg == "--game" && i + 1 < argc) {
|
|
game_root = argv[++i];
|
|
elf_path = game_root / "EBOOT.ELF";
|
|
} else if (arg == "--elf" && i + 1 < argc) {
|
|
elf_path = argv[++i];
|
|
} else if ((arg == "--max-dispatches" || arg == "--dispatch-cap") && i + 1 < argc) {
|
|
max_dispatches = std::strtoull(argv[++i], nullptr, 0);
|
|
} else if (arg == "--max-seconds" && i + 1 < argc) {
|
|
max_seconds = std::strtod(argv[++i], nullptr);
|
|
} else {
|
|
std::cerr << "Ignoring unrecognised argument \"" << arg << "\"\n";
|
|
}
|
|
}
|
|
|
|
try {
|
|
std::error_code executable_error;
|
|
const std::filesystem::path executable_directory =
|
|
argc > 0 ? std::filesystem::absolute(argv[0], executable_error).parent_path()
|
|
: std::filesystem::current_path();
|
|
lcs::initialize_lcs_render_configuration(executable_directory);
|
|
|
|
auto elf = psprecomp::Elf32Image::from_file(elf_path);
|
|
|
|
psprecomp::Runtime runtime;
|
|
runtime.set_game_root(game_root);
|
|
|
|
const auto stats = elf.load_and_relocate(runtime.memory(), psprecomp::kDefaultPspUserLoadBase);
|
|
std::cout << "Loaded " << elf_path.string() << " (" << stats.total << " relocations applied)\n" << std::flush;
|
|
|
|
#ifndef LCS_NO_GENERATED
|
|
psprecomp::register_generated_functions(runtime);
|
|
#endif
|
|
std::cout << "Registered " << runtime.function_count() << " generated functions\n" << std::flush;
|
|
|
|
|
|
// User memory arena starts right after the ELF image.
|
|
std::uint64_t image_end = 0u;
|
|
for (std::size_t index = 0; index < elf.segments().size(); ++index) {
|
|
const auto &segment = elf.segments()[index];
|
|
if (segment.type != 1u) continue; // PT_LOAD
|
|
const std::uint64_t start = elf.segment_runtime_address(index, psprecomp::kDefaultPspUserLoadBase);
|
|
image_end = std::max(image_end, start + segment.memory_size);
|
|
}
|
|
const std::uint32_t user_arena_start =
|
|
static_cast<std::uint32_t>((image_end + 0xFFu) & ~0xFFull);
|
|
lcs::display_window_init();
|
|
lcs::install_profile(runtime, user_arena_start);
|
|
|
|
std::string gpu_backend_error;
|
|
if (!lcs::initialize_ge_gpu_backend(gpu_backend_error))
|
|
std::cerr << "[ge] GPU backend unavailable: " << gpu_backend_error << "\n";
|
|
lcs::display_window_attach_gpu_backend();
|
|
const lcs::GeGpuBackendReport gpu_start = lcs::ge_gpu_backend_report();
|
|
std::cerr << "[ge] backend requested=" << lcs::ge_gpu_backend_name(gpu_start.requested)
|
|
<< " active=" << lcs::ge_gpu_backend_name(gpu_start.active)
|
|
<< " status=" << gpu_start.message << "\n";
|
|
lcs::set_wall_clock_limit(max_seconds);
|
|
|
|
if (const auto module = elf.find_module_info(runtime.memory(), psprecomp::kDefaultPspUserLoadBase)) {
|
|
runtime.cpu().set_gpr(28, module->gp);
|
|
} else {
|
|
throw psprecomp::Error("PSP module info not found after relocation");
|
|
}
|
|
runtime.cpu().set_gpr(31, 0u);
|
|
runtime.cpu().set_gpr(4, 0u);
|
|
runtime.cpu().set_gpr(5, 0u);
|
|
|
|
const std::uint32_t entry = elf.runtime_entry(psprecomp::kDefaultPspUserLoadBase);
|
|
std::cout << "Running from entry " << psprecomp::hex32(entry) << "\n" << std::flush;
|
|
runtime.run(entry, max_dispatches);
|
|
lcs::ge_worker_shutdown();
|
|
|
|
if (runtime.stopped()) {
|
|
std::cout << "Runtime stopped: " << runtime.stop_reason() << "\n";
|
|
lcs::debug_dump_threads();
|
|
lcs::dump_pc_profile();
|
|
lcs::dump_framebuffer_stats(runtime);
|
|
lcs::dump_watched_memory(runtime);
|
|
lcs::dump_disc_read_stats();
|
|
}
|
|
if (std::getenv("PSPRECOMP_GE_PHASE_DIAG") != nullptr) {
|
|
const lcs::GePhaseTotals phases = lcs::ge_phase_totals();
|
|
std::cerr << "[ge-phase] pixel_loop_ms=" << phases.pixel_loop_ns / 1000000u
|
|
<< " draw_setup_ms=" << phases.draw_setup_ns / 1000000u
|
|
<< " texture_upload_ms=" << phases.texture_upload_ns / 1000000u
|
|
<< " vertex_decode_ms=" << phases.vertex_decode_ns / 1000000u
|
|
<< " triangle_prep_ms=" << phases.triangle_prep_ns / 1000000u
|
|
<< " triangles=" << phases.triangles
|
|
<< " primitives=" << phases.primitives
|
|
<< " vertices=" << phases.vertices << "\n";
|
|
}
|
|
runtime.report_hle_histogram(60u);
|
|
const lcs::GeGpuBackendReport gpu_end = lcs::ge_gpu_backend_report();
|
|
std::cerr << "[ge-gpu] frames=" << gpu_end.game_frames
|
|
<< " draws=" << gpu_end.game_draw_calls
|
|
<< " tris=" << gpu_end.game_triangles
|
|
<< " verts=" << gpu_end.game_vertices
|
|
<< " hw_transform_draws=" << gpu_end.hw_transform_draw_calls
|
|
<< " tex_uploads=" << gpu_end.decoded_texture_uploads
|
|
<< " tex_hits=" << gpu_end.texture_cache_hits
|
|
<< " rejected=" << gpu_end.rejected_gpu_draws
|
|
<< " no_texture=" << gpu_end.game_textured_draws_without_texture
|
|
<< " vertex_overflows=" << gpu_end.game_vertex_overflows
|
|
<< " no_display_target=" << gpu_end.frames_without_displayed_target
|
|
<< " presented=" << gpu_end.gpu_frame_presented_to_window << "\n";
|
|
lcs::audio_output_shutdown();
|
|
} catch (const std::exception &ex) {
|
|
std::cerr << "LCSNative error: " << ex.what() << "\n";
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|