1 /* Copyright (c) 2013-2017 Jeffrey Pfau
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "MapView.h"
7 
8 #include "CoreController.h"
9 #include "GBAApp.h"
10 #include "LogController.h"
11 
12 #include <mgba-util/png-io.h>
13 #include <mgba-util/vfs.h>
14 #ifdef M_CORE_GBA
15 #include <mgba/internal/gba/gba.h>
16 #include <mgba/internal/gba/io.h>
17 #include <mgba/internal/gba/memory.h>
18 #include <mgba/internal/gba/video.h>
19 #endif
20 #ifdef M_CORE_GB
21 #include <mgba/internal/gb/gb.h>
22 #include <mgba/internal/gb/memory.h>
23 #endif
24 
25 #include <QAction>
26 #include <QButtonGroup>
27 #include <QClipboard>
28 #include <QMouseEvent>
29 #include <QRadioButton>
30 #include <QTimer>
31 
32 using namespace QGBA;
33 
MapView(std::shared_ptr<CoreController> controller,QWidget * parent)34 MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
35 	: AssetView(controller, parent)
36 	, m_controller(controller)
37 {
38 	m_ui.setupUi(this);
39 	m_ui.tile->setController(controller);
40 
41 	switch (m_controller->platform()) {
42 #ifdef M_CORE_GBA
43 	case mPLATFORM_GBA:
44 		m_boundary = 2048;
45 		m_addressBase = BASE_VRAM;
46 		m_addressWidth = 8;
47 		m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
48 		m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
49 		m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
50 		m_ui.bgInfo->addCustomProperty("size", tr("Size"));
51 		m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
52 		m_ui.bgInfo->addCustomProperty("transform", tr("Xform"));
53 		break;
54 #endif
55 #ifdef M_CORE_GB
56 	case mPLATFORM_GB:
57 		m_boundary = 1024;
58 		m_addressBase = GB_BASE_VRAM;
59 		m_addressWidth = 4;
60 		m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
61 		m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
62 		m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
63 		break;
64 #endif
65 	default:
66 		return;
67 	}
68 	m_ui.tile->setBoundary(m_boundary, 0, 0);
69 
70 	connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
71 		updateTiles(true);
72 	});
73 
74 	CoreController::Interrupter interrupter(m_controller);
75 	const mCoreChannelInfo* videoLayers;
76 	size_t nVideo = m_controller->thread()->core->listVideoLayers(m_controller->thread()->core, &videoLayers);
77 	QButtonGroup* group = new QButtonGroup(this);
78 	for (size_t i = 0; i < nVideo; ++i) {
79 		if (strncmp(videoLayers[i].internalName, "bg", 2) != 0) {
80 			continue;
81 		}
82 		QRadioButton* button = new QRadioButton(tr(videoLayers[i].visibleName));
83 		if (!i) {
84 			button->setChecked(true);
85 		}
86 		m_ui.bgLayout->addWidget(button);
87 		connect(button, &QAbstractButton::pressed, button, [this, i]() {
88 			selectMap(i);
89 		});
90 		group->addButton(button);
91 	}
92 	connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MapView::exportMap);
93 	connect(m_ui.copyButton, &QAbstractButton::clicked, this, &MapView::copyMap);
94 
95 	QAction* exportAction = new QAction(this);
96 	exportAction->setShortcut(QKeySequence::Save);
97 	connect(exportAction, &QAction::triggered, this, &MapView::exportMap);
98 	addAction(exportAction);
99 
100 	QAction* copyAction = new QAction(this);
101 	copyAction->setShortcut(QKeySequence::Copy);
102 	connect(copyAction, &QAction::triggered, this, &MapView::copyMap);
103 	addAction(copyAction);
104 
105 	m_ui.map->installEventFilter(this);
106 	m_ui.tile->addCustomProperty("mapAddr", tr("Map Addr."));
107 	m_ui.tile->addCustomProperty("flip", tr("Mirror"));
108 	selectTile(0, 0);
109 }
110 
selectMap(int map)111 void MapView::selectMap(int map) {
112 	if (map == m_map || map < 0) {
113 		return;
114 	}
115 	if (static_cast<unsigned>(map) >= mMapCacheSetSize(&m_cacheSet->maps)) {
116 		return;
117 	}
118 	m_map = map;
119 	updateTiles(true);
120 }
121 
selectTile(int x,int y)122 void MapView::selectTile(int x, int y) {
123 	CoreController::Interrupter interrupter(m_controller);
124 	mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
125 	size_t tileCache = mTileCacheSetIndex(&m_cacheSet->tiles, mapCache->tileCache);
126 	m_ui.tile->setBoundary(m_boundary, tileCache, tileCache);
127 	uint32_t location = mMapCacheTileId(mapCache, x, y);
128 	mMapCacheEntry* entry = &m_mapStatus[location];
129 	m_ui.tile->selectIndex(entry->tileId + mapCache->tileStart);
130 	m_ui.tile->setPalette(mMapCacheEntryFlagsGetPaletteId(entry->flags));
131 	m_ui.tile->setFlip(mMapCacheEntryFlagsGetHMirror(entry->flags), mMapCacheEntryFlagsGetVMirror(entry->flags));
132 	location <<= (mMapCacheSystemInfoGetMapAlign(mapCache->sysConfig));
133 	location += m_addressBase + mapCache->mapStart;
134 
135 	QString flip(tr("None"));
136 	if (mMapCacheEntryFlagsGetHMirror(entry->flags) && mMapCacheEntryFlagsGetVMirror(entry->flags)) {
137 		flip = tr("Both");
138 	} else if (mMapCacheEntryFlagsGetHMirror(entry->flags)) {
139 		flip = tr("Horizontal");
140 	} else if (mMapCacheEntryFlagsGetVMirror(entry->flags)) {
141 		flip = tr("Vertical");
142 	}
143 	m_ui.tile->setCustomProperty("flip", flip);
144 	m_ui.tile->setCustomProperty("mapAddr", QString("%0%1")
145 		.arg(m_addressWidth == 8 ? "0x" : "")
146 		.arg(location, m_addressWidth, 16, QChar('0')));
147 }
148 
eventFilter(QObject *,QEvent * event)149 bool MapView::eventFilter(QObject*, QEvent* event) {
150 	if (event->type() != QEvent::MouseButtonPress) {
151 		return false;
152 	}
153 	int x = static_cast<QMouseEvent*>(event)->x();
154 	int y = static_cast<QMouseEvent*>(event)->y();
155 	x /= 8 * m_ui.magnification->value();
156 	y /= 8 * m_ui.magnification->value();
157 	selectTile(x, y);
158 	return true;
159 }
160 
updateTilesGBA(bool)161 void MapView::updateTilesGBA(bool) {
162 	{
163 		CoreController::Interrupter interrupter(m_controller);
164 		int bitmap = -1;
165 		int priority = -1;
166 		int frame = 0;
167 		QString offset(tr("N/A"));
168 		QString transform(tr("N/A"));
169 #ifdef M_CORE_GBA
170 		if (m_controller->platform() == mPLATFORM_GBA) {
171 			uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
172 			int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]);
173 			if (m_map == 2 && mode > 2) {
174 				bitmap = mode == 4 ? 1 : 0;
175 				if (mode != 3) {
176 					frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
177 				}
178 			}
179 			priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
180 			if (mode == 0 || (mode == 1 && m_map != 2)) {
181 				offset = QString("%1, %2")
182 					.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
183 					.arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]);
184 			} else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
185 				int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
186 				refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
187 				int32_t refY = io[(REG_BG2Y_LO >> 1) + ((m_map - 2) << 2)];
188 				refY |= io[(REG_BG2Y_HI >> 1) + ((m_map - 2) << 2)] << 16;
189 				refX <<= 4;
190 				refY <<= 4;
191 				refX >>= 4;
192 				refY >>= 4;
193 				offset = QString("%1\n%2").arg(refX / 65536., 0, 'f', 3).arg(refY / 65536., 0, 'f', 3);
194 				transform = QString("%1 %2\n%3 %4")
195 					.arg(io[(REG_BG2PA >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
196 					.arg(io[(REG_BG2PB >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
197 					.arg(io[(REG_BG2PC >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
198 					.arg(io[(REG_BG2PD >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2);
199 
200 			}
201 		}
202 #endif
203 #ifdef M_CORE_GB
204 		if (m_controller->platform() == mPLATFORM_GB) {
205 			uint8_t* io = static_cast<GB*>(m_controller->thread()->core->board)->memory.io;
206 			int x = io[m_map == 0 ? 0x42 : 0x4A];
207 			int y = io[m_map == 0 ? 0x43 : 0x4B];
208 			offset = QString("%1, %2").arg(x).arg(y);
209 		}
210 #endif
211 		if (bitmap >= 0) {
212 			mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
213 			int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig);
214 			int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig);
215 			m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0')));
216 			m_ui.bgInfo->setCustomProperty("charBase", tr("N/A"));
217 			m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height));
218 			m_ui.bgInfo->setCustomProperty("priority", priority);
219 			m_ui.bgInfo->setCustomProperty("offset", offset);
220 			m_ui.bgInfo->setCustomProperty("transform", transform);
221 			m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32);
222 			uchar* bgBits = m_rawMap.bits();
223 			for (int j = 0; j < height; ++j) {
224 				mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
225 				memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
226 			}
227 			m_rawMap = m_rawMap.convertToFormat(QImage::Format_RGB32).rgbSwapped();
228 		} else {
229 			mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
230 			int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
231 			int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
232 			m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1")
233 					.arg(m_addressWidth == 8 ? "0x" : "")
234 					.arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0')));
235 			m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1")
236 					.arg(m_addressWidth == 8 ? "0x" : "")
237 					.arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0')));
238 			m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8));
239 			m_ui.bgInfo->setCustomProperty("priority", priority);
240 			m_ui.bgInfo->setCustomProperty("offset", offset);
241 			m_ui.bgInfo->setCustomProperty("transform", transform);
242 			m_rawMap = compositeMap(m_map, m_mapStatus);
243 		}
244 	}
245 	QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
246 	if (m_ui.magnification->value() > 1) {
247 		map = map.scaled(map.size() * m_ui.magnification->value());
248 	}
249 	m_ui.map->setPixmap(map);
250 }
251 
252 #ifdef M_CORE_GB
updateTilesGB(bool force)253 void MapView::updateTilesGB(bool force) {
254 	updateTilesGBA(force);
255 }
256 #endif
257 
exportMap()258 void MapView::exportMap() {
259 	QString filename = GBAApp::app()->getSaveFileName(this, tr("Export map"),
260 	                                                  tr("Portable Network Graphics (*.png)"));
261 	if (filename.isNull()) {
262 		return;
263 	}
264 
265 	CoreController::Interrupter interrupter(m_controller);
266 	m_rawMap.save(filename, "PNG");
267 }
268 
copyMap()269 void MapView::copyMap() {
270 	CoreController::Interrupter interrupter(m_controller);
271 	GBAApp::app()->clipboard()->setImage(m_rawMap);
272 }
273