Skip to content
Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
debugserver.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 Twilight
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15*/
16
41
42#include <QApplication>
43
44#ifdef QT_DEBUG
45
46#include <QTcpServer>
47#include <QTcpSocket>
48#include <QHostAddress>
49#include <QJsonDocument>
50#include <QJsonObject>
51#include <QJsonArray>
52#include <QJsonValue>
53#include <QMetaObject>
54#include <QByteArray>
55#include <QDebug>
56
58#include "../bridge/bridge.h"
59#include "../bridge/router.h"
61
62namespace {
63
64QObject* findItem(const QString& name)
65{
66 auto* mw = MainWindow::getInstance();
67 if(mw == nullptr) return nullptr;
68 QObject* root = mw->qmlRootObject();
69 if(root == nullptr) return nullptr;
70 if(root->objectName() == name) return root;
71 return root->findChild<QObject*>(name);
72}
73
74QJsonObject execute(const QJsonObject& c)
75{
76 const QString cmd = c.value(QStringLiteral("cmd")).toString();
77 auto ok = [](QJsonValue r){ QJsonObject o; o[QStringLiteral("ok")] = true; o[QStringLiteral("result")] = r; return o; };
78 auto err = [](const QString& m){ QJsonObject o; o[QStringLiteral("ok")] = false; o[QStringLiteral("error")] = m; return o; };
79
80 if(cmd == QStringLiteral("ping")) return ok(QStringLiteral("pong"));
81
83 if(brg == nullptr) return err(QStringLiteral("bridge not ready"));
84
85 if(cmd == QStringLiteral("title"))
86 return ok(brg->router->title);
87
88 if(cmd == QStringLiteral("screen")) {
89 const QString s = c.value(QStringLiteral("arg")).toString();
90 if(!Router::screens.contains(s)) return err(QStringLiteral("unknown screen: ") + s);
91 if(s == QStringLiteral("pokemonDetails")) {
92 if(auto* mw = MainWindow::getInstance())
93 mw->debugOpenPartyDetails(c.value(QStringLiteral("index")).toInt(0));
94 } else {
95 brg->router->changeScreen(s);
96 }
97 return ok(brg->router->title);
98 }
99
100 if(cmd == QStringLiteral("party")) {
101 if(auto* mw = MainWindow::getInstance())
102 return ok(mw->debugOpenPartyDetails(c.value(QStringLiteral("arg")).toInt(0)));
103 return err(QStringLiteral("no window"));
104 }
105
106 if(cmd == QStringLiteral("back")) {
107 brg->router->closeScreen();
108 return ok(brg->router->title);
109 }
110
111 if(cmd == QStringLiteral("home")) {
112 brg->router->changeScreen(QStringLiteral("home"));
113 return ok(QStringLiteral("home"));
114 }
115
116 if(cmd == QStringLiteral("sav")) {
117 brg->file->setPath(c.value(QStringLiteral("arg")).toString());
118 brg->file->reopenFile();
119 return ok(brg->file->getPath());
120 }
121
122 if(cmd == QStringLiteral("shot")) {
123 auto* mw = MainWindow::getInstance();
124 if(mw == nullptr) return err(QStringLiteral("no window"));
125 const QString p = c.value(QStringLiteral("arg")).toString();
126 if(p.isEmpty()) return err(QStringLiteral("shot needs 'arg' path"));
127 return mw->saveShot(p) ? ok(p) : err(QStringLiteral("grab failed"));
128 }
129
130 if(cmd == QStringLiteral("get")) {
131 QObject* it = findItem(c.value(QStringLiteral("obj")).toString());
132 if(it == nullptr) return err(QStringLiteral("no object: ") + c.value(QStringLiteral("obj")).toString());
133 const QByteArray prop = c.value(QStringLiteral("prop")).toString().toUtf8();
134 return ok(QJsonValue::fromVariant(it->property(prop.constData())));
135 }
136
137 if(cmd == QStringLiteral("set")) {
138 QObject* it = findItem(c.value(QStringLiteral("obj")).toString());
139 if(it == nullptr) return err(QStringLiteral("no object"));
140 const QByteArray prop = c.value(QStringLiteral("prop")).toString().toUtf8();
141 const bool okSet = it->setProperty(prop.constData(), c.value(QStringLiteral("val")).toVariant());
142 return okSet ? ok(true) : err(QStringLiteral("set failed (no such property?)"));
143 }
144
145 if(cmd == QStringLiteral("click")) {
146 QObject* it = findItem(c.value(QStringLiteral("obj")).toString());
147 if(it == nullptr) return err(QStringLiteral("no object"));
148 const bool okClick = QMetaObject::invokeMethod(it, "clicked");
149 return okClick ? ok(true) : err(QStringLiteral("no clicked() signal"));
150 }
151
152 if(cmd == QStringLiteral("reload")) {
153 if(auto* mw = MainWindow::getInstance()) { mw->reloadQml(); return ok(QStringLiteral("reloaded")); }
154 return err(QStringLiteral("no window"));
155 }
156
157 if(cmd == QStringLiteral("list")) {
158 const QString sub = c.value(QStringLiteral("arg")).toString();
159 auto* mw = MainWindow::getInstance();
160 QObject* root = mw ? mw->qmlRootObject() : nullptr;
161 if(root == nullptr) return err(QStringLiteral("no root"));
162 QJsonArray arr;
163 const auto all = root->findChildren<QObject*>();
164 for(QObject* o : all) {
165 const QString n = o->objectName();
166 if(!n.isEmpty() && (sub.isEmpty() || n.contains(sub, Qt::CaseInsensitive)))
167 arr.append(n);
168 }
169 return ok(arr);
170 }
171
172 return err(QStringLiteral("unknown cmd: ") + cmd);
173}
174
175} // namespace
176
177void startDebugServer()
178{
179 auto* server = new QTcpServer(qApp);
180 const quint16 port = 8766;
181 if(!server->listen(QHostAddress::LocalHost, port)) {
182 qWarning() << "[debug-server] could NOT listen on 127.0.0.1:" << port
183 << "--" << server->errorString();
184 return;
185 }
186 qInfo() << "[debug-server] listening on 127.0.0.1:" << port
187 << "(newline-delimited JSON)";
188
189 QObject::connect(server, &QTcpServer::newConnection, qApp, [server]() {
190 while(server->hasPendingConnections()) {
191 QTcpSocket* sock = server->nextPendingConnection();
192 QObject::connect(sock, &QTcpSocket::readyRead, sock, [sock]() {
193 while(sock->canReadLine()) {
194 const QByteArray line = sock->readLine().trimmed();
195 if(line.isEmpty()) continue;
196 QJsonParseError pe;
197 const QJsonDocument doc = QJsonDocument::fromJson(line, &pe);
198 QJsonObject resp;
199 if(pe.error != QJsonParseError::NoError || !doc.isObject()) {
200 resp[QStringLiteral("ok")] = false;
201 resp[QStringLiteral("error")] = QStringLiteral("bad json: ") + pe.errorString();
202 } else {
203 resp = execute(doc.object());
204 }
205 sock->write(QJsonDocument(resp).toJson(QJsonDocument::Compact));
206 sock->write("\n");
207 sock->flush();
208 }
209 });
210 QObject::connect(sock, &QTcpSocket::disconnected, sock, &QObject::deleteLater);
211 }
212 });
213}
214
215#else
216
218
219#endif
The single QML<->C++ doorway – everything the UI touches hangs off here.
Definition bridge.h:71
FileManagement * file
Definition bridge.h:149
Router * router
Definition bridge.h:153
void setPath(QString path)
Set the active path (backs the path property).
void reopenFile()
Reload the current path from disk, discarding edits.
QString getPath()
Current file path.
static MainWindow * getInstance()
The single MainWindow instance.
static Bridge * bridge
The brg aggregate (created here, injected into QML).
Definition mainwindow.h:62
void changeScreen(QString name)
Navigate to the registered screen name.
Definition router.cpp:35
void closeScreen()
Close the top screen.
Definition router.cpp:78
static QHash< QString, Screen * > screens
The registry of named screens.
Definition router.h:104
QString title
Definition router.h:98
void startDebugServer()