mirror of
https://github.com/RetroDECK/RetroQUEST.git
synced 2025-04-21 01:24:06 +00:00
feat(libretro_binding): some advancement but broken on godot side
This commit is contained in:
parent
9c612af548
commit
6f7436e8d1
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@ godot-cpp
|
|||
.sconsign.dblite
|
||||
libretro_binding/libretro_binding.os
|
||||
libretro_binding/libretro_binding.so
|
||||
.sconsign.dblite
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "external/gdlibretro"]
|
||||
path = external/gdlibretro
|
||||
url = https://github.com/gabrielmedici/gdlibretro
|
|
@ -1,40 +0,0 @@
|
|||
# Dichiarazione del progetto
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(gdretroplay)
|
||||
|
||||
# Imposta il C++ standard
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Percorsi per Godot C++ bindings
|
||||
message(STATUS "Godot C++ Bindings Path: $ENV{GODOT_CPP_BINDINGS_PATH}")
|
||||
|
||||
include_directories($ENV{GODOT_CPP_BINDINGS_PATH}/include)
|
||||
include_directories($ENV{GODOT_CPP_BINDINGS_PATH}/include/core)
|
||||
include_directories($ENV{GODOT_CPP_BINDINGS_PATH}/include/gen)
|
||||
|
||||
# File sorgenti
|
||||
file(GLOB SOURCES "src/*.cpp")
|
||||
message(STATUS "Sources found: ${SOURCES}")
|
||||
|
||||
# Creazione della libreria condivisa
|
||||
add_library(${PROJECT_NAME} SHARED ${SOURCES})
|
||||
|
||||
# Configurazioni specifiche per piattaforma
|
||||
if(UNIX AND NOT ANDROID)
|
||||
set(CMAKE_SHARED_LIBRARY_PREFIX "")
|
||||
set(CMAKE_SHARED_LIBRARY_SUFFIX ".so")
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
|
||||
set(CMAKE_SHARED_LIBRARY_PREFIX "")
|
||||
set(CMAKE_SHARED_LIBRARY_SUFFIX ".so")
|
||||
endif()
|
||||
|
||||
# Imposta le proprietà del target
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "gdretroplay")
|
||||
|
||||
# Linka le librerie di Godot
|
||||
link_directories($ENV{GODOT_CPP_BINDINGS_PATH}/bin)
|
||||
target_link_libraries(${PROJECT_NAME} godot-cpp)
|
|
@ -1,13 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
GODOT_CPP_BINDINGS_PATH="$HOME/gits/godot-cpp"
|
||||
|
||||
cd "$GODOT_CPP_BINDINGS_PATH"
|
||||
scons platform=linux target=template_release generate_bindings=yes
|
||||
cd -
|
||||
|
||||
rm -rf build_linux
|
||||
mkdir build_linux
|
||||
cd build_linux
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build .
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"entry_symbol": "gdextension_gdretroplay_library_init",
|
||||
"library": [
|
||||
{
|
||||
"platform": "Linux",
|
||||
"debug": "build/gdretroplay.linux.debug.so",
|
||||
"release": "build/gdretroplay.linux.release.so"
|
||||
},
|
||||
{
|
||||
"platform": "Android",
|
||||
"debug": "build/gdretroplay.android.debug.so",
|
||||
"release": "build/gdretroplay.android.release.so"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
#include "emulator_core.h"
|
||||
#include <godot_cpp/classes/file_access.hpp>
|
||||
#include <godot_cpp/classes/image.hpp>
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace EmulatorSystem {
|
||||
|
||||
EmulatorCore::EmulatorCore() {
|
||||
core = std::make_unique<CoreFunctions>();
|
||||
frame_texture.instantiate();
|
||||
audio_stream.instantiate();
|
||||
input_state.fill(0);
|
||||
}
|
||||
|
||||
EmulatorCore::~EmulatorCore() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
bool EmulatorCore::initialize_core(const godot::String& core_path) {
|
||||
cleanup();
|
||||
|
||||
core->handle = dlopen(core_path.utf8().get_data(), RTLD_LAZY);
|
||||
if (!core->handle) return false;
|
||||
|
||||
if (!load_core_functions()) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
core->init();
|
||||
setup_av_system();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmulatorCore::load_core_functions() {
|
||||
if (!core->handle) return false;
|
||||
|
||||
#define LOAD_SYM(sym) \
|
||||
core->sym = (decltype(core->sym))dlsym(core->handle, "retro_" #sym)
|
||||
|
||||
LOAD_SYM(init);
|
||||
LOAD_SYM(deinit);
|
||||
LOAD_SYM(run);
|
||||
LOAD_SYM(load_game);
|
||||
LOAD_SYM(save_game_state);
|
||||
LOAD_SYM(load_game_state);
|
||||
LOAD_SYM(get_memory_data);
|
||||
LOAD_SYM(set_memory_data);
|
||||
LOAD_SYM(set_controller_port_device);
|
||||
LOAD_SYM(serialize_size);
|
||||
|
||||
#undef LOAD_SYM
|
||||
|
||||
return (core->init && core->run && core->load_game);
|
||||
}
|
||||
|
||||
bool EmulatorCore::load_game(const godot::String& game_path) {
|
||||
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(game_path, godot::FileAccess::READ);
|
||||
if (f.is_null()) return false;
|
||||
|
||||
save_path = game_path.get_basename() + ".srm";
|
||||
game_data.resize(f->get_length());
|
||||
f->get_buffer(game_data.data(), game_data.size());
|
||||
|
||||
if (!core->load_game(game_data.data(), game_data.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inizializza il buffer per gli states
|
||||
if (core->serialize_size) {
|
||||
state_buffer.resize(core->serialize_size());
|
||||
}
|
||||
|
||||
// Carica SRAM se esiste
|
||||
load_sram();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmulatorCore::process_frame() {
|
||||
if (core->run) {
|
||||
core->run();
|
||||
}
|
||||
}
|
||||
|
||||
bool EmulatorCore::save_state(const godot::String& path) {
|
||||
if (!core->save_game_state || state_buffer.empty()) return false;
|
||||
|
||||
if (!core->save_game_state(state_buffer.data(), state_buffer.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(path, godot::FileAccess::WRITE);
|
||||
if (f.is_null()) return false;
|
||||
|
||||
f->store_buffer(state_buffer.data(), state_buffer.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmulatorCore::load_state(const godot::String& path) {
|
||||
if (!core->load_game_state || state_buffer.empty()) return false;
|
||||
|
||||
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(path, godot::FileAccess::READ);
|
||||
if (f.is_null()) return false;
|
||||
|
||||
f->get_buffer(state_buffer.data(), state_buffer.size());
|
||||
return core->load_game_state(state_buffer.data(), state_buffer.size());
|
||||
}
|
||||
|
||||
bool EmulatorCore::load_sram() {
|
||||
if (!core->get_memory_data) return false;
|
||||
|
||||
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(save_path, godot::FileAccess::READ);
|
||||
if (f.is_null()) return false;
|
||||
|
||||
save_data.resize(f->get_length());
|
||||
f->get_buffer(save_data.data(), save_data.size());
|
||||
|
||||
return core->set_memory_data(0, save_data.data());
|
||||
}
|
||||
|
||||
bool EmulatorCore::save_sram() {
|
||||
if (!core->get_memory_data) return false;
|
||||
|
||||
if (!core->get_memory_data(0, save_data.data())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
godot::Ref<godot::FileAccess> f = godot::FileAccess::open(save_path, godot::FileAccess::WRITE);
|
||||
if (f.is_null()) return false;
|
||||
|
||||
f->store_buffer(save_data.data(), save_data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmulatorCore::set_button_pressed(int port, int button, bool pressed) {
|
||||
if (port >= 0 && port < system_info.input_ports && button < 16) {
|
||||
if (pressed) {
|
||||
input_state[port] |= (1 << button);
|
||||
} else {
|
||||
input_state[port] &= ~(1 << button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorCore::set_axis_value(int port, int axis, float value) {
|
||||
// Implementa la gestione degli assi analogici se necessario
|
||||
}
|
||||
|
||||
void EmulatorCore::video_callback(const void* data, unsigned width,
|
||||
unsigned height, size_t pitch) {
|
||||
// Implementa il rendering del frame
|
||||
// ...
|
||||
}
|
||||
|
||||
void EmulatorCore::audio_callback(int16_t left, int16_t right) {
|
||||
// Implementa la gestione dell'audio
|
||||
// ...
|
||||
}
|
||||
|
||||
void EmulatorCore::_bind_methods() {
|
||||
using namespace godot;
|
||||
|
||||
ClassDB::bind_method(D_METHOD("initialize_core", "core_path"),
|
||||
&EmulatorCore::initialize_core);
|
||||
ClassDB::bind_method(D_METHOD("load_game", "game_path"),
|
||||
&EmulatorCore::load_game);
|
||||
ClassDB::bind_method(D_METHOD("process_frame"),
|
||||
&EmulatorCore::process_frame);
|
||||
ClassDB::bind_method(D_METHOD("save_state", "path"),
|
||||
&EmulatorCore::save_state);
|
||||
ClassDB::bind_method(D_METHOD("load_state", "path"),
|
||||
&EmulatorCore::load_state);
|
||||
ClassDB::bind_method(D_METHOD("set_button_pressed", "port", "button", "pressed"),
|
||||
&EmulatorCore::set_button_pressed);
|
||||
ClassDB::bind_method(D_METHOD("set_axis_value", "port", "axis", "value"),
|
||||
&EmulatorCore::set_axis_value);
|
||||
ClassDB::bind_method(D_METHOD("get_texture"),
|
||||
&EmulatorCore::get_texture);
|
||||
ClassDB::bind_method(D_METHOD("get_audio_stream"),
|
||||
&EmulatorCore::get_audio_stream);
|
||||
}
|
||||
|
||||
} // namespace EmulatorSystem
|
|
@ -1,108 +0,0 @@
|
|||
#ifndef EMULATOR_CORE_H
|
||||
#define EMULATOR_CORE_H
|
||||
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/image_texture.hpp>
|
||||
#include <godot_cpp/classes/audio_stream_generator.hpp>
|
||||
#include <godot_cpp/classes/audio_stream_generator_playback.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
namespace EmulatorSystem {
|
||||
|
||||
class EmulatorCore : public godot::Node {
|
||||
GDCLASS(EmulatorCore, godot::Node)
|
||||
|
||||
private:
|
||||
// Strutture per la gestione del core
|
||||
struct CoreFunctions {
|
||||
void* handle{nullptr};
|
||||
void (*init)();
|
||||
void (*deinit)();
|
||||
void (*run)();
|
||||
bool (*load_game)(const void* data, size_t size);
|
||||
bool (*save_game_state)(void* data, size_t size);
|
||||
bool (*load_game_state)(const void* data, size_t size);
|
||||
bool (*get_memory_data)(unsigned id, void* data);
|
||||
bool (*set_memory_data)(unsigned id, const void* data);
|
||||
void (*set_controller_port_device)(unsigned port, unsigned device);
|
||||
size_t (*serialize_size)();
|
||||
};
|
||||
|
||||
// Strutture per la gestione dell'audio/video
|
||||
struct SystemInfo {
|
||||
unsigned width{0};
|
||||
unsigned height{0};
|
||||
double fps{60.0};
|
||||
double sample_rate{44100.0};
|
||||
unsigned input_ports{2};
|
||||
};
|
||||
|
||||
// Membri privati
|
||||
std::unique_ptr<CoreFunctions> core;
|
||||
SystemInfo system_info;
|
||||
std::vector<uint8_t> game_data;
|
||||
std::vector<uint8_t> save_data;
|
||||
std::string save_path;
|
||||
|
||||
godot::Ref<godot::ImageTexture> frame_texture;
|
||||
godot::Ref<godot::AudioStreamGenerator> audio_stream;
|
||||
godot::Ref<godot::AudioStreamGeneratorPlayback> audio_playback;
|
||||
|
||||
std::vector<uint8_t> frame_buffer;
|
||||
std::array<uint16_t, 16> input_state;
|
||||
|
||||
// Buffer per states
|
||||
std::vector<uint8_t> state_buffer;
|
||||
|
||||
// Funzioni private
|
||||
bool load_core_functions();
|
||||
void setup_av_system();
|
||||
void cleanup();
|
||||
bool load_sram();
|
||||
bool save_sram();
|
||||
|
||||
// Callback handlers
|
||||
static void video_callback(const void* data, unsigned width,
|
||||
unsigned height, size_t pitch);
|
||||
static void audio_callback(int16_t left, int16_t right);
|
||||
static void input_poll_callback();
|
||||
static int16_t input_state_callback(unsigned port, unsigned device,
|
||||
unsigned index, unsigned id);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
EmulatorCore();
|
||||
~EmulatorCore();
|
||||
|
||||
// Funzioni principali
|
||||
bool initialize_core(const godot::String& core_path);
|
||||
bool load_game(const godot::String& game_path);
|
||||
void process_frame();
|
||||
void reset();
|
||||
void close_game();
|
||||
|
||||
// Gestione stati
|
||||
bool save_state(const godot::String& path);
|
||||
bool load_state(const godot::String& path);
|
||||
|
||||
// Input
|
||||
void set_button_pressed(int port, int button, bool pressed);
|
||||
void set_axis_value(int port, int axis, float value);
|
||||
|
||||
// Getters
|
||||
godot::Ref<godot::ImageTexture> get_texture() const { return frame_texture; }
|
||||
godot::Ref<godot::AudioStreamGenerator> get_audio_stream() const { return audio_stream; }
|
||||
|
||||
// Configurazione
|
||||
void set_audio_buffer_size(float seconds);
|
||||
void set_controller_type(int port, int device_type);
|
||||
};
|
||||
|
||||
} // namespace EmulatorSystem
|
||||
|
||||
#endif // EMULATOR_CORE_H
|
1
external/gdlibretro
vendored
Submodule
1
external/gdlibretro
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 7ebbf840187f54466988c0b5b20524ad21ca1d58
|
6
libretro_binding/libretro_binding.gdextension
Normal file
6
libretro_binding/libretro_binding.gdextension
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"entry_class": "LibretroCoreBinding",
|
||||
"library_path": "res://libretro_binding/libretro_binding.so",
|
||||
"singleton": false,
|
||||
"autoload": false
|
||||
}
|
22
main.gd
22
main.gd
|
@ -1,20 +1,26 @@
|
|||
extends Node3D
|
||||
|
||||
var xr_interface: XRInterface
|
||||
var emulator_script: Object
|
||||
|
||||
func _ready():
|
||||
# Inizializzazione OpenXR
|
||||
xr_interface = XRServer.find_interface("OpenXR")
|
||||
|
||||
if xr_interface and xr_interface.is_initialized():
|
||||
print("OpenXR initialized succesfully")
|
||||
|
||||
print("OpenXR initialized successfully")
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
|
||||
get_viewport().use_xr = true
|
||||
else:
|
||||
print("OpenXR not initialized, please check if your headset is connected")
|
||||
|
||||
# Configurazione SubViewport
|
||||
$SubViewport.size = Vector2(320, 240) # Risoluzione tipica retro
|
||||
$SubViewport/TextureRect.expand = true
|
||||
$SubViewport/TextureRect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
# Carica lo script dell'emulatore
|
||||
emulator_script = preload("res://scripts/emulate.gd").new()
|
||||
|
||||
func launch_game():
|
||||
var core_path = "res://cores/genesis_plus_gx_libretro.so"
|
||||
var rom_path = "res://roms/megadrive/Sonic the Hedgehog.bin"
|
||||
|
||||
if emulator_script.start_game(core_path, rom_path):
|
||||
print("Il gioco è stato avviato con successo!")
|
||||
else:
|
||||
print("Errore durante l'avvio del gioco.")
|
||||
|
|
|
@ -25,6 +25,10 @@ XrSimulator="*res://addons/xr-simulator/XRSimulator.tscn"
|
|||
|
||||
enabled=PackedStringArray("res://addons/godot-xr-tools/plugin.cfg")
|
||||
|
||||
[gd_extension]
|
||||
|
||||
extensions=PackedStringArray("res://libretro_binding/libretro_binding.gdextension")
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
|
|
46
scripts/emulate.gd
Normal file
46
scripts/emulate.gd
Normal file
|
@ -0,0 +1,46 @@
|
|||
extends Node
|
||||
|
||||
var libretro_core = LibretroCoreBinding.new() # Use the class directly
|
||||
|
||||
func start_game(core_path: String, rom_path: String) -> bool:
|
||||
"""
|
||||
Inizializza il core libretro e carica la ROM specificata.
|
||||
Restituisce true se il gioco è stato caricato correttamente, altrimenti false.
|
||||
"""
|
||||
# Carica il gestore libretro
|
||||
var libretro_handler = preload("res://scripts/libretro_loader.gd").new()
|
||||
|
||||
# Inizializzazione del core
|
||||
if not libretro_handler.initialize(core_path):
|
||||
print("Errore: impossibile caricare il core libretro:", core_path)
|
||||
return false
|
||||
|
||||
print("Core caricato con successo:", core_path)
|
||||
|
||||
# Caricamento della ROM
|
||||
var rom_data: PackedByteArray = load_rom_data(rom_path)
|
||||
if not libretro_handler.load_game(rom_data):
|
||||
print("Errore: impossibile caricare la ROM:", rom_path)
|
||||
return false
|
||||
|
||||
print("Gioco caricato con successo:", rom_path)
|
||||
return true
|
||||
|
||||
func load_rom_data(rom_path: String) -> PackedByteArray:
|
||||
"""
|
||||
Carica i dati di una ROM da file.
|
||||
"""
|
||||
var file = FileAccess.open(rom_path, FileAccess.READ)
|
||||
if not file:
|
||||
push_error("Error opening file: " + rom_path)
|
||||
return PackedByteArray()
|
||||
|
||||
var data = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
return data
|
||||
|
||||
func initialize_core(core_path: String) -> bool:
|
||||
if not libretro_core.initialize(core_path):
|
||||
print("Errore: impossibile caricare il core libretro")
|
||||
return false
|
||||
return true
|
|
@ -1,67 +1,51 @@
|
|||
# libretro_loader.gd
|
||||
extends Node
|
||||
|
||||
# Configurazione SubViewport TV
|
||||
@onready var tv_viewport: SubViewport = $CRTTV/SubViewport
|
||||
@onready var tv_texture_rect: TextureRect = $CRTTV/SubViewport/TextureRect
|
||||
|
||||
# Stato dell'emulazione
|
||||
var current_core = null
|
||||
var current_rom = null
|
||||
|
||||
# Funzione per caricare un core
|
||||
func load_libretro_core(core_path: String):
|
||||
# Caricamento dinamico del core
|
||||
func load_libretro_core(core_path: String) -> bool:
|
||||
current_core = load(core_path)
|
||||
|
||||
if not current_core:
|
||||
push_error("Impossibile caricare il core: " + core_path)
|
||||
push_error("Failed to load core: " + core_path)
|
||||
return false
|
||||
|
||||
# Inizializzazione base del core
|
||||
current_core.initialize()
|
||||
return true
|
||||
|
||||
# Funzione per caricare una ROM
|
||||
func load_rom(rom_path: String):
|
||||
# Verifica esistenza ROM
|
||||
func load_rom(rom_path: String) -> bool:
|
||||
if not FileAccess.file_exists(rom_path):
|
||||
push_error("ROM non trovata: " + rom_path)
|
||||
push_error("ROM not found: " + rom_path)
|
||||
return false
|
||||
|
||||
# Caricamento dati ROM
|
||||
var rom_data = FileAccess.get_file_as_bytes(rom_path)
|
||||
var file = FileAccess.open(rom_path, FileAccess.READ)
|
||||
if not file:
|
||||
push_error("Error opening ROM: " + rom_path)
|
||||
return false
|
||||
|
||||
# Caricamento ROM nel core
|
||||
var load_result = current_core.load_game(rom_data)
|
||||
var rom_data = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
|
||||
if not current_core.load_game(rom_data):
|
||||
push_error("Failed to load ROM: " + rom_path)
|
||||
return false
|
||||
|
||||
if load_result:
|
||||
current_rom = rom_path
|
||||
return true
|
||||
|
||||
return load_result
|
||||
|
||||
# Funzione per avviare l'emulazione
|
||||
func start_emulation(core_path: String, rom_path: String):
|
||||
# Caricamento core
|
||||
func start_emulation(core_path: String, rom_path: String) -> bool:
|
||||
if not load_libretro_core(core_path):
|
||||
return false
|
||||
|
||||
# Caricamento ROM
|
||||
if not load_rom(rom_path):
|
||||
return false
|
||||
|
||||
# Avvio emulazione
|
||||
current_core.run()
|
||||
return true
|
||||
|
||||
# Processo di rendering
|
||||
func _process(delta):
|
||||
if current_core:
|
||||
# Recupero frame corrente
|
||||
var frame_data = current_core.get_frame_buffer()
|
||||
|
||||
if frame_data:
|
||||
# Conversione in texture
|
||||
var image = Image.create_from_data(
|
||||
current_core.get_frame_width(),
|
||||
current_core.get_frame_height(),
|
||||
|
@ -69,23 +53,5 @@ func load_rom(rom_path: String):
|
|||
Image.FORMAT_RGB8,
|
||||
frame_data
|
||||
)
|
||||
|
||||
var texture = ImageTexture.create_from_image(image)
|
||||
|
||||
# Applicazione texture alla TV
|
||||
tv_texture_rect.texture = texture
|
||||
|
||||
# Esempio di utilizzo
|
||||
func _ready():
|
||||
# Percorsi di esempio
|
||||
var CORE_PATH = "res://cores/genesis_plus_gx_libretro.so"
|
||||
var ROM_PATH = "res://roms/megadrive/Sonic.bin"
|
||||
|
||||
# Avvio emulazione
|
||||
start_emulation(CORE_PATH, ROM_PATH)
|
||||
|
||||
# Gestione input
|
||||
func _input(event):
|
||||
if current_core:
|
||||
# Conversione input Godot in input Libretro
|
||||
current_core.handle_input(event)
|
||||
|
|
Loading…
Reference in a new issue