mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
a6ce525da1
32
README.md
32
README.md
|
@ -10,7 +10,7 @@ Up-to-date news and downloads can be found at [mgba.io](https://mgba.io/).
|
|||
Features
|
||||
--------
|
||||
|
||||
- Near full Game Boy Advance hardware support[<sup>[1]</sup>](#missing).
|
||||
- Highly accurate Game Boy Advance hardware support[<sup>[1]</sup>](#missing).
|
||||
- Partial DS hardware support[<sup>[1]</sup>](#missing).
|
||||
- Game Boy/Game Boy Color hardware support.
|
||||
- Fast emulation for Game Boy and Game Boy Advance. Known to run at full speed even on low end hardware, such as netbooks[<sup>[2]</sup>](#dscaveat).
|
||||
|
@ -19,6 +19,7 @@ Features
|
|||
- Save type detection, even for flash memory size[<sup>[3]</sup>](#flashdetect).
|
||||
- Support for cartridges with motion sensors and rumble (only usable with game controllers)[<sup>[2]</sup>](#dscaveat).
|
||||
- Real-time clock support, even without configuration.
|
||||
- Solar sensor support for Boktai games.
|
||||
- Game Boy Camera and Game Boy Printer support.
|
||||
- A built-in GBA BIOS implementation, and ability to load external BIOS files. DS currently requires BIOS and firmware dumps[<sup>[2]</sup>](#dscaveat).
|
||||
- Turbo/fast-forward support by holding Tab.
|
||||
|
@ -37,6 +38,31 @@ Features
|
|||
- Cores available for RetroArch/Libretro and OpenEmu.
|
||||
- Many, many smaller things.
|
||||
|
||||
#### Game Boy mappers
|
||||
|
||||
The following mappers are fully supported:
|
||||
|
||||
- MBC1
|
||||
- MBC1M
|
||||
- MBC2
|
||||
- MBC3
|
||||
- MBC3+RTC
|
||||
- MBC5
|
||||
- MBC5+Rumble
|
||||
- MBC7
|
||||
|
||||
The following mappers are partially supported:
|
||||
|
||||
- Pocket Cam
|
||||
- TAMA5
|
||||
- HuC-3
|
||||
|
||||
The following mappers are not currently supported:
|
||||
|
||||
- MBC6
|
||||
- HuC-1
|
||||
- MMM01
|
||||
|
||||
### Planned features
|
||||
|
||||
- Networked multiplayer link cable support.
|
||||
|
@ -128,11 +154,11 @@ To build on Windows for development, using MSYS2 is recommended. Follow the inst
|
|||
|
||||
For x86 (32 bit) builds:
|
||||
|
||||
pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
For x86_64 (64 bit) builds:
|
||||
|
||||
pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
Check out the source code by running this command:
|
||||
|
||||
|
|
32
README_DE.md
32
README_DE.md
|
@ -10,7 +10,7 @@ Aktuelle Neuigkeiten und Downloads findest Du auf [mgba.io](https://mgba.io).
|
|||
Features
|
||||
--------
|
||||
|
||||
- Nahzu vollständige Unterstützung der Game Boy Advance-Hardware[<sup>[1]</sup>](#missing).
|
||||
- Sehr genaue Unterstützung der Game Boy Advance-Hardware[<sup>[1]</sup>](#missing).
|
||||
- Unterstützung der Game Boy-/Game Boy Color-Hardware.
|
||||
- Schnelle Emulation. mGBA ist dafür bekannt, auch auf schwacher Hardware wie Netbooks mit voller Geschwindigkeit zu laufen.
|
||||
- Qt- und SDL-Portierungen für eine vollwertige und eine "leichtgewichtige" Benutzeroberfläche.
|
||||
|
@ -18,6 +18,7 @@ Features
|
|||
- Erkennung des Speichertypes, einschließlich der Größe des Flash-Speichers[<sup>[2]</sup>](#flashdetect).
|
||||
- Unterstützung für Spielmodule mit Bewegungssensoren und Rüttel-Effekten (nur verwendbar mit Spiele-Controllern).
|
||||
- Unterstützung für Echtzeituhren, selbst ohne Konfiguration.
|
||||
- Unterstützung für den Lichtsensor in Boktai-Spielen
|
||||
- Unterstützung für Game Boy Printer und Game Boy Camera.
|
||||
- Eingebaute BIOS-Implementierung mit der Möglichkeit, externe BIOS-Dateien zu laden.
|
||||
- Turbo/Vorlauf-Unterstützung durch drücken der Tab-Taste.
|
||||
|
@ -36,6 +37,31 @@ Features
|
|||
- Verfügbare Cores für RetroArch/Libretro und OpenEmu.
|
||||
- Viele, viele kleinere Dinge.
|
||||
|
||||
### Game Boy-Mapper
|
||||
|
||||
Die folgenden Mapper werden vollständig unterstützt:
|
||||
|
||||
- MBC1
|
||||
- MBC1M
|
||||
- MBC2
|
||||
- MBC3
|
||||
- MBC3+RTC (MBC3+Echtzeituhr)
|
||||
- MBC5
|
||||
- MBC5+Rumble (MBC5+Rüttel-Modul)
|
||||
- MBC7
|
||||
|
||||
Die folgenden Mapper werden teilweise unterstützt:
|
||||
|
||||
- Pocket Cam
|
||||
- TAMA5
|
||||
- HuC-3
|
||||
|
||||
Die folgenden Mapper werden derzeit nicht unterstützt:
|
||||
|
||||
- MBC6
|
||||
- HuC-1
|
||||
- MMM01
|
||||
|
||||
### Geplante Features
|
||||
|
||||
- Unterstützung für Link-Kabel-Multiplayer über ein Netzwerk.
|
||||
|
@ -110,11 +136,11 @@ Um mGBA auf Windows zu kompilieren, wird MSYS2 empfohlen. Befolge die Installati
|
|||
|
||||
Für x86 (32 Bit):
|
||||
|
||||
pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
Für x86_64 (64 Bit):
|
||||
|
||||
pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
Lade den aktuellen mGBA-Quellcode mithilfe des folgenden Kommandos herunter:
|
||||
|
||||
|
|
|
@ -203,8 +203,10 @@ void* mCoreGetMemoryBlock(struct mCore* core, uint32_t start, size_t* size);
|
|||
#ifdef USE_ELF
|
||||
struct ELF;
|
||||
bool mCoreLoadELF(struct mCore* core, struct ELF* elf);
|
||||
#ifdef USE_DEBUGGERS
|
||||
void mCoreLoadELFSymbols(struct mDebuggerSymbols* symbols, struct ELF*);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -357,6 +357,7 @@ bool mCoreLoadELF(struct mCore* core, struct ELF* elf) {
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
void mCoreLoadELFSymbols(struct mDebuggerSymbols* symbols, struct ELF* elf) {
|
||||
size_t symIndex = ELFFindSection(elf, ".symtab");
|
||||
size_t names = ELFFindSection(elf, ".strtab");
|
||||
|
@ -376,5 +377,5 @@ void mCoreLoadELFSymbols(struct mDebuggerSymbols* symbols, struct ELF* elf) {
|
|||
mDebuggerSymbolAdd(symbols, name, syms[i].st_value, -1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -612,6 +612,7 @@ void GBDetectModel(struct GB* gb) {
|
|||
void GBUpdateIRQs(struct GB* gb) {
|
||||
int irqs = gb->memory.ie & gb->memory.io[REG_IF];
|
||||
if (!irqs) {
|
||||
gb->cpu->irqPending = false;
|
||||
return;
|
||||
}
|
||||
gb->cpu->halted = false;
|
||||
|
|
|
@ -738,7 +738,9 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
|||
}
|
||||
struct ELF* elf = ELFOpen(vf);
|
||||
if (elf) {
|
||||
#ifdef USE_DEBUGGERS
|
||||
mCoreLoadELFSymbols(core->symbolTable, elf);
|
||||
#endif
|
||||
ELFClose(elf);
|
||||
}
|
||||
if (closeAfter) {
|
||||
|
|
|
@ -1629,7 +1629,7 @@ void _pristineCow(struct GBA* gba) {
|
|||
if (!gba->isPristine) {
|
||||
return;
|
||||
}
|
||||
#ifndef FIXED_ROM_BUFFER
|
||||
#if !defined(FIXED_ROM_BUFFER) && !defined(__wii__)
|
||||
void* newRom = anonymousMemoryMap(SIZE_CART0);
|
||||
memcpy(newRom, gba->memory.rom, gba->memory.romSize);
|
||||
memset(((uint8_t*) newRom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize);
|
||||
|
|
|
@ -76,6 +76,16 @@ static void _reloadSettings(void) {
|
|||
opts.skipBios = strcmp(var.value, "ON") == 0;
|
||||
}
|
||||
|
||||
var.key = "mgba_sgb_borders";
|
||||
var.value = 0;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
if (strcmp(var.value, "ON") == 0) {
|
||||
mCoreConfigSetDefaultIntValue(&core->config, "sgb.borders", true);
|
||||
} else {
|
||||
mCoreConfigSetDefaultIntValue(&core->config, "sgb.borders", false);
|
||||
}
|
||||
}
|
||||
|
||||
var.key = "mgba_idle_optimization";
|
||||
var.value = 0;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
|
||||
|
@ -111,6 +121,7 @@ void retro_set_environment(retro_environment_t env) {
|
|||
{ "mgba_allow_opposing_directions", "Allow opposing directional input; OFF|ON" },
|
||||
{ "mgba_use_bios", "Use BIOS file if found (requires restart); ON|OFF" },
|
||||
{ "mgba_skip_bios", "Skip BIOS intro (requires restart); OFF|ON" },
|
||||
{ "mgba_sgb_borders", "Use Super Game Boy borders (requires restart); ON|OFF" },
|
||||
{ "mgba_idle_optimization", "Idle loop removal; Remove Known|Detect and Remove|Don't Remove" },
|
||||
{ "mgba_frameskip", "Frameskip; 0|1|2|3|4|5|6|7|8|9|10" },
|
||||
{ 0, 0 }
|
||||
|
|
|
@ -83,10 +83,12 @@ bool mPythonScriptEngineLoadScript(struct mScriptEngine* se, const char* name, s
|
|||
void mPythonScriptEngineRun(struct mScriptEngine* se) {
|
||||
struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se;
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
struct mDebugger* debugger = mScriptBridgeGetDebugger(engine->sb);
|
||||
if (debugger) {
|
||||
mPythonSetDebugger(debugger);
|
||||
}
|
||||
#endif
|
||||
|
||||
mPythonRunPending();
|
||||
}
|
||||
|
|
|
@ -8,12 +8,6 @@
|
|||
#include "DisplayGL.h"
|
||||
#include "DisplayQt.h"
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/video.h>
|
||||
#elif defined(M_CORE_GBA)
|
||||
#include <mgba/internal/gba/video.h>
|
||||
#endif
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY)
|
||||
|
@ -55,11 +49,6 @@ Display::Display(QWidget* parent)
|
|||
: QWidget(parent)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
#ifdef M_CORE_GB
|
||||
setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
|
||||
#elif defined(M_CORE_GBA)
|
||||
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
#endif
|
||||
connect(&m_mouseTimer, &QTimer::timeout, this, &Display::hideCursor);
|
||||
m_mouseTimer.setSingleShot(true);
|
||||
m_mouseTimer.setInterval(MOUSE_DISAPPEAR_TIMER);
|
||||
|
|
|
@ -458,7 +458,9 @@ void Window::openSettingsWindow() {
|
|||
connect(settingsWindow, &SettingsView::cameraDriverChanged, this, &Window::mustRestart);
|
||||
connect(settingsWindow, &SettingsView::languageChanged, this, &Window::mustRestart);
|
||||
connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig);
|
||||
#ifdef USE_SQLITE3
|
||||
connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear);
|
||||
#endif
|
||||
openView(settingsWindow);
|
||||
}
|
||||
|
||||
|
@ -723,8 +725,6 @@ void Window::gameStarted() {
|
|||
multiplayerChanged();
|
||||
updateTitle();
|
||||
QSize size = m_controller->screenDimensions();
|
||||
m_display->setMinimumSize(size);
|
||||
m_screenWidget->setMinimumSize(m_display->minimumSize());
|
||||
m_screenWidget->setDimensions(size.width(), size.height());
|
||||
m_config->updateOption("lockIntegerScaling");
|
||||
m_config->updateOption("lockAspectRatio");
|
||||
|
@ -733,6 +733,7 @@ void Window::gameStarted() {
|
|||
}
|
||||
attachWidget(m_display.get());
|
||||
setMouseTracking(true);
|
||||
m_display->setMinimumSize(size);
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
if (isFullScreen()) {
|
||||
|
@ -805,7 +806,6 @@ void Window::gameStopped() {
|
|||
#elif defined(M_CORE_GBA)
|
||||
m_display->setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
#endif
|
||||
m_screenWidget->setMinimumSize(m_display->minimumSize());
|
||||
|
||||
setMouseTracking(false);
|
||||
m_videoLayers->clear();
|
||||
|
@ -858,8 +858,7 @@ void Window::reloadDisplayDriver() {
|
|||
m_shaderView.reset();
|
||||
m_shaderView = std::make_unique<ShaderSelector>(m_display.get(), m_config);
|
||||
#endif
|
||||
m_screenWidget->setMinimumSize(m_display->minimumSize());
|
||||
m_screenWidget->setSizePolicy(m_display->sizePolicy());
|
||||
|
||||
connect(this, &Window::shutdown, m_display.get(), &Display::stopDrawing);
|
||||
connect(m_display.get(), &Display::hideCursor, [this]() {
|
||||
if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display.get()) {
|
||||
|
@ -885,6 +884,7 @@ void Window::reloadDisplayDriver() {
|
|||
#endif
|
||||
|
||||
if (m_controller) {
|
||||
m_display->setMinimumSize(m_controller->screenDimensions());
|
||||
connect(m_controller.get(), &CoreController::stopping, m_display.get(), &Display::stopDrawing);
|
||||
connect(m_controller.get(), &CoreController::stateLoaded, m_display.get(), &Display::forceDraw);
|
||||
connect(m_controller.get(), &CoreController::rewound, m_display.get(), &Display::forceDraw);
|
||||
|
@ -895,6 +895,12 @@ void Window::reloadDisplayDriver() {
|
|||
|
||||
attachWidget(m_display.get());
|
||||
m_display->startDrawing(m_controller);
|
||||
} else {
|
||||
#ifdef M_CORE_GB
|
||||
m_display->setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);
|
||||
#elif defined(M_CORE_GBA)
|
||||
m_display->setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1018,6 +1024,7 @@ void Window::openStateWindow(LoadSave ls) {
|
|||
}
|
||||
m_stateWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_stateWindow->setMode(ls);
|
||||
updateFrame();
|
||||
attachWidget(m_stateWindow);
|
||||
}
|
||||
|
||||
|
@ -1757,6 +1764,16 @@ void Window::focusCheck() {
|
|||
}
|
||||
}
|
||||
|
||||
void Window::updateFrame() {
|
||||
QSize size = m_controller->screenDimensions();
|
||||
QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(),
|
||||
size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888);
|
||||
QPixmap pixmap;
|
||||
pixmap.convertFromImage(currentImage);
|
||||
m_screenWidget->setPixmap(pixmap);
|
||||
emit paused(true);
|
||||
}
|
||||
|
||||
void Window::setController(CoreController* controller, const QString& fname) {
|
||||
if (!controller) {
|
||||
return;
|
||||
|
@ -1793,15 +1810,7 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
});
|
||||
}
|
||||
connect(m_controller.get(), &CoreController::stopping, &m_inputController, &InputController::resumeScreensaver);
|
||||
connect(m_controller.get(), &CoreController::paused, [this]() {
|
||||
QSize size = m_controller->screenDimensions();
|
||||
QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(),
|
||||
size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888);
|
||||
QPixmap pixmap;
|
||||
pixmap.convertFromImage(currentImage);
|
||||
m_screenWidget->setPixmap(pixmap);
|
||||
emit paused(true);
|
||||
});
|
||||
connect(m_controller.get(), &CoreController::paused, this, &Window::updateFrame);
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
connect(m_controller.get(), &CoreController::paused, menuBar(), &QWidget::show);
|
||||
|
@ -1831,13 +1840,17 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
connect(m_controller.get(), &CoreController::failed, this, &Window::gameFailed);
|
||||
connect(m_controller.get(), &CoreController::unimplementedBiosCall, this, &Window::unimplementedBiosCall);
|
||||
|
||||
#ifdef USE_GDB_STUB
|
||||
if (m_gdbController) {
|
||||
m_gdbController->setController(m_controller);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
if (m_console) {
|
||||
m_console->setController(m_controller);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
if (m_gifView) {
|
||||
|
@ -1869,11 +1882,15 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
}
|
||||
|
||||
WindowBackground::WindowBackground(QWidget* parent)
|
||||
: QLabel(parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setLayout(new QStackedLayout());
|
||||
layout()->setContentsMargins(0, 0, 0, 0);
|
||||
setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
|
||||
void WindowBackground::setPixmap(const QPixmap& pmap) {
|
||||
m_pixmap = pmap;
|
||||
update();
|
||||
}
|
||||
|
||||
void WindowBackground::setSizeHint(const QSize& hint) {
|
||||
|
@ -1904,11 +1921,9 @@ void WindowBackground::setLockAspectRatio(bool lock) {
|
|||
m_lockAspectRatio = lock;
|
||||
}
|
||||
|
||||
void WindowBackground::paintEvent(QPaintEvent*) {
|
||||
const QPixmap* logo = pixmap();
|
||||
if (!logo) {
|
||||
return;
|
||||
}
|
||||
void WindowBackground::paintEvent(QPaintEvent* event) {
|
||||
QWidget::paintEvent(event);
|
||||
const QPixmap& logo = pixmap();
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
painter.fillRect(QRect(QPoint(), size()), Qt::black);
|
||||
|
@ -1933,5 +1948,5 @@ void WindowBackground::paintEvent(QPaintEvent*) {
|
|||
}
|
||||
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
|
||||
QRect full(origin, ds);
|
||||
painter.drawPixmap(full, *logo);
|
||||
painter.drawPixmap(full, logo);
|
||||
}
|
||||
|
|
|
@ -133,6 +133,8 @@ private slots:
|
|||
void showFPS();
|
||||
void focusCheck();
|
||||
|
||||
void updateFrame();
|
||||
|
||||
private:
|
||||
static const int FPS_TIMER_INTERVAL = 2000;
|
||||
static const int FRAME_LIST_SIZE = 120;
|
||||
|
@ -218,12 +220,13 @@ private:
|
|||
#endif
|
||||
};
|
||||
|
||||
class WindowBackground : public QLabel {
|
||||
class WindowBackground : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WindowBackground(QWidget* parent = 0);
|
||||
|
||||
void setPixmap(const QPixmap& pixmap);
|
||||
void setSizeHint(const QSize& size);
|
||||
virtual QSize sizeHint() const override;
|
||||
void setDimensions(int width, int height);
|
||||
|
@ -231,10 +234,13 @@ public:
|
|||
void setLockIntegerScaling(bool lock);
|
||||
void setLockAspectRatio(bool lock);
|
||||
|
||||
const QPixmap& pixmap() const { return m_pixmap; }
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
QPixmap m_pixmap;
|
||||
QSize m_sizeHint;
|
||||
bool m_centered;
|
||||
int m_aspectWidth;
|
||||
|
|
Loading…
Reference in New Issue