Add Gambatte GB/GBC emulator, Call/SMS anti-spam blocklist, and Lua/bc/dc/Yabasic
This commit is contained in:
+661
-160
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// CallLog DWM Plugin - Preloaded resident X11 call log viewer & dialer launcher (240x320)
|
||||
// CallLog DWM Plugin - Preloaded resident X11 call log viewer, anti-spam blocklist & dialer launcher (240x320)
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
@@ -37,6 +37,20 @@ void handleSignal(int) {
|
||||
gRunning = 0;
|
||||
}
|
||||
|
||||
enum class ViewMode {
|
||||
Log = 0,
|
||||
Options = 1,
|
||||
Blocklist = 2,
|
||||
AddBlock = 3,
|
||||
};
|
||||
|
||||
struct OptionEntry {
|
||||
int id;
|
||||
std::string icon;
|
||||
std::string label;
|
||||
bool danger = false;
|
||||
};
|
||||
|
||||
int calllog_run_main(int argc, char** argv) {
|
||||
gRunning = 1;
|
||||
bool isPreload = false;
|
||||
@@ -116,13 +130,29 @@ int calllog_run_main(int argc, char** argv) {
|
||||
XftColor& colRed = theme.colRed;
|
||||
|
||||
ContactBook book;
|
||||
BlockList blockList;
|
||||
std::vector<CallLogItem> allLogs;
|
||||
std::vector<CallLogItem> blockedLogs;
|
||||
std::vector<CallLogItem> filtered;
|
||||
FileStamp contactsStamp;
|
||||
FileStamp logStamp;
|
||||
FileStamp blockFileStamp;
|
||||
FileStamp blockLogStamp;
|
||||
|
||||
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out
|
||||
constexpr int kNumTabs = 5;
|
||||
const char* kTabLabels[kNumTabs] = {"All", "Miss", "In", "Out", "Spam"};
|
||||
|
||||
ViewMode mode = ViewMode::Log;
|
||||
int filterTab = 0; // 0 = All, 1 = Missed, 2 = In, 3 = Out, 4 = Spam/Blocked
|
||||
int selected = 0;
|
||||
int optionSelected = 0;
|
||||
int blockSelected = 0;
|
||||
std::vector<OptionEntry> options;
|
||||
std::string addBlockInput;
|
||||
|
||||
std::string toastMsg;
|
||||
int64_t toastUntilMs = 0;
|
||||
|
||||
bool visible = false;
|
||||
int64_t openStartMs = 0;
|
||||
int64_t startupIgnoreUntilMs = 0;
|
||||
@@ -130,13 +160,32 @@ int calllog_run_main(int argc, char** argv) {
|
||||
int softkeyPending = 0;
|
||||
int64_t softkeyDeadlineMs = 0;
|
||||
|
||||
auto showToast = [&](const std::string& msg) {
|
||||
toastMsg = msg;
|
||||
toastUntilMs = nowMs() + 1800;
|
||||
};
|
||||
|
||||
auto applyFilter = [&]() {
|
||||
filtered.clear();
|
||||
for (const auto& item : allLogs) {
|
||||
if (filterTab == 1 && item.direction != CallDirection::Missed) continue;
|
||||
if (filterTab == 2 && item.direction != CallDirection::Incoming) continue;
|
||||
if (filterTab == 3 && item.direction != CallDirection::Outgoing) continue;
|
||||
filtered.push_back(item);
|
||||
if (filterTab == 4) {
|
||||
for (auto item : blockedLogs) {
|
||||
item.isBlockedNumber = blockList.isBlocked(item.number);
|
||||
filtered.push_back(item);
|
||||
}
|
||||
for (auto item : allLogs) {
|
||||
if (blockList.isBlocked(item.number)) {
|
||||
item.isBlockedNumber = true;
|
||||
filtered.push_back(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto item : allLogs) {
|
||||
if (filterTab == 1 && item.direction != CallDirection::Missed) continue;
|
||||
if (filterTab == 2 && item.direction != CallDirection::Incoming) continue;
|
||||
if (filterTab == 3 && item.direction != CallDirection::Outgoing) continue;
|
||||
item.isBlockedNumber = blockList.isBlocked(item.number);
|
||||
filtered.push_back(item);
|
||||
}
|
||||
}
|
||||
if (selected >= static_cast<int>(filtered.size())) {
|
||||
selected = std::max(0, static_cast<int>(filtered.size()) - 1);
|
||||
@@ -144,21 +193,60 @@ int calllog_run_main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
bool logsLoadedOnce = false;
|
||||
auto reloadLogsIfNeeded = [&]() -> bool {
|
||||
auto reloadLogsIfNeeded = [&](bool force = false) -> bool {
|
||||
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
|
||||
bool lChanged = logStamp.checkAndUpdate(getModemLogFilePath());
|
||||
if (cChanged || lChanged || !logsLoadedOnce) {
|
||||
bool bChanged = blockFileStamp.checkAndUpdate(getBlockFilePath());
|
||||
bool blChanged = blockLogStamp.checkAndUpdate(getBlockedModemLogFilePath());
|
||||
if (force || cChanged || lChanged || bChanged || blChanged || !logsLoadedOnce) {
|
||||
logsLoadedOnce = true;
|
||||
book.load();
|
||||
blockList.load();
|
||||
allLogs = loadCallLog(book);
|
||||
blockedLogs = loadBlockedCallLog(book);
|
||||
applyFilter();
|
||||
if (blockSelected >= static_cast<int>(blockList.entries().size())) {
|
||||
blockSelected = std::max(0, static_cast<int>(blockList.entries().size()) - 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
reloadLogsIfNeeded();
|
||||
applyFilter();
|
||||
reloadLogsIfNeeded(true);
|
||||
|
||||
auto buildOptions = [&]() {
|
||||
options.clear();
|
||||
bool hasSel = (!filtered.empty() && selected >= 0 &&
|
||||
selected < static_cast<int>(filtered.size()));
|
||||
if (hasSel) {
|
||||
const CallLogItem& item = filtered[selected];
|
||||
bool isBlk = blockList.isBlocked(item.number);
|
||||
options.push_back({1, kIconPhone, "Call Number", false});
|
||||
options.push_back({2, kIconPhoneLog, "Open in Dialer", false});
|
||||
options.push_back({3, kIconSms, "Send SMS", false});
|
||||
if (isBlk) {
|
||||
options.push_back({4, kIconCheck, "Unblock Number", false});
|
||||
} else {
|
||||
options.push_back({4, kIconBan, "Block Number (Spam)", true});
|
||||
}
|
||||
options.push_back({5, kIconUser, "Contacts", false});
|
||||
} else {
|
||||
options.push_back({2, kIconPhoneLog, "Open Dialer", false});
|
||||
}
|
||||
std::string blkCount =
|
||||
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
|
||||
options.push_back({6, kIconShield, blkCount, false});
|
||||
options.push_back({7, kIconPlus, "Block a Number...", false});
|
||||
if (filterTab == 4) {
|
||||
if (!blockedLogs.empty()) {
|
||||
options.push_back({8, kIconTrash, "Clear Spam Log", true});
|
||||
}
|
||||
} else if (!allLogs.empty()) {
|
||||
options.push_back({9, kIconTrash, "Clear Call Log", true});
|
||||
}
|
||||
optionSelected = 0;
|
||||
};
|
||||
|
||||
auto drawPango = [&](PangoLayout* layout, XftColor* color, int x, int y, int h,
|
||||
const std::string& text, int maxW = -1) {
|
||||
@@ -191,8 +279,6 @@ int calllog_run_main(int argc, char** argv) {
|
||||
constexpr int kListH = kWinHeight - kHeaderH - kSoftbarH;
|
||||
constexpr int kVisibleRows = 5;
|
||||
|
||||
const char* kTabLabels[4] = {"All", "Missed", "In", "Out"};
|
||||
|
||||
auto render = [&]() {
|
||||
XSetForeground(dpy, gc, theme.spec.bgMain);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kWinHeight);
|
||||
@@ -200,108 +286,270 @@ int calllog_run_main(int argc, char** argv) {
|
||||
XSetForeground(dpy, gc, theme.spec.bgHeader);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, kHeaderH);
|
||||
|
||||
for (int t = 0; t < 4; ++t) {
|
||||
int tx = (t * kWinWidth) / 4;
|
||||
int tw = ((t + 1) * kWinWidth) / 4 - tx;
|
||||
bool active = (t == filterTab);
|
||||
if (active) {
|
||||
XSetForeground(dpy, gc, theme.spec.selBg);
|
||||
XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2);
|
||||
XSetForeground(dpy, gc, theme.spec.accent);
|
||||
XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3);
|
||||
}
|
||||
std::string lbl(kTabLabels[t]);
|
||||
int lw = pangoWidth(layoutSmall, lbl);
|
||||
drawPango(layoutSmall, active ? &colWhite : &colMuted,
|
||||
tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl);
|
||||
}
|
||||
std::string softLeft = "Options";
|
||||
std::string softCenter = "CALL";
|
||||
std::string softRight = "Close";
|
||||
|
||||
XSetForeground(dpy, gc, theme.spec.border);
|
||||
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
|
||||
|
||||
if (filtered.empty()) {
|
||||
drawPango(layoutIcon, &colMuted, 112, kListY + 60, 24, kIconPhoneLog);
|
||||
drawPango(layoutMain, &colMuted, 52, kListY + 90, 28, "No call history", 160);
|
||||
drawPango(layoutSmall, &colMuted, 24, kListY + 120, 20,
|
||||
"Press 0-9 or Left Softkey to dial", 200);
|
||||
} else {
|
||||
int total = static_cast<int>(filtered.size());
|
||||
int scrollOffset = 0;
|
||||
if (selected >= kVisibleRows) {
|
||||
scrollOffset = selected - kVisibleRows + 1;
|
||||
}
|
||||
if (scrollOffset > std::max(0, total - kVisibleRows)) {
|
||||
scrollOffset = std::max(0, total - kVisibleRows);
|
||||
}
|
||||
|
||||
for (int i = 0; i < kVisibleRows; ++i) {
|
||||
int idx = scrollOffset + i;
|
||||
if (idx >= total) {
|
||||
break;
|
||||
if (mode == ViewMode::Log) {
|
||||
for (int t = 0; t < kNumTabs; ++t) {
|
||||
int tx = (t * kWinWidth) / kNumTabs;
|
||||
int tw = ((t + 1) * kWinWidth) / kNumTabs - tx;
|
||||
bool active = (t == filterTab);
|
||||
if (active) {
|
||||
XSetForeground(dpy, gc, theme.spec.selBg);
|
||||
XFillRectangle(dpy, pix, gc, tx, 0, tw, kHeaderH - 2);
|
||||
XSetForeground(dpy, gc, (t == 4) ? theme.spec.danger : theme.spec.accent);
|
||||
XFillRectangle(dpy, pix, gc, tx, kHeaderH - 3, tw, 3);
|
||||
}
|
||||
int ry = kListY + (i * kListH) / kVisibleRows;
|
||||
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
|
||||
int rowH = nextRy - ry;
|
||||
bool isSel = (idx == selected);
|
||||
std::string lbl(kTabLabels[t]);
|
||||
int lw = pangoWidth(layoutSmall, lbl);
|
||||
XftColor* tabCol = active ? ((t == 4) ? &colRed : &colWhite) : &colMuted;
|
||||
drawPango(layoutSmall, tabCol, tx + (tw - lw) / 2, 0, kHeaderH - 2, lbl);
|
||||
}
|
||||
|
||||
XSetForeground(dpy, gc,
|
||||
isSel ? theme.spec.selBg : ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
|
||||
XSetForeground(dpy, gc, theme.spec.border);
|
||||
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
|
||||
|
||||
if (filtered.empty()) {
|
||||
drawPango(layoutIcon, &colMuted, 112, kListY + 54, 24,
|
||||
(filterTab == 4) ? kIconShield : kIconPhoneLog);
|
||||
std::string emptyTitle =
|
||||
(filterTab == 4) ? "No blocked calls" : "No call history";
|
||||
int etw = pangoWidth(layoutMain, emptyTitle);
|
||||
drawPango(layoutMain, &colMuted, std::max(8, (kWinWidth - etw) / 2),
|
||||
kListY + 84, 26, emptyTitle, 224);
|
||||
std::string emptySub =
|
||||
(filterTab == 4)
|
||||
? ("Blocklist: " + std::to_string(blockList.entries().size()) +
|
||||
" number(s)")
|
||||
: "Press 0-9 to dial or Options";
|
||||
int esw = pangoWidth(layoutSmall, emptySub);
|
||||
drawPango(layoutSmall, &colMuted, std::max(8, (kWinWidth - esw) / 2),
|
||||
kListY + 114, 20, emptySub, 224);
|
||||
} else {
|
||||
int total = static_cast<int>(filtered.size());
|
||||
int scrollOffset = 0;
|
||||
if (selected >= kVisibleRows) {
|
||||
scrollOffset = selected - kVisibleRows + 1;
|
||||
}
|
||||
if (scrollOffset > std::max(0, total - kVisibleRows)) {
|
||||
scrollOffset = std::max(0, total - kVisibleRows);
|
||||
}
|
||||
|
||||
for (int i = 0; i < kVisibleRows; ++i) {
|
||||
int idx = scrollOffset + i;
|
||||
if (idx >= total) {
|
||||
break;
|
||||
}
|
||||
int ry = kListY + (i * kListH) / kVisibleRows;
|
||||
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
|
||||
int rowH = nextRy - ry;
|
||||
bool isSel = (idx == selected);
|
||||
|
||||
XSetForeground(
|
||||
dpy, gc,
|
||||
isSel ? theme.spec.selBg
|
||||
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
|
||||
|
||||
if (isSel) {
|
||||
XSetForeground(dpy, gc, theme.spec.accent);
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
|
||||
}
|
||||
|
||||
const CallLogItem& item = filtered[idx];
|
||||
const char* dirIcon = kIconInCall;
|
||||
XftColor* dirCol = &colGreen;
|
||||
const char* dirName = "In";
|
||||
if (item.direction == CallDirection::Blocked) {
|
||||
dirIcon = kIconBan;
|
||||
dirCol = &colRed;
|
||||
dirName = "Blocked";
|
||||
} else if (item.direction == CallDirection::Outgoing) {
|
||||
dirIcon = kIconOutCall;
|
||||
dirCol = &colBlue;
|
||||
dirName = "Out";
|
||||
} else if (item.direction == CallDirection::Missed) {
|
||||
dirIcon = kIconMissedCall;
|
||||
dirCol = &colRed;
|
||||
dirName = "Missed";
|
||||
}
|
||||
|
||||
drawPango(layoutIcon, dirCol, 8, ry, rowH, dirIcon);
|
||||
|
||||
std::string rightBadge = formatDuration(item.durationSec);
|
||||
XftColor* badgeCol = isSel ? &colYellow : &colGreen;
|
||||
if (item.isBlockedNumber) {
|
||||
rightBadge = "BLK";
|
||||
badgeCol = &colRed;
|
||||
}
|
||||
int durW = 0;
|
||||
if (!rightBadge.empty()) {
|
||||
durW = pangoWidth(layoutSmall, rightBadge);
|
||||
drawPango(layoutSmall, badgeCol, kWinWidth - durW - 6, ry + 3, 22,
|
||||
rightBadge);
|
||||
}
|
||||
|
||||
std::string primary =
|
||||
item.contactName.empty() ? item.number : item.contactName;
|
||||
XftColor* priCol =
|
||||
((item.direction == CallDirection::Missed ||
|
||||
item.direction == CallDirection::Blocked ||
|
||||
item.isBlockedNumber) &&
|
||||
!isSel)
|
||||
? &colRed
|
||||
: (isSel ? &colWhite : &colLight);
|
||||
int priMaxW = (durW > 0) ? (kWinWidth - 36 - durW - 6) : 200;
|
||||
drawPango(layoutMain, priCol, 30, ry + 3, 22, primary, priMaxW);
|
||||
|
||||
int timeW = pangoWidth(layoutSmall, item.displayTime);
|
||||
drawPango(layoutSmall, isSel ? &colWhite : &colMuted,
|
||||
kWinWidth - timeW - 6, ry + 24, 19, item.displayTime);
|
||||
|
||||
std::string secondary =
|
||||
!item.contactName.empty() ? item.number : std::string(dirName);
|
||||
int secMaxW = std::max(60, kWinWidth - 36 - timeW - 8);
|
||||
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24,
|
||||
19, secondary, secMaxW);
|
||||
}
|
||||
|
||||
if (total > kVisibleRows) {
|
||||
int sbH = std::max(16, (kListH * kVisibleRows) / total);
|
||||
int sbY = kListY +
|
||||
((kListH - sbH) * scrollOffset) / (total - kVisibleRows);
|
||||
XSetForeground(dpy, gc, theme.spec.divider);
|
||||
XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH);
|
||||
}
|
||||
}
|
||||
} else if (mode == ViewMode::Options) {
|
||||
drawPango(layoutIcon, &colYellow, 8, 0, kHeaderH, kIconList);
|
||||
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, "Call Log Options", 200);
|
||||
XSetForeground(dpy, gc, theme.spec.border);
|
||||
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
|
||||
|
||||
constexpr int kOptVis = 6;
|
||||
int total = static_cast<int>(options.size());
|
||||
int scrollOffset = 0;
|
||||
if (optionSelected >= kOptVis) {
|
||||
scrollOffset = optionSelected - kOptVis + 1;
|
||||
}
|
||||
int rowH = kListH / kOptVis;
|
||||
for (int i = 0; i < kOptVis; ++i) {
|
||||
int idx = scrollOffset + i;
|
||||
if (idx >= total) break;
|
||||
int ry = kListY + i * rowH;
|
||||
bool isSel = (idx == optionSelected);
|
||||
XSetForeground(
|
||||
dpy, gc,
|
||||
isSel ? theme.spec.selBg
|
||||
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
|
||||
|
||||
if (isSel) {
|
||||
XSetForeground(dpy, gc, theme.spec.accent);
|
||||
XSetForeground(dpy, gc,
|
||||
options[idx].danger ? theme.spec.danger : theme.spec.accent);
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
|
||||
}
|
||||
|
||||
const CallLogItem& item = filtered[idx];
|
||||
const char* dirIcon = kIconInCall;
|
||||
XftColor* dirCol = &colGreen;
|
||||
const char* dirName = "In";
|
||||
if (item.direction == CallDirection::Outgoing) {
|
||||
dirIcon = kIconOutCall;
|
||||
dirCol = &colBlue;
|
||||
dirName = "Out";
|
||||
} else if (item.direction == CallDirection::Missed) {
|
||||
dirIcon = kIconMissedCall;
|
||||
dirCol = &colRed;
|
||||
dirName = "Missed";
|
||||
}
|
||||
|
||||
drawPango(layoutIcon, dirCol, 8, ry, rowH, dirIcon);
|
||||
|
||||
std::string dur = formatDuration(item.durationSec);
|
||||
int durW = 0;
|
||||
if (!dur.empty()) {
|
||||
durW = pangoWidth(layoutSmall, dur);
|
||||
drawPango(layoutSmall, isSel ? &colYellow : &colGreen,
|
||||
kWinWidth - durW - 6, ry + 3, 22, dur);
|
||||
}
|
||||
|
||||
std::string primary = item.contactName.empty() ? item.number : item.contactName;
|
||||
XftColor* priCol = (item.direction == CallDirection::Missed && !isSel)
|
||||
? &colRed
|
||||
: (isSel ? &colWhite : &colLight);
|
||||
int priMaxW = (durW > 0) ? (kWinWidth - 36 - durW - 6) : 200;
|
||||
drawPango(layoutMain, priCol, 30, ry + 3, 22, primary, priMaxW);
|
||||
|
||||
int timeW = pangoWidth(layoutSmall, item.displayTime);
|
||||
drawPango(layoutSmall, isSel ? &colWhite : &colMuted,
|
||||
kWinWidth - timeW - 6, ry + 24, 19, item.displayTime);
|
||||
|
||||
std::string secondary =
|
||||
!item.contactName.empty() ? item.number : std::string(dirName);
|
||||
int secMaxW = std::max(60, kWinWidth - 36 - timeW - 8);
|
||||
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24, 19,
|
||||
secondary, secMaxW);
|
||||
XftColor* icCol = options[idx].danger
|
||||
? &colRed
|
||||
: (isSel ? &colYellow : &colBlue);
|
||||
XftColor* txCol = options[idx].danger
|
||||
? &colRed
|
||||
: (isSel ? &colWhite : &colLight);
|
||||
drawPango(layoutIcon, icCol, 10, ry, rowH, options[idx].icon);
|
||||
drawPango(layoutMain, txCol, 34, ry, rowH, options[idx].label,
|
||||
kWinWidth - 42);
|
||||
}
|
||||
softLeft = "Select";
|
||||
softCenter = "OK";
|
||||
softRight = "Back";
|
||||
} else if (mode == ViewMode::Blocklist) {
|
||||
drawPango(layoutIcon, &colRed, 8, 0, kHeaderH, kIconBan);
|
||||
std::string hdr =
|
||||
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
|
||||
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, hdr, 204);
|
||||
XSetForeground(dpy, gc, theme.spec.border);
|
||||
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
|
||||
|
||||
if (total > kVisibleRows) {
|
||||
int sbH = std::max(16, (kListH * kVisibleRows) / total);
|
||||
int sbY = kListY +
|
||||
((kListH - sbH) * scrollOffset) / (total - kVisibleRows);
|
||||
XSetForeground(dpy, gc, theme.spec.divider);
|
||||
XFillRectangle(dpy, pix, gc, kWinWidth - 3, sbY, 3, sbH);
|
||||
const auto& entries = blockList.entries();
|
||||
if (entries.empty()) {
|
||||
drawPango(layoutIcon, &colMuted, 112, kListY + 54, 24, kIconShield);
|
||||
drawPango(layoutMain, &colMuted, 44, kListY + 84, 26,
|
||||
"Blocklist is empty", 180);
|
||||
drawPango(layoutSmall, &colMuted, 22, kListY + 114, 20,
|
||||
"Left Softkey: Block a Number", 204);
|
||||
} else {
|
||||
int total = static_cast<int>(entries.size());
|
||||
int scrollOffset = 0;
|
||||
if (blockSelected >= kVisibleRows) {
|
||||
scrollOffset = blockSelected - kVisibleRows + 1;
|
||||
}
|
||||
for (int i = 0; i < kVisibleRows; ++i) {
|
||||
int idx = scrollOffset + i;
|
||||
if (idx >= total) break;
|
||||
int ry = kListY + (i * kListH) / kVisibleRows;
|
||||
int nextRy = kListY + ((i + 1) * kListH) / kVisibleRows;
|
||||
int rowH = nextRy - ry;
|
||||
bool isSel = (idx == blockSelected);
|
||||
XSetForeground(
|
||||
dpy, gc,
|
||||
isSel ? theme.spec.selBg
|
||||
: ((i % 2 == 0) ? theme.spec.bgAltEven : theme.spec.bgAltOdd));
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH);
|
||||
if (isSel) {
|
||||
XSetForeground(dpy, gc, theme.spec.danger);
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, 4, rowH);
|
||||
}
|
||||
const auto& e = entries[idx];
|
||||
std::string cname = book.resolveName(e.number);
|
||||
std::string topLine = cname.empty() ? e.number : cname;
|
||||
std::string subLine =
|
||||
cname.empty() ? e.label : (e.number + " (" + e.label + ")");
|
||||
drawPango(layoutIcon, &colRed, 8, ry, rowH, kIconBan);
|
||||
drawPango(layoutMain, isSel ? &colWhite : &colLight, 30, ry + 3, 22,
|
||||
topLine, kWinWidth - 38);
|
||||
drawPango(layoutSmall, isSel ? &colYellow : &colMuted, 30, ry + 24,
|
||||
19, subLine, kWinWidth - 38);
|
||||
}
|
||||
}
|
||||
softLeft = "Add New";
|
||||
softCenter = entries.empty() ? "" : "Unblock";
|
||||
softRight = "Back";
|
||||
} else if (mode == ViewMode::AddBlock) {
|
||||
drawPango(layoutIcon, &colRed, 8, 0, kHeaderH, kIconBan);
|
||||
drawPango(layoutMain, &colWhite, 28, 0, kHeaderH, "Block Phone Number", 204);
|
||||
XSetForeground(dpy, gc, theme.spec.border);
|
||||
XFillRectangle(dpy, pix, gc, 0, kHeaderH - 1, kWinWidth, 1);
|
||||
|
||||
drawPango(layoutSmall, &colMuted, 12, kListY + 18, 20,
|
||||
"Enter number or prefix (e.g. +1800*):", kWinWidth - 24);
|
||||
XSetForeground(dpy, gc, theme.spec.bgAltOdd);
|
||||
XFillRectangle(dpy, pix, gc, 12, kListY + 44, kWinWidth - 24, 34);
|
||||
XSetForeground(dpy, gc, theme.spec.accent);
|
||||
XDrawRectangle(dpy, pix, gc, 12, kListY + 44, kWinWidth - 25, 33);
|
||||
|
||||
std::string disp = addBlockInput + "_";
|
||||
drawPango(layoutMain, &colWhite, 18, kListY + 47, 28, disp, kWinWidth - 36);
|
||||
drawPango(layoutSmall, &colMuted, 12, kListY + 90, 20,
|
||||
"0-9 / * / #: Digits #: + prefix", kWinWidth - 24);
|
||||
drawPango(layoutSmall, &colMuted, 12, kListY + 112, 20,
|
||||
"Left/Down: Backspace", kWinWidth - 24);
|
||||
|
||||
softLeft = "Block";
|
||||
softCenter = "SAVE";
|
||||
softRight = "Cancel";
|
||||
}
|
||||
|
||||
if (!toastMsg.empty() && nowMs() < toastUntilMs) {
|
||||
int tw = std::min(kWinWidth - 16, pangoWidth(layoutSmall, toastMsg) + 20);
|
||||
int tx = (kWinWidth - tw) / 2;
|
||||
int ty = kWinHeight - kSoftbarH - 28;
|
||||
XSetForeground(dpy, gc, theme.spec.selBg);
|
||||
XFillRectangle(dpy, pix, gc, tx, ty, tw, 24);
|
||||
XSetForeground(dpy, gc, theme.spec.accent);
|
||||
XDrawRectangle(dpy, pix, gc, tx, ty, tw - 1, 23);
|
||||
int textW = pangoWidth(layoutSmall, toastMsg);
|
||||
drawPango(layoutSmall, &colYellow, tx + (tw - textW) / 2, ty + 2, 20,
|
||||
toastMsg, tw - 8);
|
||||
}
|
||||
|
||||
int sbY = kWinHeight - kSoftbarH;
|
||||
@@ -310,15 +558,21 @@ int calllog_run_main(int argc, char** argv) {
|
||||
XSetForeground(dpy, gc, theme.spec.border);
|
||||
XFillRectangle(dpy, pix, gc, 0, sbY, kWinWidth, 1);
|
||||
|
||||
drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, "Dialer");
|
||||
std::string centerLabel = "CALL";
|
||||
int cw = pangoWidth(layoutMain, centerLabel);
|
||||
drawPango(layoutMain, filtered.empty() ? &colMuted : &colGreen,
|
||||
(kWinWidth - cw) / 2, sbY + 1, kSoftbarH - 1, centerLabel);
|
||||
std::string rightLabel = "Close";
|
||||
int rw = pangoWidth(layoutMain, rightLabel);
|
||||
drawPango(layoutMain, &colLight, 6, sbY + 1, kSoftbarH - 1, softLeft);
|
||||
if (!softCenter.empty()) {
|
||||
int cw = pangoWidth(layoutMain, softCenter);
|
||||
XftColor* cCol = &colGreen;
|
||||
if (mode == ViewMode::Log && filtered.empty()) {
|
||||
cCol = &colMuted;
|
||||
} else if (mode == ViewMode::Blocklist) {
|
||||
cCol = &colRed;
|
||||
}
|
||||
drawPango(layoutMain, cCol, (kWinWidth - cw) / 2, sbY + 1, kSoftbarH - 1,
|
||||
softCenter);
|
||||
}
|
||||
int rw = pangoWidth(layoutMain, softRight);
|
||||
drawPango(layoutMain, &colLight, kWinWidth - rw - 6, sbY + 1,
|
||||
kSoftbarH - 1, rightLabel);
|
||||
kSoftbarH - 1, softRight);
|
||||
|
||||
if (visible) {
|
||||
XCopyArea(dpy, pix, win, gc, 0, 0, kWinWidth, kWinHeight, 0, 0);
|
||||
@@ -326,9 +580,124 @@ int calllog_run_main(int argc, char** argv) {
|
||||
}
|
||||
};
|
||||
|
||||
auto executeOption = [&](int optId) {
|
||||
bool hasSel = (!filtered.empty() && selected >= 0 &&
|
||||
selected < static_cast<int>(filtered.size()));
|
||||
std::string selNum = hasSel ? filtered[selected].number : "";
|
||||
std::string selName = hasSel ? filtered[selected].contactName : "";
|
||||
|
||||
switch (optId) {
|
||||
case 1: { // Call Number
|
||||
if (!selNum.empty()) {
|
||||
mode = ViewMode::Log;
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
XUnmapWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
::unlink(activePidPath.c_str());
|
||||
visible = false;
|
||||
placePhoneCall(selNum);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: { // Open in Dialer
|
||||
mode = ViewMode::Log;
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
XUnmapWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
::unlink(activePidPath.c_str());
|
||||
visible = false;
|
||||
if (!sendPluginCommand("dialer",
|
||||
selNum.empty() ? "OPEN" : ("OPEN " + selNum))) {
|
||||
if (selNum.empty()) {
|
||||
runAsyncArgv("xdialer");
|
||||
} else {
|
||||
runAsyncArgv("xdialer", selNum.c_str());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: { // Send SMS
|
||||
if (!selNum.empty()) {
|
||||
mode = ViewMode::Log;
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
XUnmapWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
::unlink(activePidPath.c_str());
|
||||
visible = false;
|
||||
if (!sendPluginCommand("sms", "OPEN " + selNum)) {
|
||||
runAsyncArgv("xsms", selNum.c_str());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: { // Block / Unblock Number
|
||||
if (!selNum.empty()) {
|
||||
if (blockList.isBlocked(selNum)) {
|
||||
blockList.unblockNumber(selNum);
|
||||
showToast("Unblocked " + selNum);
|
||||
} else {
|
||||
blockList.blockNumber(selNum, selName.empty() ? "Call Log" : selName);
|
||||
showToast("Blocked " + selNum);
|
||||
}
|
||||
reloadLogsIfNeeded(true);
|
||||
}
|
||||
mode = ViewMode::Log;
|
||||
render();
|
||||
break;
|
||||
}
|
||||
case 5: { // Contacts
|
||||
mode = ViewMode::Log;
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
XUnmapWindow(dpy, win);
|
||||
XFlush(dpy);
|
||||
::unlink(activePidPath.c_str());
|
||||
visible = false;
|
||||
if (!sendPluginCommand("contacts", "OPEN")) {
|
||||
runAsyncArgv("xcontacts");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 6: { // Manage Blocklist
|
||||
blockList.load();
|
||||
blockSelected = 0;
|
||||
mode = ViewMode::Blocklist;
|
||||
render();
|
||||
break;
|
||||
}
|
||||
case 7: { // Block a Number...
|
||||
addBlockInput = selNum;
|
||||
mode = ViewMode::AddBlock;
|
||||
render();
|
||||
break;
|
||||
}
|
||||
case 8: { // Clear Spam Log
|
||||
clearCallLogFile(getBlockedModemLogFilePath());
|
||||
reloadLogsIfNeeded(true);
|
||||
showToast("Cleared blocked call log");
|
||||
mode = ViewMode::Log;
|
||||
render();
|
||||
break;
|
||||
}
|
||||
case 9: { // Clear Call Log
|
||||
clearCallLogFile(getModemLogFilePath());
|
||||
reloadLogsIfNeeded(true);
|
||||
showToast("Cleared call history");
|
||||
mode = ViewMode::Log;
|
||||
render();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mode = ViewMode::Log;
|
||||
render();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Pre-warm Pango/FreeType/Xrender glyph caches into offscreen Pixmap at startup
|
||||
drawPango(layoutIcon, &colGreen, 0, 0, 20,
|
||||
std::string(kIconInCall) + kIconOutCall + kIconMissedCall + kIconPhoneLog);
|
||||
std::string(kIconInCall) + kIconOutCall + kIconMissedCall +
|
||||
kIconPhoneLog + kIconBan + kIconShield);
|
||||
render();
|
||||
XSync(dpy, False);
|
||||
|
||||
@@ -344,6 +713,7 @@ int calllog_run_main(int argc, char** argv) {
|
||||
initialSoftkeysReleased = (querySoftkeysDown(dpy) == 0);
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
mode = ViewMode::Log;
|
||||
visible = true;
|
||||
XMapRaised(dpy, win);
|
||||
if (!themeChanged) {
|
||||
@@ -392,7 +762,8 @@ int calllog_run_main(int argc, char** argv) {
|
||||
visible = false;
|
||||
softkeyPending = 0;
|
||||
softkeyDeadlineMs = 0;
|
||||
if (filterTab != 0 || selected != 0) {
|
||||
if (mode != ViewMode::Log || filterTab != 0 || selected != 0) {
|
||||
mode = ViewMode::Log;
|
||||
filterTab = 0;
|
||||
selected = 0;
|
||||
applyFilter();
|
||||
@@ -422,6 +793,11 @@ int calllog_run_main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (visible && !toastMsg.empty() && nowMs() >= toastUntilMs) {
|
||||
toastMsg.clear();
|
||||
render();
|
||||
}
|
||||
|
||||
if (visible && !initialSoftkeysReleased && querySoftkeysDown(dpy) == 0) {
|
||||
initialSoftkeysReleased = true;
|
||||
}
|
||||
@@ -485,6 +861,128 @@ int calllog_run_main(int argc, char** argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode == ViewMode::AddBlock) {
|
||||
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
|
||||
if (dialCh == '#') {
|
||||
if (addBlockInput.empty()) {
|
||||
addBlockInput.push_back('+');
|
||||
} else {
|
||||
addBlockInput.push_back('#');
|
||||
}
|
||||
render();
|
||||
continue;
|
||||
} else if (dialCh != '\0') {
|
||||
addBlockInput.push_back(dialCh);
|
||||
render();
|
||||
continue;
|
||||
}
|
||||
if (sym == XK_BackSpace || sym == XK_Left || sym == XK_Down) {
|
||||
if (!addBlockInput.empty()) {
|
||||
addBlockInput.pop_back();
|
||||
render();
|
||||
}
|
||||
continue;
|
||||
} else if (sym == XK_Escape) {
|
||||
mode = ViewMode::Blocklist;
|
||||
render();
|
||||
continue;
|
||||
} else if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||
std::string num = trimStr(addBlockInput);
|
||||
if (!normalizePhone(num).empty()) {
|
||||
blockList.blockNumber(num, "Manual");
|
||||
reloadLogsIfNeeded(true);
|
||||
showToast("Blocked " + num);
|
||||
}
|
||||
mode = ViewMode::Blocklist;
|
||||
render();
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode == ViewMode::Options) {
|
||||
switch (sym) {
|
||||
case XK_Escape:
|
||||
case XK_BackSpace:
|
||||
case XK_Left:
|
||||
mode = ViewMode::Log;
|
||||
render();
|
||||
break;
|
||||
case XK_Up:
|
||||
case XK_KP_Up:
|
||||
case XK_k:
|
||||
if (!options.empty()) {
|
||||
optionSelected =
|
||||
(optionSelected + static_cast<int>(options.size()) - 1) %
|
||||
static_cast<int>(options.size());
|
||||
render();
|
||||
}
|
||||
break;
|
||||
case XK_Down:
|
||||
case XK_KP_Down:
|
||||
case XK_j:
|
||||
if (!options.empty()) {
|
||||
optionSelected =
|
||||
(optionSelected + 1) % static_cast<int>(options.size());
|
||||
render();
|
||||
}
|
||||
break;
|
||||
case XK_Return:
|
||||
case XK_KP_Enter:
|
||||
if (!options.empty() && optionSelected >= 0 &&
|
||||
optionSelected < static_cast<int>(options.size())) {
|
||||
executeOption(options[optionSelected].id);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mode == ViewMode::Blocklist) {
|
||||
switch (sym) {
|
||||
case XK_Escape:
|
||||
case XK_BackSpace:
|
||||
case XK_Left:
|
||||
mode = ViewMode::Log;
|
||||
render();
|
||||
break;
|
||||
case XK_Up:
|
||||
case XK_KP_Up:
|
||||
case XK_k:
|
||||
if (!blockList.entries().empty()) {
|
||||
int n = static_cast<int>(blockList.entries().size());
|
||||
blockSelected = (blockSelected + n - 1) % n;
|
||||
render();
|
||||
}
|
||||
break;
|
||||
case XK_Down:
|
||||
case XK_KP_Down:
|
||||
case XK_j:
|
||||
if (!blockList.entries().empty()) {
|
||||
int n = static_cast<int>(blockList.entries().size());
|
||||
blockSelected = (blockSelected + 1) % n;
|
||||
render();
|
||||
}
|
||||
break;
|
||||
case XK_Return:
|
||||
case XK_KP_Enter:
|
||||
if (!blockList.entries().empty() && blockSelected >= 0 &&
|
||||
blockSelected < static_cast<int>(blockList.entries().size())) {
|
||||
std::string num = blockList.entries()[blockSelected].number;
|
||||
blockList.unblockNumber(num);
|
||||
reloadLogsIfNeeded(true);
|
||||
showToast("Unblocked " + num);
|
||||
render();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
char dialCh = mapDialKey(sym, ev.xkey.keycode, ev.xkey.state);
|
||||
if (dialCh != '\0') {
|
||||
std::string arg(1, dialCh);
|
||||
@@ -505,7 +1003,7 @@ int calllog_run_main(int argc, char** argv) {
|
||||
case XK_Left:
|
||||
case XK_KP_Left:
|
||||
case XK_h:
|
||||
filterTab = (filterTab + 3) % 4;
|
||||
filterTab = (filterTab + kNumTabs - 1) % kNumTabs;
|
||||
selected = 0;
|
||||
applyFilter();
|
||||
render();
|
||||
@@ -513,7 +1011,7 @@ int calllog_run_main(int argc, char** argv) {
|
||||
case XK_Right:
|
||||
case XK_KP_Right:
|
||||
case XK_l:
|
||||
filterTab = (filterTab + 1) % 4;
|
||||
filterTab = (filterTab + 1) % kNumTabs;
|
||||
selected = 0;
|
||||
applyFilter();
|
||||
render();
|
||||
@@ -549,9 +1047,16 @@ int calllog_run_main(int argc, char** argv) {
|
||||
hideWindow(true);
|
||||
placePhoneCall(num);
|
||||
} else if (filtered.empty()) {
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("dialer", "OPEN")) {
|
||||
runAsyncArgv("xdialer");
|
||||
if (filterTab == 4) {
|
||||
blockList.load();
|
||||
blockSelected = 0;
|
||||
mode = ViewMode::Blocklist;
|
||||
render();
|
||||
} else {
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("dialer", "OPEN")) {
|
||||
runAsyncArgv("xdialer");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -572,22 +1077,40 @@ int calllog_run_main(int argc, char** argv) {
|
||||
runAsyncArgv("xdrawer");
|
||||
}
|
||||
} else if (comb == 1) {
|
||||
std::string arg;
|
||||
if (!filtered.empty() && selected >= 0 &&
|
||||
selected < static_cast<int>(filtered.size())) {
|
||||
arg = filtered[selected].number;
|
||||
}
|
||||
hideWindow(false);
|
||||
if (!sendPluginCommand("dialer", arg.empty() ? "OPEN" : ("OPEN " + arg))) {
|
||||
if (arg.empty()) {
|
||||
runAsyncArgv("xdialer");
|
||||
} else {
|
||||
runAsyncArgv("xdialer", arg.c_str());
|
||||
if (mode == ViewMode::Log) {
|
||||
buildOptions();
|
||||
mode = ViewMode::Options;
|
||||
render();
|
||||
} else if (mode == ViewMode::Options) {
|
||||
if (!options.empty() && optionSelected >= 0 &&
|
||||
optionSelected < static_cast<int>(options.size())) {
|
||||
executeOption(options[optionSelected].id);
|
||||
}
|
||||
} else if (mode == ViewMode::Blocklist) {
|
||||
addBlockInput.clear();
|
||||
mode = ViewMode::AddBlock;
|
||||
render();
|
||||
} else if (mode == ViewMode::AddBlock) {
|
||||
std::string num = trimStr(addBlockInput);
|
||||
if (!normalizePhone(num).empty()) {
|
||||
blockList.blockNumber(num, "Manual");
|
||||
reloadLogsIfNeeded(true);
|
||||
showToast("Blocked " + num);
|
||||
}
|
||||
mode = ViewMode::Blocklist;
|
||||
render();
|
||||
}
|
||||
} else if (comb == 2) {
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
if (mode == ViewMode::AddBlock) {
|
||||
mode = ViewMode::Blocklist;
|
||||
render();
|
||||
} else if (mode == ViewMode::Options || mode == ViewMode::Blocklist) {
|
||||
mode = ViewMode::Log;
|
||||
render();
|
||||
} else {
|
||||
writeSoftkeySuppress(nowMs() + 350);
|
||||
hideWindow(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,46 +1176,24 @@ int calllog_on_global_key(const DwmHostApi* host, KeySym keysym,
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int calllog_on_idle_key(const DwmHostApi* host, KeySym keysym,
|
||||
unsigned int keycode, unsigned int state) {
|
||||
if (!host || !host->is_idle_desktop || !host->is_idle_desktop() || state != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (keysym == XF86XK_Phone || keysym == XF86XK_PickupPhone || keycode == 177) {
|
||||
if (isCallRingingFast()) {
|
||||
if (!sendPluginCommand("callscreen", "ACCEPT")) {
|
||||
runAsyncArgv("xcallscreen", "accept");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (isCallActiveOrRingingFast()) {
|
||||
if (!sendPluginCommand("callscreen", "OPEN")) {
|
||||
runAsyncArgv("xcallscreen");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (host->spawn_plugin) {
|
||||
host->spawn_plugin("calllog", nullptr);
|
||||
if (!sendPluginCommand("calllog", "OPEN")) {
|
||||
runAsyncArgv("xcalllog");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) const DwmPlugin dwm_plugin_entry = {
|
||||
DWM_PLUGIN_API_VERSION,
|
||||
"calllog",
|
||||
"Keypad-driven call log viewer & dialer launcher (opens on idle Call key)",
|
||||
"Preloaded resident X11 call log viewer, anti-spam blocklist & dialer launcher",
|
||||
calllog_plugin_init,
|
||||
nullptr,
|
||||
nullptr,
|
||||
calllog_on_idle_key,
|
||||
nullptr,
|
||||
calllog_on_global_key,
|
||||
calllog_run_main
|
||||
};
|
||||
|
||||
@@ -118,6 +118,10 @@ bool isMediaExt(const std::string& ext) {
|
||||
ext == "avi";
|
||||
}
|
||||
|
||||
bool isGameBoyExt(const std::string& ext) {
|
||||
return ext == "gb" || ext == "gbc";
|
||||
}
|
||||
|
||||
bool isLikelyTextFile(const std::string& path, off_t size) {
|
||||
if (size == 0) return true;
|
||||
if (size > 256 * 1024) return false;
|
||||
@@ -612,6 +616,8 @@ int runFilesApp(int argc, char** argv) {
|
||||
} else if (isAudioExt(ext)) {
|
||||
actionsList.push_back({1, kIconMusic, "Play in Music Player"});
|
||||
actionsList.push_back({15, kIconBell, "Set as Ringtone"});
|
||||
} else if (isGameBoyExt(ext)) {
|
||||
actionsList.push_back({1, kIconGamepad, "Play in Game Boy Emulator"});
|
||||
} else {
|
||||
actionsList.push_back({1, kIconEye, "Open / View"});
|
||||
}
|
||||
@@ -689,6 +695,10 @@ int runFilesApp(int argc, char** argv) {
|
||||
} else if (isAudioExt(ext)) {
|
||||
openInMusicPlayer(item.fullPath);
|
||||
return;
|
||||
} else if (isGameBoyExt(ext)) {
|
||||
hideWindow();
|
||||
runAsyncArgv("gambatte", item.fullPath.c_str());
|
||||
return;
|
||||
} else if (isMediaExt(ext)) {
|
||||
hideWindow();
|
||||
runAsyncArgv("sxmo_open.sh", item.fullPath.c_str());
|
||||
@@ -832,6 +842,9 @@ int runFilesApp(int argc, char** argv) {
|
||||
} else if (isMediaExt(ext)) {
|
||||
ic = kIconMusic;
|
||||
icCol = &colBlue;
|
||||
} else if (isGameBoyExt(ext)) {
|
||||
ic = kIconGamepad;
|
||||
icCol = &colYellow;
|
||||
} else if (it.isExec || ext == "sh") {
|
||||
ic = kIconTerm;
|
||||
icCol = &colOrange;
|
||||
|
||||
+351
-17
@@ -93,6 +93,9 @@ constexpr const char* kIconToggleOff = "\xef\x88\x84"; // U+F204 toggle off
|
||||
constexpr const char* kIconKey = "\xef\x82\x84"; // U+F084 key / PIN
|
||||
constexpr const char* kIconSim = "\xf3\xb0\x99\x95"; // U+F0655 nf-md-sim
|
||||
constexpr const char* kIconBell = "\xef\x83\xb3"; // U+F0F3 bell / ringtone
|
||||
constexpr const char* kIconBan = "\xef\x81\x9e"; // U+F05E ban / block
|
||||
constexpr const char* kIconShield = "\xef\x84\xb2"; // U+F132 shield / anti-spam
|
||||
constexpr const char* kIconGamepad = "\xef\x84\x9b"; // U+F11B gamepad / emulator
|
||||
|
||||
inline int64_t nowMs() {
|
||||
struct timespec ts {};
|
||||
@@ -947,10 +950,247 @@ class ContactBook {
|
||||
std::unordered_map<std::string, std::string> suffixMap_;
|
||||
};
|
||||
|
||||
inline std::string getBlockFilePath() {
|
||||
const char* env = std::getenv("SXMO_BLOCKFILE");
|
||||
if (env && env[0]) {
|
||||
return env;
|
||||
}
|
||||
const char* xdg = std::getenv("XDG_CONFIG_HOME");
|
||||
if (xdg && xdg[0]) {
|
||||
return std::string(xdg) + "/sxmo/block.tsv";
|
||||
}
|
||||
const char* home = std::getenv("HOME");
|
||||
return std::string(home && home[0] ? home : "/home/user") +
|
||||
"/.config/sxmo/block.tsv";
|
||||
}
|
||||
|
||||
inline std::string getBlockDirPath() {
|
||||
const char* env = std::getenv("SXMO_BLOCKDIR");
|
||||
if (env && env[0]) {
|
||||
return env;
|
||||
}
|
||||
const char* xdg = std::getenv("XDG_DATA_HOME");
|
||||
if (xdg && xdg[0]) {
|
||||
return std::string(xdg) + "/sxmo/block";
|
||||
}
|
||||
const char* home = std::getenv("HOME");
|
||||
return std::string(home && home[0] ? home : "/home/user") +
|
||||
"/.local/share/sxmo/block";
|
||||
}
|
||||
|
||||
inline std::string getBlockedModemLogFilePath() {
|
||||
return getBlockDirPath() + "/modemlog.tsv";
|
||||
}
|
||||
|
||||
struct BlockEntry {
|
||||
std::string number;
|
||||
std::string normNumber;
|
||||
std::string label;
|
||||
};
|
||||
|
||||
class BlockList {
|
||||
public:
|
||||
void load() {
|
||||
entries_.clear();
|
||||
exactSet_.clear();
|
||||
suffixSet_.clear();
|
||||
|
||||
std::ifstream bf(getBlockFilePath());
|
||||
std::string line;
|
||||
while (std::getline(bf, line)) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
size_t tab = line.find('\t');
|
||||
std::string num = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
|
||||
std::string lbl = tab == std::string::npos ? "" : trimStr(line.substr(tab + 1));
|
||||
std::string norm = normalizePhone(num);
|
||||
if (norm.empty()) {
|
||||
continue;
|
||||
}
|
||||
BlockEntry e;
|
||||
e.number = num;
|
||||
e.normNumber = norm;
|
||||
e.label = lbl.empty() ? "Blocked" : lbl;
|
||||
entries_.push_back(e);
|
||||
exactSet_.insert(norm);
|
||||
std::string sfx = phoneSuffixKey(norm);
|
||||
if (sfx.size() >= 7) {
|
||||
suffixSet_.insert(sfx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isBlocked(const std::string& rawNumber) const {
|
||||
std::string norm = normalizePhone(rawNumber);
|
||||
if (norm.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (exactSet_.count(norm) > 0) {
|
||||
return true;
|
||||
}
|
||||
std::string sfx = phoneSuffixKey(norm);
|
||||
if (sfx.size() >= 7 && suffixSet_.count(sfx) > 0) {
|
||||
return true;
|
||||
}
|
||||
for (const auto& e : entries_) {
|
||||
if (!e.normNumber.empty() && e.normNumber.back() == '*' &&
|
||||
e.normNumber.size() > 1) {
|
||||
std::string prefix = e.normNumber.substr(0, e.normNumber.size() - 1);
|
||||
if (norm.rfind(prefix, 0) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool blockNumber(const std::string& rawNumber,
|
||||
const std::string& labelRaw = "") {
|
||||
std::string num = trimStr(rawNumber);
|
||||
std::string norm = normalizePhone(num);
|
||||
if (norm.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (num.find('\t') != std::string::npos ||
|
||||
num.find('\n') != std::string::npos) {
|
||||
num = norm;
|
||||
}
|
||||
std::string label = trimStr(labelRaw);
|
||||
for (char& ch : label) {
|
||||
if (ch == '\t' || ch == '\r' || ch == '\n') {
|
||||
ch = ' ';
|
||||
}
|
||||
}
|
||||
if (label.empty()) {
|
||||
label = "Blocked";
|
||||
}
|
||||
|
||||
std::string path = getBlockFilePath();
|
||||
ContactBook::ensureParentDir(path);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> rows;
|
||||
{
|
||||
std::ifstream in(path);
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (line.empty()) continue;
|
||||
size_t tab = line.find('\t');
|
||||
std::string rNum = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
|
||||
std::string rLbl = tab == std::string::npos ? "Blocked" : trimStr(line.substr(tab + 1));
|
||||
if (!normalizePhone(rNum).empty()) {
|
||||
rows.push_back({rNum, rLbl.empty() ? "Blocked" : rLbl});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string sfx = phoneSuffixKey(norm);
|
||||
bool updated = false;
|
||||
for (auto& r : rows) {
|
||||
std::string rNorm = normalizePhone(r.first);
|
||||
std::string rSfx = phoneSuffixKey(rNorm);
|
||||
if (rNorm == norm || (sfx.size() >= 7 && rSfx == sfx)) {
|
||||
r.first = num;
|
||||
if (!labelRaw.empty()) {
|
||||
r.second = label;
|
||||
}
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
rows.push_back({num, label});
|
||||
}
|
||||
|
||||
std::ofstream out(path, std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& r : rows) {
|
||||
out << r.first << "\t" << r.second << "\n";
|
||||
}
|
||||
out.close();
|
||||
load();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unblockNumber(const std::string& rawNumber) {
|
||||
std::string norm = normalizePhone(rawNumber);
|
||||
if (norm.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string sfx = phoneSuffixKey(norm);
|
||||
std::string path = getBlockFilePath();
|
||||
std::vector<std::pair<std::string, std::string>> rows;
|
||||
bool removed = false;
|
||||
{
|
||||
std::ifstream in(path);
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
if (line.empty()) continue;
|
||||
size_t tab = line.find('\t');
|
||||
std::string rNum = trimStr(tab == std::string::npos ? line : line.substr(0, tab));
|
||||
std::string rLbl = tab == std::string::npos ? "Blocked" : trimStr(line.substr(tab + 1));
|
||||
std::string rNorm = normalizePhone(rNum);
|
||||
if (rNorm.empty()) continue;
|
||||
std::string rSfx = phoneSuffixKey(rNorm);
|
||||
if (rNorm == norm || (sfx.size() >= 7 && rSfx == sfx)) {
|
||||
removed = true;
|
||||
continue;
|
||||
}
|
||||
rows.push_back({rNum, rLbl.empty() ? "Blocked" : rLbl});
|
||||
}
|
||||
}
|
||||
if (!removed) {
|
||||
return false;
|
||||
}
|
||||
ContactBook::ensureParentDir(path);
|
||||
std::ofstream out(path, std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& r : rows) {
|
||||
out << r.first << "\t" << r.second << "\n";
|
||||
}
|
||||
out.close();
|
||||
load();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool clearAll() {
|
||||
std::string path = getBlockFilePath();
|
||||
ContactBook::ensureParentDir(path);
|
||||
std::ofstream out(path, std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
out.close();
|
||||
load();
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<BlockEntry>& entries() const { return entries_; }
|
||||
|
||||
private:
|
||||
std::vector<BlockEntry> entries_;
|
||||
std::unordered_set<std::string> exactSet_;
|
||||
std::unordered_set<std::string> suffixSet_;
|
||||
};
|
||||
|
||||
enum class CallDirection {
|
||||
Incoming = 0,
|
||||
Outgoing = 1,
|
||||
Missed = 2,
|
||||
Blocked = 3,
|
||||
};
|
||||
|
||||
struct CallLogItem {
|
||||
@@ -959,6 +1199,7 @@ struct CallLogItem {
|
||||
std::string contactName;
|
||||
std::string displayTime;
|
||||
int durationSec = 0;
|
||||
bool isBlockedNumber = false;
|
||||
};
|
||||
|
||||
inline time_t parseIsoTime(const std::string& iso) {
|
||||
@@ -999,8 +1240,10 @@ inline std::string formatDuration(int sec) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
|
||||
std::ifstream lf(getModemLogFilePath());
|
||||
inline std::vector<CallLogItem> loadCallLogFromFile(const ContactBook& book,
|
||||
const std::string& logPath,
|
||||
bool forceBlockedDir = false) {
|
||||
std::ifstream lf(logPath);
|
||||
std::string line;
|
||||
|
||||
struct ActiveSession {
|
||||
@@ -1022,7 +1265,7 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
|
||||
return;
|
||||
}
|
||||
CallLogItem item;
|
||||
item.direction = cur.dir;
|
||||
item.direction = forceBlockedDir ? CallDirection::Blocked : cur.dir;
|
||||
item.number = cur.number;
|
||||
item.contactName = book.resolveName(cur.number);
|
||||
item.displayTime = formatShortTime(cur.startTimeStr);
|
||||
@@ -1053,10 +1296,24 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
|
||||
|
||||
time_t epoch = parseIsoTime(ts);
|
||||
|
||||
if (evt == "call_ring") {
|
||||
if (evt == "call_blocked") {
|
||||
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
|
||||
cur.dir = CallDirection::Blocked;
|
||||
flushSession();
|
||||
} else {
|
||||
flushSession();
|
||||
CallLogItem item;
|
||||
item.direction = CallDirection::Blocked;
|
||||
item.number = num;
|
||||
item.contactName = book.resolveName(num);
|
||||
item.displayTime = formatShortTime(ts);
|
||||
item.durationSec = 0;
|
||||
history.push_back(item);
|
||||
}
|
||||
} else if (evt == "call_ring") {
|
||||
flushSession();
|
||||
cur.active = true;
|
||||
cur.dir = CallDirection::Missed;
|
||||
cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
|
||||
cur.number = num;
|
||||
cur.startTimeStr = ts;
|
||||
cur.startEpoch = epoch;
|
||||
@@ -1088,12 +1345,12 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
|
||||
}
|
||||
} else if (evt == "call_missed") {
|
||||
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
|
||||
cur.dir = CallDirection::Missed;
|
||||
cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
|
||||
flushSession();
|
||||
} else {
|
||||
flushSession();
|
||||
CallLogItem item;
|
||||
item.direction = CallDirection::Missed;
|
||||
item.direction = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
|
||||
item.number = num;
|
||||
item.contactName = book.resolveName(num);
|
||||
item.displayTime = formatShortTime(ts);
|
||||
@@ -1105,10 +1362,10 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
|
||||
if (cur.active && normalizePhone(cur.number) == normalizePhone(num)) {
|
||||
if (evt == "call_discard" && !cur.answered &&
|
||||
cur.dir != CallDirection::Outgoing) {
|
||||
cur.dir = CallDirection::Missed;
|
||||
cur.dir = forceBlockedDir ? CallDirection::Blocked : CallDirection::Missed;
|
||||
}
|
||||
CallLogItem item;
|
||||
item.direction = cur.dir;
|
||||
item.direction = forceBlockedDir ? CallDirection::Blocked : cur.dir;
|
||||
item.number = cur.number;
|
||||
item.contactName = book.resolveName(cur.number);
|
||||
item.displayTime = formatShortTime(cur.startTimeStr);
|
||||
@@ -1126,6 +1383,44 @@ inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
|
||||
return history;
|
||||
}
|
||||
|
||||
inline std::vector<CallLogItem> loadCallLog(const ContactBook& book) {
|
||||
return loadCallLogFromFile(book, getModemLogFilePath(), false);
|
||||
}
|
||||
|
||||
inline std::vector<CallLogItem> loadBlockedCallLog(const ContactBook& book) {
|
||||
return loadCallLogFromFile(book, getBlockedModemLogFilePath(), true);
|
||||
}
|
||||
|
||||
inline bool clearCallLogFile(const std::string& logPath) {
|
||||
std::ifstream in(logPath);
|
||||
if (!in.is_open()) {
|
||||
return true;
|
||||
}
|
||||
std::vector<std::string> keep;
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
if (line.empty()) continue;
|
||||
size_t t1 = line.find('\t');
|
||||
if (t1 == std::string::npos) continue;
|
||||
size_t t2 = line.find('\t', t1 + 1);
|
||||
if (t2 == std::string::npos) continue;
|
||||
std::string evt = trimStr(line.substr(t1 + 1, t2 - t1 - 1));
|
||||
if (evt.rfind("call_", 0) == 0 || evt == "ring_muted") {
|
||||
continue;
|
||||
}
|
||||
keep.push_back(line);
|
||||
}
|
||||
in.close();
|
||||
std::ofstream out(logPath, std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& l : keep) {
|
||||
out << l << "\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void placePhoneCall(const std::string& rawNumber) {
|
||||
std::string num = normalizePhone(rawNumber);
|
||||
if (num.empty()) {
|
||||
@@ -1285,6 +1580,7 @@ struct SmsThread {
|
||||
time_t lastEpoch = 0;
|
||||
bool lastIncoming = true;
|
||||
bool hasDraft = false;
|
||||
bool isBlocked = false;
|
||||
int messageCount = 0;
|
||||
};
|
||||
|
||||
@@ -1319,9 +1615,12 @@ inline bool parseSmsHeaderLine(const std::string& line,
|
||||
return false;
|
||||
}
|
||||
|
||||
inline std::vector<SmsMessage> loadSmsThreadMessages(const std::string& number) {
|
||||
inline std::vector<SmsMessage> loadSmsThreadMessages(
|
||||
const std::string& number,
|
||||
const std::string& customBaseDir = "") {
|
||||
std::vector<SmsMessage> msgs;
|
||||
std::string path = getModemLogDirPath() + "/" + number + "/sms.txt";
|
||||
std::string baseDir = customBaseDir.empty() ? getModemLogDirPath() : customBaseDir;
|
||||
std::string path = baseDir + "/" + number + "/sms.txt";
|
||||
std::ifstream in(path);
|
||||
if (!in.is_open()) {
|
||||
return msgs;
|
||||
@@ -1424,11 +1723,13 @@ inline void removeSmsDraft(const std::string& number) {
|
||||
::unlink(path.c_str());
|
||||
}
|
||||
|
||||
inline bool deleteSmsThread(const std::string& number) {
|
||||
inline bool deleteSmsThread(const std::string& number,
|
||||
const std::string& customBaseDir = "") {
|
||||
if (number.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string dir = getModemLogDirPath() + "/" + number;
|
||||
std::string baseDir = customBaseDir.empty() ? getModemLogDirPath() : customBaseDir;
|
||||
std::string dir = baseDir + "/" + number;
|
||||
std::string smsPath = dir + "/sms.txt";
|
||||
std::string draftPath = dir + "/draft.txt";
|
||||
::unlink(smsPath.c_str());
|
||||
@@ -1437,9 +1738,42 @@ inline bool deleteSmsThread(const std::string& number) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) {
|
||||
inline bool moveSmsThreadBetweenDirs(const std::string& number,
|
||||
const std::string& fromBaseDir,
|
||||
const std::string& toBaseDir) {
|
||||
if (number.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string srcPath = fromBaseDir + "/" + number + "/sms.txt";
|
||||
std::ifstream in(srcPath);
|
||||
if (!in.is_open()) {
|
||||
return false;
|
||||
}
|
||||
std::ostringstream ss;
|
||||
ss << in.rdbuf();
|
||||
in.close();
|
||||
std::string content = ss.str();
|
||||
if (content.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string dstPath = toBaseDir + "/" + number + "/sms.txt";
|
||||
ContactBook::ensureParentDir(dstPath);
|
||||
std::ofstream out(dstPath, std::ios::app);
|
||||
if (!out.is_open()) {
|
||||
return false;
|
||||
}
|
||||
out << content;
|
||||
out.close();
|
||||
deleteSmsThread(number, fromBaseDir);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::vector<SmsThread> loadSmsThreads(
|
||||
const ContactBook& book,
|
||||
const std::string& customBaseDir = "") {
|
||||
std::vector<SmsThread> threads;
|
||||
std::string baseDir = getModemLogDirPath();
|
||||
bool isSpamDir = !customBaseDir.empty();
|
||||
std::string baseDir = isSpamDir ? customBaseDir : getModemLogDirPath();
|
||||
DIR* d = ::opendir(baseDir.c_str());
|
||||
if (!d) {
|
||||
return threads;
|
||||
@@ -1459,13 +1793,13 @@ inline std::vector<SmsThread> loadSmsThreads(const ContactBook& book) {
|
||||
std::string smsPath = subDir + "/sms.txt";
|
||||
std::string draftPath = subDir + "/draft.txt";
|
||||
bool hasSms = (::access(smsPath.c_str(), R_OK) == 0);
|
||||
std::string draft = loadSmsDraft(name);
|
||||
std::string draft = isSpamDir ? "" : loadSmsDraft(name);
|
||||
bool hasDraft = !draft.empty();
|
||||
if (!hasSms && !hasDraft) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto msgs = loadSmsThreadMessages(name);
|
||||
auto msgs = loadSmsThreadMessages(name, baseDir);
|
||||
if (msgs.empty() && !hasDraft) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+315
-36
@@ -28,6 +28,8 @@ enum class ViewMode {
|
||||
PickContact = 3,
|
||||
Options = 4,
|
||||
ConfirmDelete = 5,
|
||||
Blocklist = 6,
|
||||
AddBlock = 7,
|
||||
};
|
||||
|
||||
enum class TextCaseMode {
|
||||
@@ -171,6 +173,9 @@ int runSmsApp(int argc, char** argv) {
|
||||
auto& colRed = pt.colRed;
|
||||
|
||||
ContactBook book;
|
||||
BlockList blockList;
|
||||
std::vector<SmsThread> inboxThreads;
|
||||
std::vector<SmsThread> spamThreads;
|
||||
std::vector<SmsThread> allThreads;
|
||||
std::vector<SmsThread> filteredThreads;
|
||||
std::vector<SmsMessage> threadMessages;
|
||||
@@ -178,7 +183,10 @@ int runSmsApp(int argc, char** argv) {
|
||||
|
||||
FileStamp contactsStamp;
|
||||
FileStamp modemLogStamp;
|
||||
FileStamp blockFileStamp;
|
||||
FileStamp blockLogStamp;
|
||||
bool loadedOnce = false;
|
||||
bool viewingSpamFolder = false;
|
||||
|
||||
ViewMode mode = ViewMode::Threads;
|
||||
ViewMode prevMode = ViewMode::Threads;
|
||||
@@ -188,6 +196,8 @@ int runSmsApp(int argc, char** argv) {
|
||||
int selectedOption = 0;
|
||||
int selectedPicker = 0;
|
||||
int confirmSelected = 0;
|
||||
int blockSelected = 0;
|
||||
std::string addBlockInput;
|
||||
|
||||
std::string activeNumber;
|
||||
std::string composeRecipient;
|
||||
@@ -216,10 +226,15 @@ int runSmsApp(int argc, char** argv) {
|
||||
int id;
|
||||
std::string icon;
|
||||
std::string label;
|
||||
bool danger = false;
|
||||
};
|
||||
std::vector<OptionEntry> optionsList;
|
||||
|
||||
auto updateFilter = [&]() {
|
||||
allThreads = viewingSpamFolder ? spamThreads : inboxThreads;
|
||||
for (auto& t : allThreads) {
|
||||
t.isBlocked = viewingSpamFolder || blockList.isBlocked(t.number);
|
||||
}
|
||||
filteredThreads.clear();
|
||||
if (searchQuery.empty()) {
|
||||
filteredThreads = allThreads;
|
||||
@@ -247,13 +262,22 @@ int runSmsApp(int argc, char** argv) {
|
||||
auto reloadAllData = [&](bool force = false) {
|
||||
bool cChanged = contactsStamp.checkAndUpdate(getContactsFilePath());
|
||||
bool lChanged = modemLogStamp.checkAndUpdate(getModemLogFilePath());
|
||||
if (force || cChanged || lChanged || !loadedOnce) {
|
||||
bool bChanged = blockFileStamp.checkAndUpdate(getBlockFilePath());
|
||||
bool blChanged = blockLogStamp.checkAndUpdate(getBlockedModemLogFilePath());
|
||||
if (force || cChanged || lChanged || bChanged || blChanged || !loadedOnce) {
|
||||
loadedOnce = true;
|
||||
book.load();
|
||||
allThreads = loadSmsThreads(book);
|
||||
blockList.load();
|
||||
inboxThreads = loadSmsThreads(book, getModemLogDirPath());
|
||||
spamThreads = loadSmsThreads(book, getBlockDirPath());
|
||||
updateFilter();
|
||||
if (blockSelected >= static_cast<int>(blockList.entries().size())) {
|
||||
blockSelected = std::max(0, static_cast<int>(blockList.entries().size()) - 1);
|
||||
}
|
||||
if (!activeNumber.empty()) {
|
||||
threadMessages = loadSmsThreadMessages(activeNumber);
|
||||
std::string curBase =
|
||||
viewingSpamFolder ? getBlockDirPath() : getModemLogDirPath();
|
||||
threadMessages = loadSmsThreadMessages(activeNumber, curBase);
|
||||
if (selectedMsg >= static_cast<int>(threadMessages.size())) {
|
||||
selectedMsg =
|
||||
std::max(0, static_cast<int>(threadMessages.size()) - 1);
|
||||
@@ -266,7 +290,9 @@ int runSmsApp(int argc, char** argv) {
|
||||
|
||||
auto openThreadView = [&](const std::string& num) {
|
||||
activeNumber = trimStr(num);
|
||||
threadMessages = loadSmsThreadMessages(activeNumber);
|
||||
std::string curBase =
|
||||
viewingSpamFolder ? getBlockDirPath() : getModemLogDirPath();
|
||||
threadMessages = loadSmsThreadMessages(activeNumber, curBase);
|
||||
selectedMsg = std::max(0, static_cast<int>(threadMessages.size()) - 1);
|
||||
mode = ViewMode::ThreadView;
|
||||
};
|
||||
@@ -289,22 +315,55 @@ int runSmsApp(int argc, char** argv) {
|
||||
auto buildOptions = [&]() {
|
||||
optionsList.clear();
|
||||
if (mode == ViewMode::Threads) {
|
||||
optionsList.push_back({1, kIconPlus, "New Message"});
|
||||
optionsList.push_back({1, kIconPlus, "New Message", false});
|
||||
if (!filteredThreads.empty()) {
|
||||
optionsList.push_back({2, kIconEye, "Open Conversation"});
|
||||
optionsList.push_back({3, kIconSend, "Reply to Selected"});
|
||||
optionsList.push_back({4, kIconPhone, "Call Contact"});
|
||||
optionsList.push_back({5, kIconUser, "Open in Contacts"});
|
||||
optionsList.push_back({6, kIconTrash, "Delete Conversation"});
|
||||
const auto& selTh = filteredThreads[selectedThread];
|
||||
bool isBlk = blockList.isBlocked(selTh.number);
|
||||
optionsList.push_back({2, kIconEye, "Open Conversation", false});
|
||||
optionsList.push_back({3, kIconSend, "Reply to Selected", false});
|
||||
optionsList.push_back({4, kIconPhone, "Call Contact", false});
|
||||
if (isBlk || viewingSpamFolder) {
|
||||
optionsList.push_back({8, kIconCheck, "Unblock & Restore", false});
|
||||
} else {
|
||||
optionsList.push_back({8, kIconBan, "Block Number (Spam)", true});
|
||||
}
|
||||
optionsList.push_back({5, kIconUser, "Open in Contacts", false});
|
||||
optionsList.push_back({6, kIconTrash, "Delete Conversation", true});
|
||||
}
|
||||
if (viewingSpamFolder) {
|
||||
optionsList.push_back(
|
||||
{9, kIconSms,
|
||||
"Switch to Inbox (" + std::to_string(inboxThreads.size()) + ")",
|
||||
false});
|
||||
} else {
|
||||
optionsList.push_back(
|
||||
{9, kIconShield,
|
||||
"Spam Folder (" + std::to_string(spamThreads.size()) + ")",
|
||||
false});
|
||||
}
|
||||
optionsList.push_back(
|
||||
{10, kIconBan,
|
||||
"Manage Blocklist (" + std::to_string(blockList.entries().size()) + ")",
|
||||
false});
|
||||
optionsList.push_back({11, kIconPlus, "Block a Number...", false});
|
||||
} else if (mode == ViewMode::ThreadView) {
|
||||
optionsList.push_back({3, kIconSend, "Reply / Compose"});
|
||||
optionsList.push_back({4, kIconPhone, "Call Contact"});
|
||||
bool isBlk = blockList.isBlocked(activeNumber);
|
||||
optionsList.push_back({3, kIconSend, "Reply / Compose", false});
|
||||
optionsList.push_back({4, kIconPhone, "Call Contact", false});
|
||||
if (!threadMessages.empty()) {
|
||||
optionsList.push_back({7, kIconCopy, "Forward Selected Msg"});
|
||||
optionsList.push_back({7, kIconCopy, "Forward Selected Msg", false});
|
||||
}
|
||||
optionsList.push_back({5, kIconUser, "Save / View Contact"});
|
||||
optionsList.push_back({6, kIconTrash, "Delete Conversation"});
|
||||
if (isBlk || viewingSpamFolder) {
|
||||
optionsList.push_back({8, kIconCheck, "Unblock & Restore", false});
|
||||
} else {
|
||||
optionsList.push_back({8, kIconBan, "Block Number (Spam)", true});
|
||||
}
|
||||
optionsList.push_back({5, kIconUser, "Save / View Contact", false});
|
||||
optionsList.push_back({6, kIconTrash, "Delete Conversation", true});
|
||||
optionsList.push_back(
|
||||
{10, kIconBan,
|
||||
"Manage Blocklist (" + std::to_string(blockList.entries().size()) + ")",
|
||||
false});
|
||||
}
|
||||
selectedOption = 0;
|
||||
};
|
||||
@@ -462,9 +521,13 @@ int runSmsApp(int argc, char** argv) {
|
||||
XDrawLine(dpy, pix, gc, 0, 37, kWinWidth, 37);
|
||||
|
||||
if (searchQuery.empty()) {
|
||||
drawPango(layoutIcon, &colYellow, 8, 2, 34, kIconSms);
|
||||
drawPango(layoutMain, &colWhite, 30, 2, 34, "Messages", 145);
|
||||
std::string cnt = "[" + std::to_string(allThreads.size()) + "]";
|
||||
drawPango(layoutIcon, viewingSpamFolder ? &colRed : &colYellow, 8, 2,
|
||||
34, viewingSpamFolder ? kIconBan : kIconSms);
|
||||
std::string hdrTitle =
|
||||
viewingSpamFolder ? "Spam / Blocked" : "Messages";
|
||||
drawPango(layoutMain, viewingSpamFolder ? &colRed : &colWhite, 30, 2,
|
||||
34, hdrTitle, 145);
|
||||
std::string cnt = "< [" + std::to_string(allThreads.size()) + "] >";
|
||||
pango_layout_set_width(layoutSmall, -1);
|
||||
pango_layout_set_text(layoutSmall, cnt.c_str(),
|
||||
static_cast<int>(cnt.size()));
|
||||
@@ -489,9 +552,15 @@ int runSmsApp(int argc, char** argv) {
|
||||
|
||||
if (filteredThreads.empty()) {
|
||||
drawPango(layoutMain, &colMuted, 16, 110, 24,
|
||||
searchQuery.empty() ? "No messages yet" : "No matches", 208);
|
||||
searchQuery.empty()
|
||||
? (viewingSpamFolder ? "No blocked messages"
|
||||
: "No messages yet")
|
||||
: "No matches",
|
||||
208);
|
||||
drawPango(layoutSmall, &colBlue, 16, 138, 20,
|
||||
"Press Left Softkey for New SMS", 208);
|
||||
viewingSpamFolder ? "Left/Right: Switch to Inbox"
|
||||
: "Left Softkey: Options / Blocklist",
|
||||
208);
|
||||
} else {
|
||||
int scroll = 0;
|
||||
if (selectedThread >= visibleRows) {
|
||||
@@ -515,17 +584,22 @@ int runSmsApp(int argc, char** argv) {
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
|
||||
}
|
||||
|
||||
const char* ic = th.hasDraft
|
||||
? kIconEdit
|
||||
: (th.lastIncoming ? kIconInCall : kIconOutCall);
|
||||
XftColor* icCol = th.hasDraft
|
||||
? &colYellow
|
||||
: (th.lastIncoming ? &colGreen : &colBlue);
|
||||
const char* ic =
|
||||
th.isBlocked
|
||||
? kIconBan
|
||||
: (th.hasDraft ? kIconEdit
|
||||
: (th.lastIncoming ? kIconInCall : kIconOutCall));
|
||||
XftColor* icCol =
|
||||
th.isBlocked
|
||||
? &colRed
|
||||
: (th.hasDraft ? &colYellow
|
||||
: (th.lastIncoming ? &colGreen : &colBlue));
|
||||
drawPango(layoutIcon, icCol, 6, ry + 1, 20, ic);
|
||||
|
||||
std::string title =
|
||||
th.contactName.empty() ? th.number : th.contactName;
|
||||
drawPango(layoutMain, &colWhite, 26, ry + 1, 20, title, 128);
|
||||
drawPango(layoutMain, th.isBlocked ? &colRed : &colWhite, 26, ry + 1,
|
||||
20, title, 128);
|
||||
|
||||
if (!th.lastDisplayTime.empty()) {
|
||||
pango_layout_set_width(layoutSmall, -1);
|
||||
@@ -807,18 +881,89 @@ int runSmsApp(int argc, char** argv) {
|
||||
|
||||
const int listY = 38;
|
||||
const int rowH = 36;
|
||||
for (size_t i = 0; i < optionsList.size(); ++i) {
|
||||
int ry = listY + static_cast<int>(i) * rowH;
|
||||
bool isSel = (static_cast<int>(i) == selectedOption);
|
||||
const int visRows = 6;
|
||||
int scroll = 0;
|
||||
if (selectedOption >= visRows) {
|
||||
scroll = selectedOption - visRows + 1;
|
||||
}
|
||||
for (int i = 0; i < visRows; ++i) {
|
||||
int idx = scroll + i;
|
||||
if (idx >= static_cast<int>(optionsList.size())) break;
|
||||
int ry = listY + i * rowH;
|
||||
bool isSel = (idx == selectedOption);
|
||||
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain);
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
|
||||
drawPango(layoutIcon, isSel ? &colYellow : &colBlue, 10, ry, rowH,
|
||||
optionsList[i].icon);
|
||||
drawPango(layoutMain, &colWhite, 34, ry, rowH, optionsList[i].label,
|
||||
198);
|
||||
XftColor* icCol =
|
||||
optionsList[idx].danger ? &colRed : (isSel ? &colYellow : &colBlue);
|
||||
XftColor* txCol = optionsList[idx].danger ? &colRed : &colWhite;
|
||||
drawPango(layoutIcon, icCol, 10, ry, rowH, optionsList[idx].icon);
|
||||
drawPango(layoutMain, txCol, 34, ry, rowH, optionsList[idx].label, 198);
|
||||
}
|
||||
drawSoftkeyBar("Select", "Select", "Back");
|
||||
|
||||
} else if (mode == ViewMode::Blocklist) {
|
||||
XSetForeground(dpy, gc, pt.spec.bgHeader);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34);
|
||||
drawPango(layoutIcon, &colRed, 8, 0, 34, kIconBan);
|
||||
std::string hdr =
|
||||
"Blocklist (" + std::to_string(blockList.entries().size()) + ")";
|
||||
drawPango(layoutMain, &colWhite, 30, 0, 34, hdr, 200);
|
||||
|
||||
const auto& entries = blockList.entries();
|
||||
if (entries.empty()) {
|
||||
drawPango(layoutIcon, &colMuted, 112, 94, 24, kIconShield);
|
||||
drawPango(layoutMain, &colMuted, 44, 124, 24, "Blocklist is empty",
|
||||
180);
|
||||
drawPango(layoutSmall, &colBlue, 22, 152, 20,
|
||||
"Left Softkey: Block a Number", 204);
|
||||
} else {
|
||||
const int listY = 36;
|
||||
const int rowH = 38;
|
||||
const int visRows = 6;
|
||||
int scroll = 0;
|
||||
if (blockSelected >= visRows) {
|
||||
scroll = blockSelected - visRows + 1;
|
||||
}
|
||||
for (int i = 0; i < visRows; ++i) {
|
||||
int idx = scroll + i;
|
||||
if (idx >= static_cast<int>(entries.size())) break;
|
||||
const auto& e = entries[idx];
|
||||
int ry = listY + i * rowH;
|
||||
bool isSel = (idx == blockSelected);
|
||||
XSetForeground(dpy, gc, isSel ? pt.spec.selBg : pt.spec.bgMain);
|
||||
XFillRectangle(dpy, pix, gc, 0, ry, kWinWidth, rowH - 1);
|
||||
std::string cname = book.resolveName(e.number);
|
||||
std::string topLine = cname.empty() ? e.number : cname;
|
||||
std::string subLine =
|
||||
cname.empty() ? e.label : (e.number + " (" + e.label + ")");
|
||||
drawPango(layoutIcon, &colRed, 8, ry + 1, 20, kIconBan);
|
||||
drawPango(layoutMain, &colWhite, 28, ry + 1, 20, topLine, 204);
|
||||
drawPango(layoutSmall, isSel ? &colLight : &colMuted, 28, ry + 20, 16,
|
||||
subLine, 204);
|
||||
}
|
||||
}
|
||||
drawSoftkeyBar("Add New", entries.empty() ? "" : "Unblock", "Back");
|
||||
|
||||
} else if (mode == ViewMode::AddBlock) {
|
||||
XSetForeground(dpy, gc, pt.spec.bgHeader);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 34);
|
||||
drawPango(layoutIcon, &colRed, 8, 0, 34, kIconBan);
|
||||
drawPango(layoutMain, &colWhite, 30, 0, 34, "Block Phone Number", 200);
|
||||
|
||||
drawPango(layoutSmall, &colMuted, 12, 54, 20,
|
||||
"Enter number or prefix (e.g. +1800*):", 216);
|
||||
XSetForeground(dpy, gc, pt.spec.bgInputFocus);
|
||||
XFillRectangle(dpy, pix, gc, 12, 80, kWinWidth - 24, 34);
|
||||
XSetForeground(dpy, gc, pt.spec.accentAlt);
|
||||
XDrawRectangle(dpy, pix, gc, 12, 80, kWinWidth - 25, 33);
|
||||
std::string disp = addBlockInput + "|";
|
||||
drawPango(layoutMain, &colWhite, 18, 83, 28, disp, kWinWidth - 36);
|
||||
drawPango(layoutSmall, &colMuted, 12, 124, 20,
|
||||
"0-9 / * / #: Digits #: + prefix", 216);
|
||||
drawPango(layoutSmall, &colMuted, 12, 146, 20,
|
||||
"BackSpace/Left: Delete digit", 216);
|
||||
drawSoftkeyBar("Block", "Save", "Cancel");
|
||||
|
||||
} else if (mode == ViewMode::ConfirmDelete) {
|
||||
XSetForeground(dpy, gc, pt.spec.bgHeader);
|
||||
XFillRectangle(dpy, pix, gc, 0, 0, kWinWidth, 36);
|
||||
@@ -997,6 +1142,49 @@ int runSmsApp(int argc, char** argv) {
|
||||
} else if (optId == 7 && !threadMessages.empty()) {
|
||||
std::string body = threadMessages[selectedMsg].text;
|
||||
openComposeView("", body);
|
||||
} else if (optId == 8) {
|
||||
std::string target = (prevMode == ViewMode::ThreadView)
|
||||
? activeNumber
|
||||
: (!filteredThreads.empty()
|
||||
? filteredThreads[selectedThread].number
|
||||
: "");
|
||||
if (!target.empty()) {
|
||||
if (blockList.isBlocked(target) || viewingSpamFolder) {
|
||||
blockList.unblockNumber(target);
|
||||
moveSmsThreadBetweenDirs(target, getBlockDirPath(),
|
||||
getModemLogDirPath());
|
||||
std::lock_guard<std::mutex> lk(sendMutex);
|
||||
statusBanner = "Unblocked & restored " + target;
|
||||
statusBannerUntilMs = nowMs() + 2000;
|
||||
} else {
|
||||
std::string cname = book.resolveName(target);
|
||||
blockList.blockNumber(target, cname.empty() ? "SMS Spam" : cname);
|
||||
moveSmsThreadBetweenDirs(target, getModemLogDirPath(),
|
||||
getBlockDirPath());
|
||||
std::lock_guard<std::mutex> lk(sendMutex);
|
||||
statusBanner = "Blocked " + target;
|
||||
statusBannerUntilMs = nowMs() + 2000;
|
||||
}
|
||||
reloadAllData(true);
|
||||
}
|
||||
mode = ViewMode::Threads;
|
||||
} else if (optId == 9) {
|
||||
viewingSpamFolder = !viewingSpamFolder;
|
||||
selectedThread = 0;
|
||||
reloadAllData(true);
|
||||
mode = ViewMode::Threads;
|
||||
} else if (optId == 10) {
|
||||
blockList.load();
|
||||
blockSelected = 0;
|
||||
mode = ViewMode::Blocklist;
|
||||
} else if (optId == 11) {
|
||||
std::string target = (prevMode == ViewMode::ThreadView)
|
||||
? activeNumber
|
||||
: (!filteredThreads.empty()
|
||||
? filteredThreads[selectedThread].number
|
||||
: "");
|
||||
addBlockInput = target;
|
||||
mode = ViewMode::AddBlock;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1010,6 +1198,10 @@ int runSmsApp(int argc, char** argv) {
|
||||
if (!searchQuery.empty()) {
|
||||
popUtf8Char(searchQuery);
|
||||
updateFilter();
|
||||
} else if (viewingSpamFolder) {
|
||||
viewingSpamFolder = false;
|
||||
selectedThread = 0;
|
||||
updateFilter();
|
||||
} else {
|
||||
hideWindow();
|
||||
return;
|
||||
@@ -1066,10 +1258,40 @@ int runSmsApp(int argc, char** argv) {
|
||||
} else {
|
||||
mode = prevMode;
|
||||
}
|
||||
} else if (mode == ViewMode::Blocklist) {
|
||||
if (isLeft) {
|
||||
addBlockInput.clear();
|
||||
mode = ViewMode::AddBlock;
|
||||
} else {
|
||||
reloadAllData(true);
|
||||
mode = prevMode;
|
||||
}
|
||||
} else if (mode == ViewMode::AddBlock) {
|
||||
if (isLeft) {
|
||||
std::string num = trimStr(addBlockInput);
|
||||
if (!normalizePhone(num).empty()) {
|
||||
blockList.blockNumber(num, "SMS Blocklist");
|
||||
moveSmsThreadBetweenDirs(num, getModemLogDirPath(),
|
||||
getBlockDirPath());
|
||||
reloadAllData(true);
|
||||
std::lock_guard<std::mutex> lk(sendMutex);
|
||||
statusBanner = "Blocked " + num;
|
||||
statusBannerUntilMs = nowMs() + 2000;
|
||||
}
|
||||
mode = ViewMode::Blocklist;
|
||||
} else {
|
||||
if (!addBlockInput.empty()) {
|
||||
popUtf8Char(addBlockInput);
|
||||
} else {
|
||||
mode = ViewMode::Blocklist;
|
||||
}
|
||||
}
|
||||
} else if (mode == ViewMode::ConfirmDelete) {
|
||||
if (isLeft) {
|
||||
if (confirmSelected == 1) {
|
||||
deleteSmsThread(activeNumber);
|
||||
deleteSmsThread(activeNumber, viewingSpamFolder
|
||||
? getBlockDirPath()
|
||||
: getModemLogDirPath());
|
||||
reloadAllData(true);
|
||||
mode = ViewMode::Threads;
|
||||
} else {
|
||||
@@ -1189,7 +1411,13 @@ int runSmsApp(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (mode == ViewMode::Threads) {
|
||||
if (sym == XK_Up || sym == XK_KP_Up) {
|
||||
if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Right ||
|
||||
sym == XK_KP_Right) {
|
||||
viewingSpamFolder = !viewingSpamFolder;
|
||||
selectedThread = 0;
|
||||
updateFilter();
|
||||
drawUI();
|
||||
} else if (sym == XK_Up || sym == XK_KP_Up) {
|
||||
if (!filteredThreads.empty()) {
|
||||
selectedThread =
|
||||
(selectedThread - 1 + static_cast<int>(filteredThreads.size())) %
|
||||
@@ -1346,6 +1574,57 @@ int runSmsApp(int argc, char** argv) {
|
||||
} else if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||
handleSoftkeyAction(true);
|
||||
}
|
||||
} else if (mode == ViewMode::Blocklist) {
|
||||
if (sym == XK_Up || sym == XK_KP_Up) {
|
||||
if (!blockList.entries().empty()) {
|
||||
int n = static_cast<int>(blockList.entries().size());
|
||||
blockSelected = (blockSelected + n - 1) % n;
|
||||
drawUI();
|
||||
}
|
||||
} else if (sym == XK_Down || sym == XK_KP_Down) {
|
||||
if (!blockList.entries().empty()) {
|
||||
int n = static_cast<int>(blockList.entries().size());
|
||||
blockSelected = (blockSelected + 1) % n;
|
||||
drawUI();
|
||||
}
|
||||
} else if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||
if (!blockList.entries().empty() && blockSelected >= 0 &&
|
||||
blockSelected < static_cast<int>(blockList.entries().size())) {
|
||||
std::string num = blockList.entries()[blockSelected].number;
|
||||
blockList.unblockNumber(num);
|
||||
moveSmsThreadBetweenDirs(num, getBlockDirPath(),
|
||||
getModemLogDirPath());
|
||||
reloadAllData(true);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(sendMutex);
|
||||
statusBanner = "Unblocked " + num;
|
||||
statusBannerUntilMs = nowMs() + 1800;
|
||||
}
|
||||
drawUI();
|
||||
}
|
||||
}
|
||||
} else if (mode == ViewMode::AddBlock) {
|
||||
if (sym == XK_Left || sym == XK_KP_Left) {
|
||||
if (!addBlockInput.empty()) {
|
||||
popUtf8Char(addBlockInput);
|
||||
drawUI();
|
||||
}
|
||||
} else if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||
handleSoftkeyAction(true);
|
||||
} else {
|
||||
char d = mapDialKey(sym, kc, ev.xkey.state);
|
||||
if (d == '#') {
|
||||
if (addBlockInput.empty()) {
|
||||
addBlockInput.push_back('+');
|
||||
} else {
|
||||
addBlockInput.push_back('#');
|
||||
}
|
||||
drawUI();
|
||||
} else if (d != '\0') {
|
||||
addBlockInput.push_back(d);
|
||||
drawUI();
|
||||
}
|
||||
}
|
||||
} else if (mode == ViewMode::ConfirmDelete) {
|
||||
if (sym == XK_Up || sym == XK_Down || sym == XK_KP_Up ||
|
||||
sym == XK_KP_Down) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef ARRAY_H
|
||||
#define ARRAY_H
|
||||
|
||||
#include <cstddef>
|
||||
#include "uncopyable.h"
|
||||
|
||||
template<typename T>
|
||||
class Array : Uncopyable {
|
||||
T *a;
|
||||
std::size_t sz;
|
||||
|
||||
public:
|
||||
explicit Array(const std::size_t size = 0) : a(size ? new T[size] : 0), sz(size) {}
|
||||
~Array() { delete []a; }
|
||||
void reset(const std::size_t size = 0) { delete []a; a = size ? new T[size] : 0; sz = size; }
|
||||
std::size_t size() const { return sz; }
|
||||
T * get() const { return a; }
|
||||
operator T*() const { return a; }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2009 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef UNCOPYABLE_H
|
||||
#define UNCOPYABLE_H
|
||||
|
||||
class Uncopyable {
|
||||
Uncopyable(const Uncopyable&);
|
||||
Uncopyable& operator=(const Uncopyable&);
|
||||
public:
|
||||
Uncopyable() {}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef GAMBATTE_H
|
||||
#define GAMBATTE_H
|
||||
|
||||
#include "inputgetter.h"
|
||||
#ifdef HAVE_NETWORK
|
||||
#include "serial_io.h"
|
||||
#endif
|
||||
#include "gbint.h"
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
|
||||
namespace gambatte {
|
||||
#if defined(VIDEO_RGB565) || defined(VIDEO_ABGR1555)
|
||||
typedef uint16_t video_pixel_t;
|
||||
#else
|
||||
typedef uint_least32_t video_pixel_t;
|
||||
#endif
|
||||
enum { BG_PALETTE = 0, SP1_PALETTE = 1, SP2_PALETTE = 2 };
|
||||
|
||||
class GB {
|
||||
public:
|
||||
GB();
|
||||
~GB();
|
||||
|
||||
enum LoadFlag {
|
||||
FORCE_DMG = 1, /**< Treat the ROM as not having CGB support regardless of what its header advertises. */
|
||||
GBA_CGB = 2, /**< Use GBA intial CPU register values when in CGB mode. */
|
||||
MULTICART_COMPAT = 4, /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */
|
||||
FORCE_CGB = 8
|
||||
};
|
||||
|
||||
int load(const void *romdata, unsigned size, unsigned flags = 0);
|
||||
|
||||
/** Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer,
|
||||
* or until a video frame has been drawn.
|
||||
*
|
||||
* There are 35112 stereo sound samples in a video frame.
|
||||
* May run for uptil 2064 stereo samples too long.
|
||||
* EDIT: Due to internal emulator bugs, may in fact run for
|
||||
* an arbitrary number of samples...
|
||||
* A stereo sample consists of two native endian 2s complement 16-bit PCM samples,
|
||||
* with the left sample preceding the right one. Usually casting soundBuf to/from
|
||||
* short* is OK and recommended. The reason for not using a short* in the interface
|
||||
* is to avoid implementation-defined behaviour without compromising performance.
|
||||
*
|
||||
* Returns early when a new video frame has finished drawing in the video buffer,
|
||||
* such that the caller may update the video output before the frame is overwritten.
|
||||
* The return value indicates whether a new video frame has been drawn, and the
|
||||
* exact time (in number of samples) at which it was drawn.
|
||||
*
|
||||
* @param videoBuf 160x144 RGB32 (native endian) video frame buffer or 0
|
||||
* @param pitch distance in number of pixels (not bytes) from the start of one line to the next in videoBuf.
|
||||
* @param soundBuf buffer with space >= samples + 2064
|
||||
* @param soundBufSize actual size of soundBuf buffer
|
||||
* @param samples in: number of stereo samples to produce, out: actual number of samples produced
|
||||
* @return sample number at which the video frame was produced. -1 means no frame was produced.
|
||||
*/
|
||||
long runFor(gambatte::video_pixel_t *videoBuf, int pitch,
|
||||
gambatte::uint_least32_t *soundBuf, std::size_t soundBufSize, unsigned &samples);
|
||||
|
||||
/** Reset to initial state.
|
||||
* Equivalent to reloading a ROM image, or turning a Game Boy Color off and on again.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/** @param palNum 0 <= palNum < 3. One of BG_PALETTE, SP1_PALETTE and SP2_PALETTE.
|
||||
* @param colorNum 0 <= colorNum < 4
|
||||
*/
|
||||
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32);
|
||||
|
||||
/** Sets the callback used for getting input state. */
|
||||
void setInputGetter(InputGetter *getInput);
|
||||
|
||||
/** Sets the callback used for getting the bootloader data. */
|
||||
void setBootloaderGetter(bool (*getter)(void *userdata, bool isgbc, uint8_t *data, uint32_t buf_size));
|
||||
|
||||
#ifdef HAVE_NETWORK
|
||||
/** Sets the callback used for transferring serial data. */
|
||||
void setSerialIO(SerialIO *serial_io);
|
||||
#endif
|
||||
|
||||
/** Sets the directory used for storing save data. The default is the same directory as the ROM Image file. */
|
||||
void setSaveDir(const std::string &sdir);
|
||||
|
||||
void *savedata_ptr();
|
||||
unsigned savedata_size();
|
||||
void *rtcdata_ptr();
|
||||
unsigned rtcdata_size();
|
||||
|
||||
/** Returns true if the currently loaded ROM image is treated as having CGB support. */
|
||||
bool isCgb() const;
|
||||
|
||||
/** Returns true if a ROM image is loaded. */
|
||||
bool isLoaded() const;
|
||||
|
||||
void saveState(void *data);
|
||||
/* Load a savestate previously produced by saveState. The size
|
||||
* parameter must match the buffer length so the parser can
|
||||
* reject malformed states without reading past the end of the
|
||||
* buffer. Returns true on success. */
|
||||
bool loadState(const void *data, size_t size);
|
||||
size_t stateSize() const;
|
||||
|
||||
void setColorCorrection(bool enable);
|
||||
void setColorCorrectionMode(unsigned colorCorrectionMode);
|
||||
void setColorCorrectionBrightness(float colorCorrectionBrightness);
|
||||
void setDarkFilterLevel(unsigned darkFilterLevel);
|
||||
video_pixel_t gbcToRgb32(const unsigned bgr15);
|
||||
|
||||
/** Set Game Genie codes to apply to currently loaded ROM image. Cleared on ROM load.
|
||||
* @param codes Game Genie codes in format HHH-HHH-HHH;HHH-HHH-HHH;... where H is [0-9]|[A-F]
|
||||
*/
|
||||
void setGameGenie(const std::string &codes);
|
||||
|
||||
/** Set Game Shark codes to apply to currently loaded ROM image. Cleared on ROM load.
|
||||
* @param codes Game Shark codes in format 01HHHHHH;01HHHHHH;... where H is [0-9]|[A-F]
|
||||
*/
|
||||
void setGameShark(const std::string &codes);
|
||||
|
||||
void clearCheats();
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
void *vram_ptr() const;
|
||||
void *rambank0_ptr() const;
|
||||
void *rambank1_ptr() const;
|
||||
void *rambank2_ptr() const;
|
||||
void *bankedram_ptr() const;
|
||||
void *rombank0_ptr() const;
|
||||
void *rombank1_ptr() const;
|
||||
void *zeropage_ptr() const;
|
||||
void *oamram_ptr() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct Priv;
|
||||
Priv *const p_;
|
||||
|
||||
void loadState(const std::string &filepath, bool osdMessage);
|
||||
GB(const GB &);
|
||||
GB & operator=(const GB &);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef GAMBATTE_LOG_H
|
||||
#define GAMBATTE_LOG_H
|
||||
|
||||
enum retro_log_level {
|
||||
RETRO_LOG_DEBUG = 0,
|
||||
RETRO_LOG_INFO,
|
||||
RETRO_LOG_WARN,
|
||||
RETRO_LOG_ERROR
|
||||
};
|
||||
|
||||
static inline void gambatte_log(enum retro_log_level level, const char *format, ...) {
|
||||
(void)level;
|
||||
(void)format;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef GAMBATTE_INT_H
|
||||
#define GAMBATTE_INT_H
|
||||
|
||||
#ifdef HAVE_CSTDINT
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace gambatte {
|
||||
using std::uint_least32_t;
|
||||
using std::uint_least16_t;
|
||||
}
|
||||
|
||||
#elif defined(HAVE_STDINT_H)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace gambatte {
|
||||
using ::uint_least32_t;
|
||||
using ::uint_least16_t;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
namespace gambatte {
|
||||
#ifdef CHAR_LEAST_32
|
||||
typedef unsigned char uint_least32_t;
|
||||
#elif defined(SHORT_LEAST_32)
|
||||
typedef unsigned short uint_least32_t;
|
||||
#elif defined(INT_LEAST_32)
|
||||
typedef unsigned uint_least32_t;
|
||||
#else
|
||||
typedef unsigned long uint_least32_t;
|
||||
#endif
|
||||
|
||||
#ifdef CHAR_LEAST_16
|
||||
typedef unsigned char uint_least16_t;
|
||||
#else
|
||||
typedef unsigned short uint_least16_t;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef GAMBATTE_INPUTGETTER_H
|
||||
#define GAMBATTE_INPUTGETTER_H
|
||||
|
||||
namespace gambatte {
|
||||
class InputGetter {
|
||||
public:
|
||||
enum { A = 0x01, B = 0x02, SELECT = 0x04, START = 0x08, RIGHT = 0x10, LEFT = 0x20, UP = 0x40, DOWN = 0x80 };
|
||||
virtual ~InputGetter() {};
|
||||
|
||||
/** @return A|B|SELECT|START|RIGHT|LEFT|UP|DOWN if those buttons are pressed. */
|
||||
virtual unsigned operator()() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,113 @@
|
||||
#include <stdint.h>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "bootloader.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Bootloader::Bootloader()
|
||||
: addrspace_start(NULL)
|
||||
, bootloadersize(0)
|
||||
, has_called_FF50(false)
|
||||
, get_raw_bootloader_data(NULL)
|
||||
, using_bootloader(false)
|
||||
{
|
||||
/* bootromswapspace and rombackup are intentionally left
|
||||
* uninitialized for performance (0x900 bytes each); they
|
||||
* are written before any read in load(). All scalar members
|
||||
* are now initialized so observers (e.g. choosebank() called
|
||||
* before a successful load) cannot read garbage. */
|
||||
}
|
||||
|
||||
void Bootloader::patch_gbc_to_gba_mode() {
|
||||
/*moves one jump over another and puts ld b,0x01 into the original position*/
|
||||
uint16_t patchloc = 0xF2;
|
||||
uint8_t patch[0x7] = {0xCD,0xD0,0x05/*<-call systemsetup*/,0x06,0x01/*<-ld b,0x1*/,0x00/*<-nop*/,0x00/*<-nop*/};
|
||||
std::memcpy(bootromswapspace + patchloc, patch, 0x7);
|
||||
}
|
||||
|
||||
void Bootloader::load(bool isgbc, bool isgba) {
|
||||
if (get_raw_bootloader_data == NULL) {
|
||||
using_bootloader = false;
|
||||
return;
|
||||
}
|
||||
|
||||
//the gba only uses the gbc bios
|
||||
if (isgba)
|
||||
isgbc = true;
|
||||
|
||||
bool bootloaderavail = get_raw_bootloader_data((void*)this, isgbc, bootromswapspace, 0x900/*buf_size*/);
|
||||
if (!bootloaderavail) {
|
||||
using_bootloader = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isgbc)
|
||||
bootloadersize = 0x900;
|
||||
else
|
||||
bootloadersize = 0x100;
|
||||
|
||||
if (isgba)//patch bootloader to fake gba mode
|
||||
patch_gbc_to_gba_mode();
|
||||
|
||||
//backup rom segment that is shared with bootloader
|
||||
std::memcpy(rombackup, (uint8_t*)addrspace_start, bootloadersize);
|
||||
|
||||
//put back cartridge data in a 256 byte window of the bios that is not mapped(GBC only)
|
||||
if (isgbc)
|
||||
std::memcpy(bootromswapspace + 0x100, rombackup + 0x100, 0x100);
|
||||
|
||||
//put bootloader in main memory
|
||||
std::memcpy((uint8_t*)addrspace_start, bootromswapspace, bootloadersize);
|
||||
|
||||
using_bootloader = true;
|
||||
}
|
||||
|
||||
void Bootloader::reset() {
|
||||
bootloadersize = 0;
|
||||
has_called_FF50 = false;
|
||||
addrspace_start = NULL;
|
||||
using_bootloader = false;
|
||||
}
|
||||
|
||||
void Bootloader::set_bootloader_getter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size)) {
|
||||
get_raw_bootloader_data = getter;
|
||||
}
|
||||
|
||||
void Bootloader::set_address_space_start(void* start) {
|
||||
addrspace_start = start;
|
||||
}
|
||||
|
||||
void Bootloader::choosebank(bool inbootloader) {
|
||||
//inbootloader = (state.mem.ioamhram.get()[0x150] != 0xFF);//do not uncomment this is just for reference
|
||||
if (using_bootloader) {
|
||||
|
||||
//switching from game to bootloader with savestate
|
||||
if (inbootloader && has_called_FF50)
|
||||
uncall_FF50();
|
||||
|
||||
//switching from bootloader to game with savestate
|
||||
else if (!inbootloader && !has_called_FF50)
|
||||
call_FF50();
|
||||
|
||||
//switching from game to game or bootloader to bootloader needs no changes
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Bootloader::call_FF50() {
|
||||
if (!has_called_FF50 && using_bootloader) {
|
||||
//put rom back in main memory when bootloader has finished
|
||||
std::memcpy((uint8_t*)addrspace_start, rombackup, bootloadersize);
|
||||
has_called_FF50 = true;
|
||||
}
|
||||
}
|
||||
|
||||
//this is a developer function only,a real gameboy can never undo calling 0xFF50,this function is for savestate functionality
|
||||
void Bootloader::uncall_FF50() {
|
||||
std::memcpy((uint8_t*)addrspace_start, bootromswapspace, bootloadersize);
|
||||
has_called_FF50 = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef BOOTLOADER_H
|
||||
#define BOOTLOADER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class Bootloader {
|
||||
|
||||
private:
|
||||
uint8_t bootromswapspace[0x900];
|
||||
uint8_t rombackup[0x900];
|
||||
void* addrspace_start;
|
||||
unsigned int bootloadersize;
|
||||
bool has_called_FF50;
|
||||
|
||||
//Set this to NULL or return false if you want to ignore bootloaders completely
|
||||
//(initial value is NULL and so the bootloader is disabled by default)
|
||||
bool (*get_raw_bootloader_data)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size);
|
||||
|
||||
void patch_gbc_to_gba_mode();
|
||||
void uncall_FF50();
|
||||
|
||||
public:
|
||||
bool using_bootloader;
|
||||
|
||||
Bootloader();
|
||||
void load(bool isgbc,bool isgba);
|
||||
void reset();
|
||||
|
||||
void set_bootloader_getter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t buf_size));
|
||||
|
||||
void set_address_space_start(void* start);
|
||||
|
||||
void choosebank(bool inbootloader);
|
||||
|
||||
void call_FF50();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef COUNTERDEF_H
|
||||
#define COUNTERDEF_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
enum { disabled_time = 0xfffffffful };
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef CPU_H
|
||||
#define CPU_H
|
||||
|
||||
#include "gambatte.h"
|
||||
#include "gambatte-memory.h"
|
||||
#include "savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
#ifdef HAVE_NETWORK
|
||||
class SerialIO;
|
||||
#endif
|
||||
|
||||
class CPU {
|
||||
public:
|
||||
CPU();
|
||||
long runFor(unsigned long cycles);
|
||||
void setStatePtrs(SaveState &state);
|
||||
void saveState(SaveState &state);
|
||||
void loadState(SaveState const &state);
|
||||
#if 0
|
||||
void loadSavedata() { mem_.loadSavedata(); }
|
||||
void saveSavedata() { mem_.saveSavedata(); }
|
||||
#endif
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
void *savedata_ptr() { return mem_.savedata_ptr(); }
|
||||
unsigned savedata_size() { return mem_.savedata_size(); }
|
||||
void *rtcdata_ptr() { return mem_.rtcdata_ptr(); }
|
||||
unsigned rtcdata_size() { return mem_.rtcdata_size(); }
|
||||
void clearCheats() { mem_.clearCheats(); }
|
||||
void *vram_ptr() const { return mem_.vram_ptr(); }
|
||||
void *rambank0_ptr() const { return mem_.rambank0_ptr(); }
|
||||
void *rambank1_ptr() const { return mem_.rambank1_ptr(); }
|
||||
void *rambank2_ptr() const { return mem_.rambank2_ptr(); }
|
||||
void *bankedram_ptr() const { return mem_.bankedram_ptr(); }
|
||||
void *rombank0_ptr() const { return mem_.rombank0_ptr(); }
|
||||
void *rombank1_ptr() const { return mem_.rombank1_ptr(); }
|
||||
void *zeropage_ptr() const { return mem_.zeropage_ptr(); }
|
||||
void *oamram_ptr() const { return mem_.oamram_ptr(); }
|
||||
#endif
|
||||
|
||||
void setVideoBuffer(video_pixel_t *videoBuf, std::ptrdiff_t pitch) {
|
||||
mem_.setVideoBuffer(videoBuf, pitch);
|
||||
}
|
||||
|
||||
void setInputGetter(InputGetter *getInput) {
|
||||
mem_.setInputGetter(getInput);
|
||||
}
|
||||
#ifdef HAVE_NETWORK
|
||||
void setSerialIO(SerialIO *serial_io) {
|
||||
mem_.setSerialIO(serial_io);
|
||||
}
|
||||
#endif
|
||||
|
||||
void setSaveDir(std::string const &sdir) {
|
||||
mem_.setSaveDir(sdir);
|
||||
}
|
||||
|
||||
std::string const saveBasePath() const {
|
||||
return mem_.saveBasePath();
|
||||
}
|
||||
|
||||
int load(const void *romdata, unsigned int romsize, unsigned int forceModel, bool multicartCompat) {
|
||||
return mem_.loadROM(romdata, romsize, forceModel, multicartCompat);
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool loaded() const { return mem_.loaded(); }
|
||||
#endif
|
||||
void setSoundBuffer(uint_least32_t *buf, std::size_t size) { mem_.setSoundBuffer(buf, size); }
|
||||
std::size_t fillSoundBuffer() { return mem_.fillSoundBuffer(cycleCounter_); }
|
||||
bool isCgb() const { return mem_.isCgb(); }
|
||||
|
||||
void setDmgPaletteColor(int palNum, int colorNum, unsigned long rgb32) {
|
||||
mem_.setDmgPaletteColor(palNum, colorNum, rgb32);
|
||||
}
|
||||
|
||||
void setGameGenie(std::string const &codes) { mem_.setGameGenie(codes); }
|
||||
void setGameShark(std::string const &codes) { mem_.setGameShark(codes); }
|
||||
|
||||
Memory mem_;
|
||||
private:
|
||||
unsigned long cycleCounter_;
|
||||
unsigned short pc_;
|
||||
unsigned short sp;
|
||||
unsigned hf1, hf2, zf, cf;
|
||||
unsigned char a_, b, c, d, e, /*f,*/ h, l;
|
||||
bool skip_;
|
||||
|
||||
void process(unsigned long cycles);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef MEMORY_H
|
||||
#define MEMORY_H
|
||||
|
||||
#include "mem/cartridge.h"
|
||||
#include "interrupter.h"
|
||||
#include "bootloader.h"
|
||||
#include "sound.h"
|
||||
#include "tima.h"
|
||||
#include "video.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class InputGetter;
|
||||
#ifdef HAVE_NETWORK
|
||||
class SerialIO;
|
||||
#endif
|
||||
|
||||
class Memory {
|
||||
public:
|
||||
Bootloader bootloader;
|
||||
|
||||
explicit Memory(Interrupter const &interrupter);
|
||||
bool loaded() const { return cart_.loaded(); }
|
||||
void setStatePtrs(SaveState &state);
|
||||
unsigned long saveState(SaveState &state, unsigned long cc);
|
||||
void loadState(SaveState const &state);
|
||||
#ifdef __LIBRETRO__
|
||||
void *savedata_ptr() { return cart_.savedata_ptr(); }
|
||||
unsigned savedata_size() { return cart_.savedata_size(); }
|
||||
void *rtcdata_ptr() { return cart_.rtcdata_ptr(); }
|
||||
unsigned rtcdata_size() { return cart_.rtcdata_size(); }
|
||||
void display_setColorCorrection(bool enable) { lcd_.setColorCorrection(enable); }
|
||||
void display_setColorCorrectionMode(unsigned colorCorrectionMode) { lcd_.setColorCorrectionMode(colorCorrectionMode); }
|
||||
void display_setColorCorrectionBrightness(float colorCorrectionBrightness) { lcd_.setColorCorrectionBrightness(colorCorrectionBrightness); }
|
||||
void display_setDarkFilterLevel(unsigned darkFilterLevel) { lcd_.setDarkFilterLevel(darkFilterLevel); }
|
||||
video_pixel_t display_gbcToRgb32(const unsigned bgr15) { return lcd_.gbcToRgb32(bgr15); }
|
||||
void clearCheats() { cart_.clearCheats(); interrupter_.clearCheats(); }
|
||||
void *vram_ptr() const { return cart_.vramdata(); }
|
||||
void *rambank0_ptr() const { return cart_.wramdata(0); }
|
||||
void *rambank1_ptr() const { return cart_.wramdata(0) + 0x1000; }
|
||||
void *rambank2_ptr() const { return cart_.wramdata(0) + 0x2000; }
|
||||
void *bankedram_ptr() const { return cart_.wramdata(1); }
|
||||
void *rombank0_ptr() const { return cart_.romdata(0); }
|
||||
void *rombank1_ptr() const { return cart_.romdata(0) + 0x4000; }
|
||||
void *zeropage_ptr() const { return (void*)(ioamhram_ + 0x0180); }
|
||||
void *oamram_ptr() const { return (void*)ioamhram_; }
|
||||
#else
|
||||
void loadSavedata() { cart_.loadSavedata(); }
|
||||
void saveSavedata() { cart_.saveSavedata(); }
|
||||
#endif
|
||||
std::string const saveBasePath() const { return cart_.saveBasePath(); }
|
||||
|
||||
unsigned long stop(unsigned long cycleCounter);
|
||||
bool isCgb() const { return lcd_.isCgb(); }
|
||||
bool ime() const { return intreq_.ime(); }
|
||||
bool halted() const { return intreq_.halted(); }
|
||||
unsigned long nextEventTime() const { return intreq_.minEventTime(); }
|
||||
bool isActive() const { return intreq_.eventTime(intevent_end) != disabled_time; }
|
||||
|
||||
long cyclesSinceBlit(unsigned long cc) const
|
||||
{
|
||||
if (cc < intreq_.eventTime(intevent_blit))
|
||||
return -1;
|
||||
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
|
||||
return (cc - intreq_.eventTime(intevent_blit)) >> is_doublespeed;
|
||||
}
|
||||
|
||||
void halt() { intreq_.halt(); }
|
||||
void ei(unsigned long cycleCounter) { if (!ime()) { intreq_.ei(cycleCounter); } }
|
||||
void di() { intreq_.di(); }
|
||||
|
||||
unsigned ff_read(unsigned p, unsigned long cc) {
|
||||
return p < 0x80 ? nontrivial_ff_read(p, cc) : ioamhram_[p + 0x100];
|
||||
}
|
||||
|
||||
unsigned read(unsigned p, unsigned long cc) {
|
||||
/* Sachen MMC1 carts need to count CPU reads of 0x0100..0x01FF
|
||||
* so the mapper can leave its boot-time locked state
|
||||
* between the bootstrap's logo-display pass (48 cart
|
||||
* reads of 0x0104..0x0133) and its logo-verify pass
|
||||
* (another 48 reads of the same range). The counter
|
||||
* pointer is null for every other cartridge, so the
|
||||
* branch below is a single predictable null-check on
|
||||
* the hot path. The unlock fires AFTER the 48th locked
|
||||
* read has returned its byte, so the display pass sees a
|
||||
* full Sachen-logo sequence and the very next read
|
||||
* (first byte of the verify pass) sees the unlocked
|
||||
* Nintendo bytes. */
|
||||
const unsigned char *const rm = cart_.rmem(p >> 12);
|
||||
const unsigned value = rm ? rm[p] : nontrivial_read(p, cc);
|
||||
if (sachenLockCounter_ && (p & 0xFF00) == 0x0100) {
|
||||
if (*sachenLockCounter_ < 48) {
|
||||
if (++*sachenLockCounter_ == 48)
|
||||
cart_.onSachenUnlock();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void write(unsigned p, unsigned data, unsigned long cc) {
|
||||
if (cart_.wmem(p >> 12)) {
|
||||
cart_.wmem(p >> 12)[p] = data;
|
||||
} else
|
||||
nontrivial_write(p, data, cc);
|
||||
}
|
||||
|
||||
void ff_write(unsigned p, unsigned data, unsigned long cc) {
|
||||
if (p - 0x80u < 0x7Fu) {
|
||||
ioamhram_[p + 0x100] = data;
|
||||
} else
|
||||
nontrivial_ff_write(p, data, cc);
|
||||
}
|
||||
#ifdef HAVE_NETWORK
|
||||
void startSerialTransfer(unsigned long cycleCounter, unsigned char data, bool fastCgb);
|
||||
#endif
|
||||
|
||||
unsigned long event(unsigned long cycleCounter);
|
||||
unsigned long resetCounters(unsigned long cycleCounter);
|
||||
void setSaveDir(std::string const &dir) { cart_.setSaveDir(dir); }
|
||||
void setInputGetter(InputGetter *getInput) { getInput_ = getInput; }
|
||||
#ifdef HAVE_NETWORK
|
||||
void setSerialIO(SerialIO* serial_io) { serial_io_ = serial_io; }
|
||||
#endif
|
||||
void setEndtime(unsigned long cc, unsigned long inc);
|
||||
void setSoundBuffer(uint_least32_t *buf, std::size_t size) { psg_.setBuffer(buf, size); }
|
||||
std::size_t fillSoundBuffer(unsigned long cc);
|
||||
|
||||
void setVideoBuffer(video_pixel_t *videoBuf, std::ptrdiff_t pitch) {
|
||||
lcd_.setVideoBuffer(videoBuf, pitch);
|
||||
}
|
||||
|
||||
void setDmgPaletteColor(int palNum, int colorNum, unsigned long rgb32) {
|
||||
lcd_.setDmgPaletteColor(palNum, colorNum, rgb32);
|
||||
}
|
||||
|
||||
void setGameGenie(std::string const &codes) { cart_.setGameGenie(codes); }
|
||||
void setGameShark(std::string const &codes) { interrupter_.setGameShark(codes); }
|
||||
#ifdef HAVE_NETWORK
|
||||
void checkSerial(unsigned long cc);
|
||||
#endif
|
||||
void updateInput();
|
||||
|
||||
int loadROM(const void *romdata, unsigned int romsize, unsigned int forceModel, const bool multicartCompat);
|
||||
|
||||
/* Forwarders for unlicensed-mapper hooks. Currently only Sachen
|
||||
* MMC1 carts use them; for every other cartridge type these are
|
||||
* no-ops and isSachen() returns false. Called by
|
||||
* GB::Priv::full_init after the bootloader has been installed
|
||||
* (or skipped). */
|
||||
bool isSachen() const { return cart_.isSachen(); }
|
||||
void sachenLockSetup(bool bootloaderUsed) {
|
||||
cart_.sachenLockSetup(bootloaderUsed);
|
||||
/* Pick up the MBC's lock-counter address now that the cart
|
||||
* has decided whether to enter the locked state. For a
|
||||
* Sachen cart, sachenLockCounterPtr() returns a real
|
||||
* address; for any other cart it returns null and the read
|
||||
* fast path stays in single-branch territory. */
|
||||
sachenLockCounter_ = cart_.sachenLockCounterPtr();
|
||||
}
|
||||
void onSachenUnlock() { cart_.onSachenUnlock(); }
|
||||
|
||||
private:
|
||||
Cartridge cart_;
|
||||
unsigned char ioamhram_[0x200];
|
||||
/* Pointer to the Sachen MBC's read counter, or null for any
|
||||
* non-Sachen cart. When non-null, Memory::read bumps it on
|
||||
* every CPU read of 0x0100..0x01FF and signals the cart on
|
||||
* the 48th such read so the mapper can leave its locked
|
||||
* state between the bootstrap's display and verify passes. */
|
||||
unsigned char *sachenLockCounter_;
|
||||
#ifdef HAVE_NETWORK
|
||||
unsigned char serialize_value_;
|
||||
bool serialize_is_fastcgb_;
|
||||
SerialIO *serial_io_;
|
||||
#endif
|
||||
InputGetter *getInput_;
|
||||
unsigned long divLastUpdate_;
|
||||
unsigned long lastOamDmaUpdate_;
|
||||
InterruptRequester intreq_;
|
||||
Tima tima_;
|
||||
LCD lcd_;
|
||||
PSG psg_;
|
||||
Interrupter interrupter_;
|
||||
unsigned short dmaSource_;
|
||||
unsigned short dmaDestination_;
|
||||
unsigned char oamDmaPos_;
|
||||
unsigned char serialCnt_;
|
||||
bool blanklcd_;
|
||||
|
||||
void decEventCycles(IntEventId eventId, unsigned long dec);
|
||||
void oamDmaInitSetup();
|
||||
void updateOamDma(unsigned long cycleCounter);
|
||||
void startOamDma(unsigned long cycleCounter);
|
||||
void endOamDma(unsigned long cycleCounter);
|
||||
unsigned char const * oamDmaSrcPtr() const;
|
||||
unsigned nontrivial_ff_read(unsigned p, unsigned long cycleCounter);
|
||||
unsigned nontrivial_read(unsigned p, unsigned long cycleCounter);
|
||||
void nontrivial_ff_write(unsigned p, unsigned data, unsigned long cycleCounter);
|
||||
void nontrivial_write(unsigned p, unsigned data, unsigned long cycleCounter);
|
||||
void updateSerial(unsigned long cc);
|
||||
void updateTimaIrq(unsigned long cc);
|
||||
void updateIrqs(unsigned long cc);
|
||||
bool isDoubleSpeed() const { return lcd_.isDoubleSpeed(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,243 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "gambatte.h"
|
||||
#include "cpu.h"
|
||||
#include "savestate.h"
|
||||
#include "statesaver.h"
|
||||
#include "initstate.h"
|
||||
#include "bootloader.h"
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
namespace gambatte {
|
||||
struct GB::Priv {
|
||||
CPU cpu;
|
||||
int stateNo;
|
||||
bool gbaCgbMode;
|
||||
|
||||
Priv() : stateNo(1), gbaCgbMode(false) {}
|
||||
|
||||
void full_init(bool clearSram = true);
|
||||
};
|
||||
|
||||
GB::GB() : p_(new Priv) {}
|
||||
|
||||
GB::~GB() {
|
||||
delete p_;
|
||||
}
|
||||
|
||||
long GB::runFor(gambatte::video_pixel_t *const videoBuf, const int pitch,
|
||||
gambatte::uint_least32_t *const soundBuf, std::size_t soundBufSize, unsigned &samples) {
|
||||
|
||||
p_->cpu.setVideoBuffer(videoBuf, pitch);
|
||||
p_->cpu.setSoundBuffer(soundBuf, soundBufSize);
|
||||
const long cyclesSinceBlit = p_->cpu.runFor(samples * 2);
|
||||
samples = p_->cpu.fillSoundBuffer();
|
||||
|
||||
return cyclesSinceBlit < 0 ? cyclesSinceBlit : static_cast<long>(samples) - (cyclesSinceBlit >> 1);
|
||||
}
|
||||
|
||||
void GB::Priv::full_init(bool const clearSram) {
|
||||
SaveState state;
|
||||
|
||||
cpu.setStatePtrs(state);
|
||||
setInitState(state, cpu.isCgb(), gbaCgbMode, clearSram);
|
||||
|
||||
cpu.mem_.bootloader.reset();
|
||||
cpu.mem_.bootloader.set_address_space_start((void*)cpu.rombank0_ptr());
|
||||
cpu.mem_.bootloader.load(cpu.isCgb(), gbaCgbMode);
|
||||
|
||||
if (cpu.mem_.bootloader.using_bootloader) {
|
||||
uint8_t *ioamhram = (uint8_t*)state.mem.ioamhram.get();
|
||||
uint8_t serialctrl = (cpu.isCgb() || gbaCgbMode) ? 0x7C : 0x7E;
|
||||
state.cpu.pc = 0x0000;
|
||||
// the hw registers must be zeroed out to prevent the logo from being garbled
|
||||
std::memset((void*)(ioamhram + 0x100), 0x00, 0x100);
|
||||
//init values taken from SameBoy
|
||||
ioamhram[0x100] = 0xCF;//joypad initial value
|
||||
ioamhram[0x102] = serialctrl;//serialctrl
|
||||
ioamhram[0x148] = 0xFC;//object palette 0
|
||||
ioamhram[0x149] = 0xFC;//object palette 1
|
||||
}
|
||||
|
||||
cpu.loadState(state);
|
||||
|
||||
/* Sachen MMC1 carts need their lock-state overlay installed
|
||||
* (or their VRAM logo injected) AFTER the generic state
|
||||
* restore has finished, so the MBC's loadState handler can't
|
||||
* tear down the overlay based on the default-initialized
|
||||
* SaveState fields. For every non-Sachen cart this is a
|
||||
* no-op; for Sachen carts it also wires the read-count
|
||||
* unlock trigger into the Memory hot path. */
|
||||
cpu.mem_.sachenLockSetup(cpu.mem_.bootloader.using_bootloader);
|
||||
}
|
||||
|
||||
void GB::reset() {
|
||||
/* Battery-backed SRAM and RTC must persist across a soft reset
|
||||
* (matching real-hardware behavior; the cart battery keeps
|
||||
* volatile SRAM alive while the console is power-cycled). */
|
||||
p_->full_init(false);
|
||||
}
|
||||
|
||||
void GB::setInputGetter(InputGetter *getInput) {
|
||||
p_->cpu.setInputGetter(getInput);
|
||||
}
|
||||
|
||||
void GB::setBootloaderGetter(bool (*getter)(void* userdata, bool isgbc, uint8_t* data, uint32_t max_size)) {
|
||||
p_->cpu.mem_.bootloader.set_bootloader_getter(getter);
|
||||
}
|
||||
|
||||
#ifdef HAVE_NETWORK
|
||||
void GB::setSerialIO(SerialIO *serial_io) {
|
||||
p_->cpu.setSerialIO(serial_io);
|
||||
}
|
||||
#endif
|
||||
|
||||
void *GB::savedata_ptr() { return p_->cpu.savedata_ptr(); }
|
||||
unsigned GB::savedata_size() { return p_->cpu.savedata_size(); }
|
||||
void *GB::rtcdata_ptr() { return p_->cpu.rtcdata_ptr(); }
|
||||
unsigned GB::rtcdata_size() { return p_->cpu.rtcdata_size(); }
|
||||
|
||||
int GB::load(const void *romdata, unsigned romsize, const unsigned flags) {
|
||||
const int failed = p_->cpu.load(romdata, romsize, flags & (FORCE_DMG | FORCE_CGB), flags & MULTICART_COMPAT);
|
||||
|
||||
if (!failed) {
|
||||
p_->gbaCgbMode = flags & GBA_CGB;
|
||||
p_->full_init();
|
||||
p_->stateNo = 1;
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
bool GB::isCgb() const {
|
||||
return p_->cpu.isCgb();
|
||||
}
|
||||
|
||||
bool GB::isLoaded() const {
|
||||
/* Previously this unconditionally returned true. The
|
||||
* underlying Cartridge::loaded() reports whether an MBC has
|
||||
* been instantiated, which is a faithful "is a ROM loaded"
|
||||
* signal, so route through to it. */
|
||||
return p_->cpu.mem_.loaded();
|
||||
}
|
||||
|
||||
void GB::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) {
|
||||
p_->cpu.setDmgPaletteColor(palNum, colorNum, rgb32);
|
||||
}
|
||||
|
||||
bool GB::loadState(const void *data, size_t size) {
|
||||
SaveState state;
|
||||
p_->cpu.setStatePtrs(state);
|
||||
|
||||
if (StateSaver::loadState(state, data, size)) {
|
||||
p_->cpu.loadState(state);
|
||||
p_->cpu.mem_.bootloader.choosebank(state.mem.ioamhram.get()[0x150] != 0xFF);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GB::saveState(void *data) {
|
||||
SaveState state;
|
||||
p_->cpu.setStatePtrs(state);
|
||||
p_->cpu.saveState(state);
|
||||
StateSaver::saveState(state, data);
|
||||
}
|
||||
|
||||
size_t GB::stateSize() const {
|
||||
SaveState state;
|
||||
p_->cpu.setStatePtrs(state);
|
||||
p_->cpu.saveState(state);
|
||||
return StateSaver::stateSize(state);
|
||||
}
|
||||
|
||||
void GB::setColorCorrection(bool enable) {
|
||||
p_->cpu.mem_.display_setColorCorrection(enable);
|
||||
}
|
||||
|
||||
void GB::setColorCorrectionMode(unsigned colorCorrectionMode) {
|
||||
p_->cpu.mem_.display_setColorCorrectionMode(colorCorrectionMode);
|
||||
}
|
||||
|
||||
void GB::setColorCorrectionBrightness(float colorCorrectionBrightness) {
|
||||
p_->cpu.mem_.display_setColorCorrectionBrightness(colorCorrectionBrightness);
|
||||
}
|
||||
|
||||
void GB::setDarkFilterLevel(unsigned darkFilterLevel) {
|
||||
p_->cpu.mem_.display_setDarkFilterLevel(darkFilterLevel);
|
||||
}
|
||||
|
||||
video_pixel_t GB::gbcToRgb32(const unsigned bgr15) {
|
||||
return p_->cpu.mem_.display_gbcToRgb32(bgr15);
|
||||
}
|
||||
|
||||
|
||||
void GB::setGameGenie(const std::string &codes) {
|
||||
p_->cpu.setGameGenie(codes);
|
||||
}
|
||||
|
||||
void GB::setGameShark(const std::string &codes) {
|
||||
p_->cpu.setGameShark(codes);
|
||||
}
|
||||
|
||||
void GB::clearCheats() {
|
||||
p_->cpu.clearCheats();
|
||||
}
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
void *GB::vram_ptr() const {
|
||||
return p_->cpu.vram_ptr();
|
||||
}
|
||||
|
||||
void *GB::rambank0_ptr() const {
|
||||
return p_->cpu.rambank0_ptr();
|
||||
}
|
||||
|
||||
void *GB::rambank1_ptr() const {
|
||||
return p_->cpu.rambank1_ptr();
|
||||
}
|
||||
|
||||
void *GB::rambank2_ptr() const {
|
||||
return p_->cpu.rambank2_ptr();
|
||||
}
|
||||
|
||||
void *GB::bankedram_ptr() const {
|
||||
return p_->cpu.bankedram_ptr();
|
||||
}
|
||||
|
||||
void *GB::rombank0_ptr() const {
|
||||
return p_->cpu.rombank0_ptr();
|
||||
}
|
||||
|
||||
void *GB::rombank1_ptr() const {
|
||||
return p_->cpu.rombank1_ptr();
|
||||
}
|
||||
|
||||
void *GB::zeropage_ptr() const {
|
||||
return p_->cpu.zeropage_ptr();
|
||||
}
|
||||
|
||||
void *GB::oamram_ptr() const {
|
||||
return p_->cpu.oamram_ptr();
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aam�s *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef INITSTATE_H
|
||||
#define INITSTATE_H
|
||||
|
||||
namespace gambatte {
|
||||
/* clearSram defaults to true to preserve the historical behavior
|
||||
* for callers that load a fresh cart (uninitialized SRAM should
|
||||
* read as 0xFF). Pass false from a soft-reset path so battery-
|
||||
* backed cartridge RAM survives the reset, matching real
|
||||
* hardware behavior. */
|
||||
void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode, bool clearSram = true);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef INSERTION_SORT_H
|
||||
#define INSERTION_SORT_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
template<typename T, class Less>
|
||||
void insertionSort(T *const start, T *const end, Less less) {
|
||||
if (start >= end)
|
||||
return;
|
||||
|
||||
T *a = start;
|
||||
|
||||
while (++a < end) {
|
||||
const T e = *a;
|
||||
|
||||
T *b = a;
|
||||
|
||||
while (b != start && less(e, *(b - 1))) {
|
||||
*b = *(b - 1);
|
||||
b = b - 1;
|
||||
}
|
||||
|
||||
*b = e;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void insertionSort(T *const start, T *const end) {
|
||||
insertionSort(start, end, std::less<T>());
|
||||
}
|
||||
|
||||
#endif /*INSERTION_SORT_H*/
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "interrupter.h"
|
||||
#include "gambatte-memory.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Interrupter::Interrupter(unsigned short &sp, unsigned short &pc)
|
||||
: sp_(sp)
|
||||
, pc_(pc)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned long Interrupter::interrupt(unsigned const address, unsigned long cc, Memory &memory) {
|
||||
cc += 8;
|
||||
sp_ = (sp_ - 1) & 0xFFFF;
|
||||
memory.write(sp_, pc_ >> 8, cc);
|
||||
cc += 4;
|
||||
sp_ = (sp_ - 1) & 0xFFFF;
|
||||
memory.write(sp_, pc_ & 0xFF, cc);
|
||||
pc_ = address;
|
||||
cc += 8;
|
||||
|
||||
if (address == 0x40 && !gsCodes_.empty())
|
||||
applyVblankCheats(cc, memory);
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
static int asHex(char c) {
|
||||
/* Accept both uppercase and lowercase A-F. The previous
|
||||
* implementation produced silently-wrong values for
|
||||
* lowercase letters, leading to no-op cheats when users
|
||||
* typed codes in lowercase. */
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 0xA;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 0xA;
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
void Interrupter::setGameShark(std::string const &codes) {
|
||||
std::string code;
|
||||
|
||||
for (std::size_t pos = 0; pos < codes.length(); pos += code.length() + 1) {
|
||||
code = codes.substr(pos, codes.find(';', pos) - pos);
|
||||
if (code.length() >= 8) {
|
||||
GsCode gs;
|
||||
gs.type = asHex(code[0]) << 4 | asHex(code[1]);
|
||||
gs.value = (asHex(code[2]) << 4 | asHex(code[3])) & 0xFF;
|
||||
gs.address = ( asHex(code[4]) << 4
|
||||
| asHex(code[5])
|
||||
| asHex(code[6]) << 12
|
||||
| asHex(code[7]) << 8) & 0xFFFF;
|
||||
gsCodes_.push_back(gs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Interrupter::clearCheats() {
|
||||
gsCodes_.clear();
|
||||
}
|
||||
|
||||
void Interrupter::applyVblankCheats(unsigned long const cc, Memory &memory) {
|
||||
for (std::size_t i = 0, size = gsCodes_.size(); i < size; ++i) {
|
||||
if (gsCodes_[i].type == 0x01)
|
||||
memory.write(gsCodes_[i].address, gsCodes_[i].value, cc);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef INTERRUPTER_H
|
||||
#define INTERRUPTER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct GsCode {
|
||||
unsigned short address;
|
||||
unsigned char value;
|
||||
unsigned char type;
|
||||
};
|
||||
|
||||
class Memory;
|
||||
|
||||
class Interrupter {
|
||||
public:
|
||||
Interrupter(unsigned short &sp, unsigned short &pc);
|
||||
unsigned long interrupt(unsigned address, unsigned long cycleCounter, Memory &memory);
|
||||
void setGameShark(std::string const &codes);
|
||||
void clearCheats();
|
||||
|
||||
private:
|
||||
unsigned short &sp_;
|
||||
unsigned short &pc_;
|
||||
std::vector<GsCode> gsCodes_;
|
||||
|
||||
void applyVblankCheats(unsigned long cc, Memory &mem);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "interruptrequester.h"
|
||||
#include "savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
InterruptRequester::InterruptRequester()
|
||||
: eventTimes_(disabled_time)
|
||||
, minIntTime_(0)
|
||||
, ifreg_(0)
|
||||
, iereg_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void InterruptRequester::saveState(SaveState &state) const {
|
||||
state.mem.minIntTime = minIntTime_;
|
||||
state.mem.IME = ime();
|
||||
state.mem.halted = halted();
|
||||
}
|
||||
|
||||
void InterruptRequester::loadState(SaveState const &state) {
|
||||
minIntTime_ = state.mem.minIntTime;
|
||||
ifreg_ = state.mem.ioamhram.get()[0x10F];
|
||||
iereg_ = state.mem.ioamhram.get()[0x1FF] & 0x1F;
|
||||
intFlags_.set(state.mem.IME, state.mem.halted);
|
||||
|
||||
eventTimes_.setValue<intevent_interrupts>(intFlags_.imeOrHalted() && pendingIrqs()
|
||||
? minIntTime_
|
||||
: static_cast<unsigned long>(disabled_time));
|
||||
}
|
||||
|
||||
void InterruptRequester::resetCc(unsigned long oldCc, unsigned long newCc) {
|
||||
minIntTime_ = minIntTime_ < oldCc ? 0 : minIntTime_ - (oldCc - newCc);
|
||||
|
||||
if (eventTimes_.value(intevent_interrupts) != disabled_time)
|
||||
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
|
||||
}
|
||||
|
||||
void InterruptRequester::ei(unsigned long cc) {
|
||||
intFlags_.setIme();
|
||||
minIntTime_ = cc + 1;
|
||||
|
||||
if (pendingIrqs())
|
||||
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
|
||||
}
|
||||
|
||||
void InterruptRequester::di() {
|
||||
intFlags_.unsetIme();
|
||||
|
||||
if (!intFlags_.imeOrHalted())
|
||||
eventTimes_.setValue<intevent_interrupts>(disabled_time);
|
||||
}
|
||||
|
||||
void InterruptRequester::halt() {
|
||||
intFlags_.setHalted();
|
||||
|
||||
if (pendingIrqs())
|
||||
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
|
||||
}
|
||||
|
||||
void InterruptRequester::unhalt() {
|
||||
intFlags_.unsetHalted();
|
||||
|
||||
if (!intFlags_.imeOrHalted())
|
||||
eventTimes_.setValue<intevent_interrupts>(disabled_time);
|
||||
}
|
||||
|
||||
void InterruptRequester::flagIrq(unsigned bit) {
|
||||
ifreg_ |= bit;
|
||||
|
||||
if (intFlags_.imeOrHalted() && pendingIrqs())
|
||||
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
|
||||
}
|
||||
|
||||
void InterruptRequester::ackIrq(unsigned bit) {
|
||||
ifreg_ ^= bit;
|
||||
di();
|
||||
}
|
||||
|
||||
void InterruptRequester::setIereg(unsigned iereg) {
|
||||
iereg_ = iereg & 0x1F;
|
||||
|
||||
if (intFlags_.imeOrHalted()) {
|
||||
eventTimes_.setValue<intevent_interrupts>(pendingIrqs()
|
||||
? minIntTime_
|
||||
: static_cast<unsigned long>(disabled_time));
|
||||
}
|
||||
}
|
||||
|
||||
void InterruptRequester::setIfreg(unsigned ifreg) {
|
||||
ifreg_ = ifreg;
|
||||
|
||||
if (intFlags_.imeOrHalted()) {
|
||||
eventTimes_.setValue<intevent_interrupts>(pendingIrqs()
|
||||
? minIntTime_
|
||||
: static_cast<unsigned long>(disabled_time));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef INTERRUPT_REQUESTER_H
|
||||
#define INTERRUPT_REQUESTER_H
|
||||
|
||||
#include "counterdef.h"
|
||||
#include "minkeeper.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
enum IntEventId { intevent_unhalt,
|
||||
intevent_end,
|
||||
intevent_blit,
|
||||
intevent_serial,
|
||||
intevent_oam,
|
||||
intevent_dma,
|
||||
intevent_tima,
|
||||
intevent_video,
|
||||
intevent_interrupts, intevent_last = intevent_interrupts };
|
||||
|
||||
class InterruptRequester {
|
||||
public:
|
||||
InterruptRequester();
|
||||
void saveState(SaveState &) const;
|
||||
void loadState(SaveState const &);
|
||||
void resetCc(unsigned long oldCc, unsigned long newCc);
|
||||
unsigned ifreg() const { return ifreg_; }
|
||||
unsigned pendingIrqs() const { return ifreg_ & iereg_; }
|
||||
bool ime() const { return intFlags_.ime(); }
|
||||
bool halted() const { return intFlags_.halted(); }
|
||||
void ei(unsigned long cc);
|
||||
void di();
|
||||
void halt();
|
||||
void unhalt();
|
||||
void flagIrq(unsigned bit);
|
||||
void ackIrq(unsigned bit);
|
||||
void setIereg(unsigned iereg);
|
||||
void setIfreg(unsigned ifreg);
|
||||
|
||||
IntEventId minEventId() const { return static_cast<IntEventId>(eventTimes_.min()); }
|
||||
unsigned long minEventTime() const { return eventTimes_.minValue(); }
|
||||
template<IntEventId id> void setEventTime(unsigned long value) { eventTimes_.setValue<id>(value); }
|
||||
void setEventTime(IntEventId id, unsigned long value) { eventTimes_.setValue(id, value); }
|
||||
unsigned long eventTime(IntEventId id) const { return eventTimes_.value(id); }
|
||||
|
||||
private:
|
||||
class IntFlags {
|
||||
public:
|
||||
IntFlags() : flags_(0) {}
|
||||
bool ime() const { return flags_ & flag_ime; }
|
||||
bool halted() const { return flags_ & flag_halted; }
|
||||
bool imeOrHalted() const { return flags_; }
|
||||
void setIme() { flags_ |= flag_ime; }
|
||||
void unsetIme() { flags_ &= ~flag_ime; }
|
||||
void setHalted() { flags_ |= flag_halted; }
|
||||
void unsetHalted() { flags_ &= ~flag_halted; }
|
||||
void set(bool ime, bool halted) { flags_ = halted * flag_halted + ime * flag_ime; }
|
||||
|
||||
private:
|
||||
unsigned char flags_;
|
||||
enum { flag_ime = 1, flag_halted = 2 };
|
||||
};
|
||||
|
||||
MinKeeper<intevent_last + 1> eventTimes_;
|
||||
unsigned long minIntTime_;
|
||||
unsigned ifreg_;
|
||||
unsigned iereg_;
|
||||
IntFlags intFlags_;
|
||||
};
|
||||
|
||||
inline void flagHdmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(0); }
|
||||
inline void flagGdmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(1); }
|
||||
inline void ackDmaReq(InterruptRequester &intreq) { intreq.setEventTime<intevent_dma>(disabled_time); }
|
||||
inline bool hdmaReqFlagged(InterruptRequester const &intreq) { return intreq.eventTime(intevent_dma) == 0; }
|
||||
inline bool gdmaReqFlagged(InterruptRequester const &intreq) { return intreq.eventTime(intevent_dma) == 1; }
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007-2010 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef CARTRIDGE_H
|
||||
#define CARTRIDGE_H
|
||||
|
||||
#include "memptrs.h"
|
||||
#include "rtc.h"
|
||||
#include "huc3.h"
|
||||
#include "savestate.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
class Mbc
|
||||
{
|
||||
public:
|
||||
virtual ~Mbc() {}
|
||||
virtual void romWrite(unsigned P, unsigned data) {};
|
||||
virtual void saveState(SaveState::Mem &ss) const {};
|
||||
virtual void loadState(const SaveState::Mem &ss) {};
|
||||
virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0;
|
||||
/* Called once when the boot ROM hands control to the cartridge
|
||||
* (write of 1 to 0xFF50). No MBC currently implements this --
|
||||
* the Sachen MMC1 now unlocks on read-count, not on FF50 --
|
||||
* but the hook is kept for any future MBC that needs to
|
||||
* sync with end-of-bootstrap. Default is a no-op so every
|
||||
* other MBC is unaffected. */
|
||||
virtual void bootloaderFinished() {}
|
||||
/* Sachen MMC1 only: pointer to the mapper's lock-progress
|
||||
* counter (a single unsigned char shared with Memory) and
|
||||
* the unlock entry point. Default null/no-op for every
|
||||
* other MBC. */
|
||||
virtual unsigned char *lockCounterPtr() { return 0; }
|
||||
virtual void unlock() {}
|
||||
};
|
||||
|
||||
class Cartridge
|
||||
{
|
||||
public:
|
||||
Cartridge() : isSachen_(false) {}
|
||||
void setStatePtrs(SaveState &);
|
||||
void saveState(SaveState &) const;
|
||||
void loadState(const SaveState &);
|
||||
|
||||
bool loaded() const { return mbc.get(); }
|
||||
|
||||
const unsigned char * rmem(unsigned area) const
|
||||
{
|
||||
return memptrs_.rmem(area);
|
||||
}
|
||||
|
||||
unsigned char * wmem(unsigned area) const
|
||||
{
|
||||
return memptrs_.wmem(area);
|
||||
}
|
||||
|
||||
unsigned char * vramdata() const
|
||||
{
|
||||
return memptrs_.vramdata();
|
||||
}
|
||||
|
||||
unsigned char * romdata(unsigned area) const
|
||||
{
|
||||
return memptrs_.romdata(area);
|
||||
}
|
||||
|
||||
unsigned char * wramdata(unsigned area) const
|
||||
{
|
||||
return memptrs_.wramdata(area);
|
||||
}
|
||||
|
||||
const unsigned char * rdisabledRam() const
|
||||
{
|
||||
return memptrs_.rdisabledRam();
|
||||
}
|
||||
|
||||
const unsigned char * rsrambankptr() const
|
||||
{
|
||||
return memptrs_.rsrambankptr();
|
||||
}
|
||||
|
||||
unsigned char * wsrambankptr() const
|
||||
{
|
||||
return memptrs_.wsrambankptr();
|
||||
}
|
||||
|
||||
unsigned char * vrambankptr() const
|
||||
{
|
||||
return memptrs_.vrambankptr();
|
||||
}
|
||||
|
||||
OamDmaSrc oamDmaSrc() const
|
||||
{
|
||||
return memptrs_.oamDmaSrc();
|
||||
}
|
||||
|
||||
void setVrambank(unsigned bank)
|
||||
{
|
||||
memptrs_.setVrambank(bank);
|
||||
}
|
||||
|
||||
void setWrambank(unsigned bank)
|
||||
{
|
||||
memptrs_.setWrambank(bank);
|
||||
}
|
||||
void setOamDmaSrc(OamDmaSrc oamDmaSrc)
|
||||
{
|
||||
memptrs_.setOamDmaSrc(oamDmaSrc);
|
||||
}
|
||||
|
||||
void mbcWrite(unsigned addr, unsigned data) { mbc->romWrite(addr, data); }
|
||||
|
||||
bool isCgb() const
|
||||
{
|
||||
return gambatte::isCgb(memptrs_);
|
||||
}
|
||||
|
||||
void rtcWrite(unsigned data)
|
||||
{
|
||||
rtc_.write(data);
|
||||
}
|
||||
|
||||
unsigned char rtcRead() const
|
||||
{
|
||||
return *rtc_.getActive();
|
||||
}
|
||||
|
||||
const std::string saveBasePath() const;
|
||||
void setSaveDir(const std::string &dir);
|
||||
int loadROM(const void *romdata, unsigned int romsize, unsigned int forceModel, bool multicartCompat);
|
||||
void setGameGenie(const std::string &codes);
|
||||
void clearCheats();
|
||||
|
||||
bool isHuC3() const { return huc3_.isHuC3(); }
|
||||
unsigned char HuC3Read(unsigned p, unsigned long const cc) { return huc3_.read(p, cc); }
|
||||
void HuC3Write(unsigned p, unsigned data) { huc3_.write(p, data); }
|
||||
|
||||
/* Sachen MMC1 (unlicensed mapper used by Sachen 4-in-1 etc.):
|
||||
* the cartridge header is bit-scrambled and a "locked" boot
|
||||
* state requires hooks outside the normal MBC write path.
|
||||
* isSachen() returns true for Sachen MMC1 carts.
|
||||
* sachenLockSetup(haveBoot) is called once after the bootloader
|
||||
* has been installed (or skipped) to
|
||||
* overlay the locked header bytes
|
||||
* when the bootstrap will run, or to
|
||||
* inject the Sachen logo into VRAM
|
||||
* when running without a bootstrap.
|
||||
* sachenLockCounterPtr() returns a pointer to the mapper's
|
||||
* "cart reads in 0x0100..0x01FF"
|
||||
* counter, or null for non-Sachen
|
||||
* carts. Memory::read bumps it and
|
||||
* calls onSachenUnlock() when the
|
||||
* bootstrap finishes its logo
|
||||
* display pass.
|
||||
* onSachenUnlock() drops the locked state mid-boot
|
||||
* so the bootstrap's verify pass
|
||||
* sees the unscrambled Nintendo
|
||||
* logo bytes and succeeds.
|
||||
* onBootloaderFinished() is a no-op for the new Sachen
|
||||
* implementation (lock now drops
|
||||
* on read-count, not on 0xFF50)
|
||||
* but is kept for any future MBC
|
||||
* that needs the hook.
|
||||
* For every non-Sachen cart these functions are no-ops. */
|
||||
bool isSachen() const { return isSachen_; }
|
||||
void sachenLockSetup(bool bootloaderUsed);
|
||||
unsigned char *sachenLockCounterPtr();
|
||||
void onSachenUnlock();
|
||||
void onBootloaderFinished() { if (mbc.get()) mbc->bootloaderFinished(); }
|
||||
|
||||
void *savedata_ptr();
|
||||
unsigned savedata_size();
|
||||
|
||||
// Not endian-safe at all, but hey.
|
||||
void *rtcdata_ptr();
|
||||
unsigned rtcdata_size();
|
||||
|
||||
private:
|
||||
struct AddrData
|
||||
{
|
||||
unsigned long addr;
|
||||
unsigned char data;
|
||||
AddrData(unsigned long addr, unsigned data) : addr(addr), data(data)
|
||||
{
|
||||
}
|
||||
};
|
||||
MemPtrs memptrs_;
|
||||
Rtc rtc_;
|
||||
HuC3Chip huc3_;
|
||||
/* True only when the loaded ROM was identified as a Sachen
|
||||
* MMC1 unlicensed cart via the scrambled-logo checksum check
|
||||
* in loadROM. Used to route bootloader/VRAM hooks. */
|
||||
bool isSachen_;
|
||||
|
||||
#if __cplusplus >= 201103L
|
||||
std::unique_ptr<Mbc> mbc;
|
||||
#else
|
||||
std::auto_ptr<Mbc> mbc;
|
||||
#endif
|
||||
|
||||
std::vector<AddrData> ggUndoList_;
|
||||
|
||||
void applyGameGenie(const std::string &code);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "cartridge.h"
|
||||
#include "../savestate.h"
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
static bool hasBattery(unsigned char headerByte0x147)
|
||||
{
|
||||
switch (headerByte0x147)
|
||||
{
|
||||
case 0x03:
|
||||
case 0x06:
|
||||
case 0x09:
|
||||
case 0x0F:
|
||||
case 0x10:
|
||||
case 0x13:
|
||||
case 0x1B:
|
||||
case 0x1E:
|
||||
case 0xFE:
|
||||
case 0xFF:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasRtc(unsigned headerByte0x147)
|
||||
{
|
||||
switch (headerByte0x147)
|
||||
{
|
||||
case 0xFE: // huc3
|
||||
case 0x0F:
|
||||
case 0x10:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void *Cartridge::savedata_ptr()
|
||||
{
|
||||
/* Sachen MMC1 carts have no SRAM, and the byte at 0x0147 is
|
||||
* the descrambled cartridge type which has no Nintendo
|
||||
* meaning anyway -- skip the hasBattery() check entirely so
|
||||
* we never hand the frontend a bogus save pointer. */
|
||||
if (isSachen_)
|
||||
return 0;
|
||||
// Check ROM header for battery.
|
||||
if (hasBattery(memptrs_.romdata()[0x147]))
|
||||
return memptrs_.rambankdata();
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned Cartridge::savedata_size()
|
||||
{
|
||||
if (isSachen_)
|
||||
return 0;
|
||||
if (hasBattery(memptrs_.romdata()[0x147]))
|
||||
return memptrs_.rambankdataend() - memptrs_.rambankdata();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *Cartridge::rtcdata_ptr()
|
||||
{
|
||||
if (isSachen_)
|
||||
return 0;
|
||||
if (hasRtc(memptrs_.romdata()[0x147])) {
|
||||
if (isHuC3()) {
|
||||
return &huc3_.getBaseTime();
|
||||
} else {
|
||||
return &rtc_.getBaseTime();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned Cartridge::rtcdata_size()
|
||||
{
|
||||
if (isSachen_)
|
||||
return 0;
|
||||
if (hasRtc(memptrs_.romdata()[0x147])) {
|
||||
if (isHuC3()) {
|
||||
return sizeof(huc3_.getBaseTime());
|
||||
} else {
|
||||
return sizeof(rtc_.getBaseTime());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include "huc3.h"
|
||||
#include "../savestate.h"
|
||||
#include "gambatte_log.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
HuC3Chip::HuC3Chip()
|
||||
: baseTime_(0)
|
||||
, haltTime_(0)
|
||||
, dataTime_(0)
|
||||
, writingTime_(0)
|
||||
, ramValue_(0)
|
||||
, shift_(0)
|
||||
, ramflag_(0)
|
||||
, modeflag_(HUC3_NONE)
|
||||
, irBaseCycle_(0)
|
||||
, enabled_(false)
|
||||
, halted_(false)
|
||||
, irReceivingPulse_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void HuC3Chip::doLatch() {
|
||||
uint64_t tmp = (halted_ ? haltTime_ : std::time(0)) - baseTime_;
|
||||
|
||||
unsigned minute = (tmp / 60) % 1440;
|
||||
unsigned day = (tmp / 86400) & 0xFFF;
|
||||
dataTime_ = (day << 12) | minute;
|
||||
}
|
||||
|
||||
void HuC3Chip::saveState(SaveState &state) const {
|
||||
state.huc3.baseTime = baseTime_;
|
||||
state.huc3.haltTime = haltTime_;
|
||||
state.huc3.dataTime = dataTime_;
|
||||
state.huc3.writingTime = writingTime_;
|
||||
state.huc3.ramValue = ramValue_;
|
||||
state.huc3.shift = shift_;
|
||||
state.huc3.halted = halted_;
|
||||
state.huc3.modeflag = modeflag_;
|
||||
state.huc3.irBaseCycle = irBaseCycle_;
|
||||
state.huc3.irReceivingPulse = irReceivingPulse_;
|
||||
}
|
||||
|
||||
void HuC3Chip::loadState(SaveState const &state) {
|
||||
baseTime_ = state.huc3.baseTime;
|
||||
haltTime_ = state.huc3.haltTime;
|
||||
dataTime_ = state.huc3.dataTime;
|
||||
ramValue_ = state.huc3.ramValue;
|
||||
shift_ = state.huc3.shift;
|
||||
halted_ = state.huc3.halted;
|
||||
modeflag_ = state.huc3.modeflag;
|
||||
writingTime_ = state.huc3.writingTime;
|
||||
irBaseCycle_ = state.huc3.irBaseCycle;
|
||||
irReceivingPulse_ = state.huc3.irReceivingPulse;
|
||||
}
|
||||
|
||||
unsigned char HuC3Chip::read(unsigned p, unsigned long const cc) {
|
||||
// should only reach here with ramflag = 0B-0E
|
||||
if(ramflag_ == 0x0E) {
|
||||
// INFRARED
|
||||
if(!irReceivingPulse_) {
|
||||
irReceivingPulse_ = true;
|
||||
irBaseCycle_ = cc;
|
||||
}
|
||||
unsigned long cyclesSinceStart = cc - irBaseCycle_;
|
||||
unsigned char modulation = (cyclesSinceStart/105) & 1; // 4194304 Hz CPU, 40000 Hz remote signal
|
||||
unsigned long timeUs = cyclesSinceStart*36/151; // actually *1000000/4194304
|
||||
// sony protocol
|
||||
if(timeUs < 10000) {
|
||||
// initialization allowance
|
||||
return 0;
|
||||
}
|
||||
else if(timeUs < 10000 + 2400) {
|
||||
// initial mark
|
||||
return modulation;
|
||||
}
|
||||
else if(timeUs < 10000 + 2400 + 600) {
|
||||
// initial space
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
// send data
|
||||
timeUs -= 13000;
|
||||
// write 20 bits (any 20 seem to do)
|
||||
unsigned int data = 0xFFFFF;
|
||||
for(unsigned long mask = 1UL << (20-1); mask; mask >>= 1) {
|
||||
unsigned int markTime = (data & mask) ? 1200 : 600;
|
||||
if(timeUs < markTime) { return modulation; }
|
||||
timeUs -= markTime;
|
||||
if(timeUs < 600) { return 0; }
|
||||
timeUs -= 600;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if(ramflag_ < 0x0B || ramflag_ > 0x0D) {
|
||||
gambatte_log(RETRO_LOG_ERROR, "<HuC3> error, hit huc3 read with ramflag=%02X\n", ramflag_);
|
||||
return 0xFF;
|
||||
}
|
||||
if(ramflag_ == 0x0D) return 1;
|
||||
else return ramValue_;
|
||||
}
|
||||
|
||||
void HuC3Chip::write(unsigned p, unsigned data) {
|
||||
// as above
|
||||
if(ramflag_ == 0x0B) {
|
||||
// command
|
||||
switch(data & 0xF0) {
|
||||
case 0x10:
|
||||
// read time
|
||||
doLatch();
|
||||
if(modeflag_ == HUC3_READ) {
|
||||
ramValue_ = (dataTime_ >> shift_) & 0x0F;
|
||||
shift_ += 4;
|
||||
if(shift_ > 24) shift_ = 0;
|
||||
}
|
||||
break;
|
||||
case 0x30:
|
||||
// write time
|
||||
if(modeflag_ == HUC3_WRITE) {
|
||||
if(shift_ == 0) writingTime_ = 0;
|
||||
if(shift_ < 24) {
|
||||
writingTime_ |= (data & 0x0F) << shift_;
|
||||
shift_ += 4;
|
||||
if(shift_ == 24) {
|
||||
updateTime();
|
||||
modeflag_ = HUC3_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
// some kind of mode shift
|
||||
switch(data & 0x0F) {
|
||||
case 0x0:
|
||||
// shift reset?
|
||||
shift_ = 0;
|
||||
break;
|
||||
case 0x3:
|
||||
// write time?
|
||||
modeflag_ = HUC3_WRITE;
|
||||
shift_ = 0;
|
||||
break;
|
||||
case 0x7:
|
||||
modeflag_ = HUC3_READ;
|
||||
shift_ = 0;
|
||||
break;
|
||||
// others are unimplemented so far
|
||||
}
|
||||
break;
|
||||
case 0x50:
|
||||
// ???
|
||||
break;
|
||||
case 0x60:
|
||||
modeflag_ = HUC3_READ; // ???
|
||||
break;
|
||||
}
|
||||
}
|
||||
// do nothing for 0C/0D yet
|
||||
}
|
||||
|
||||
void HuC3Chip::updateTime() {
|
||||
unsigned minute = (writingTime_ & 0xFFF) % 1440;
|
||||
unsigned day = (writingTime_ & 0xFFF000) >> 12;
|
||||
baseTime_ = std::time(0) - minute*60 - day*86400;
|
||||
haltTime_ = baseTime_;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#ifndef HuC3Chip_H
|
||||
#define HuC3Chip_H
|
||||
|
||||
enum
|
||||
{
|
||||
HUC3_READ = 0,
|
||||
HUC3_WRITE = 1,
|
||||
HUC3_NONE = 2
|
||||
};
|
||||
|
||||
#include <ctime>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class HuC3Chip {
|
||||
public:
|
||||
HuC3Chip();
|
||||
uint64_t baseTime() const { return baseTime_; }
|
||||
void setBaseTime(uint64_t baseTime) { baseTime_ = baseTime; }
|
||||
|
||||
uint64_t& getBaseTime()
|
||||
{
|
||||
return baseTime_;
|
||||
}
|
||||
|
||||
void saveState(SaveState &state) const;
|
||||
void loadState(SaveState const &state);
|
||||
void setRamflag(unsigned char ramflag) { ramflag_ = ramflag; irReceivingPulse_ = false; }
|
||||
bool isHuC3() const { return enabled_; }
|
||||
|
||||
void set(bool enabled) {
|
||||
enabled_ = enabled;
|
||||
}
|
||||
|
||||
unsigned char read(unsigned p, unsigned long const cc);
|
||||
void write(unsigned p, unsigned data);
|
||||
|
||||
private:
|
||||
uint64_t baseTime_;
|
||||
uint64_t haltTime_;
|
||||
unsigned dataTime_;
|
||||
unsigned writingTime_;
|
||||
unsigned char ramValue_;
|
||||
unsigned char shift_;
|
||||
unsigned char ramflag_;
|
||||
unsigned char modeflag_;
|
||||
unsigned long irBaseCycle_;
|
||||
bool enabled_;
|
||||
bool halted_;
|
||||
bool irReceivingPulse_;
|
||||
|
||||
void doLatch();
|
||||
void updateTime();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,168 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007-2010 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "memptrs.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
|
||||
MemPtrs::MemPtrs()
|
||||
: rmem_()
|
||||
, wmem_()
|
||||
, romdata_()
|
||||
, wramdata_()
|
||||
, vrambankptr_(0)
|
||||
, rsrambankptr_(0)
|
||||
, wsrambankptr_(0)
|
||||
,memchunk_(0)
|
||||
, rambankdata_(0)
|
||||
, wramdataend_(0)
|
||||
, oamDmaSrc_(oam_dma_src_off)
|
||||
{
|
||||
}
|
||||
|
||||
MemPtrs::~MemPtrs()
|
||||
{
|
||||
delete []memchunk_;
|
||||
}
|
||||
|
||||
void MemPtrs::reset(const unsigned rombanks, const unsigned rambanks, const unsigned wrambanks)
|
||||
{
|
||||
delete []memchunk_;
|
||||
memchunk_ = new unsigned char[
|
||||
0x4000
|
||||
+ rombanks * 0x4000ul + 0x4000
|
||||
+ rambanks * 0x2000ul
|
||||
+ wrambanks * 0x1000ul
|
||||
+ 0x4000];
|
||||
|
||||
romdata_[0] = romdata();
|
||||
rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000;
|
||||
wramdata_[0] = rambankdata_ + rambanks * 0x2000ul;
|
||||
wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul;
|
||||
|
||||
std::memset(rdisabledRamw(), 0xFF, 0x2000);
|
||||
|
||||
oamDmaSrc_ = oam_dma_src_off;
|
||||
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
|
||||
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
|
||||
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
|
||||
|
||||
setRombank(1);
|
||||
setRambank(0, 0);
|
||||
setVrambank(0);
|
||||
setWrambank(1);
|
||||
}
|
||||
|
||||
void MemPtrs::setRombank0(const unsigned bank)
|
||||
{
|
||||
romdata_[0] = romdata() + bank * 0x4000ul;
|
||||
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::setRombank(const unsigned bank)
|
||||
{
|
||||
romdata_[1] = romdata() + bank * 0x4000ul - 0x4000;
|
||||
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::setRambank(const unsigned flags, const unsigned rambank)
|
||||
{
|
||||
unsigned char *const srambankptr = (flags & RTC_EN)
|
||||
? 0
|
||||
: (rambankdata() != rambankdataend()
|
||||
? rambankdata_ + rambank * 0x2000ul - 0xA000 : wdisabledRam() - 0xA000);
|
||||
|
||||
rsrambankptr_ = (flags & READ_EN) && srambankptr != wdisabledRam() - 0xA000 ? srambankptr : rdisabledRamw() - 0xA000;
|
||||
wsrambankptr_ = (flags & WRITE_EN) ? srambankptr : wdisabledRam() - 0xA000;
|
||||
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
|
||||
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::setWrambank(const unsigned bank)
|
||||
{
|
||||
wramdata_[1] = wramdata_[0] + ((bank & 0x07) ? (bank & 0x07) : 1) * 0x1000;
|
||||
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::setOamDmaSrc(const OamDmaSrc oamDmaSrc)
|
||||
{
|
||||
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
|
||||
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
|
||||
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
|
||||
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
|
||||
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
|
||||
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
|
||||
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
|
||||
|
||||
oamDmaSrc_ = oamDmaSrc;
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::disconnectOamDmaAreas()
|
||||
{
|
||||
if (isCgb(*this))
|
||||
{
|
||||
switch (oamDmaSrc_)
|
||||
{
|
||||
case oam_dma_src_rom: // fall through
|
||||
case oam_dma_src_sram:
|
||||
case oam_dma_src_invalid:
|
||||
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
|
||||
rmem_[0xB] = rmem_[0xA] = 0;
|
||||
wmem_[0xB] = wmem_[0xA] = 0;
|
||||
break;
|
||||
case oam_dma_src_vram:
|
||||
break;
|
||||
case oam_dma_src_wram:
|
||||
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
|
||||
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
|
||||
break;
|
||||
case oam_dma_src_off:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (oamDmaSrc_)
|
||||
{
|
||||
case oam_dma_src_rom: // fall through
|
||||
case oam_dma_src_sram:
|
||||
case oam_dma_src_wram:
|
||||
case oam_dma_src_invalid:
|
||||
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
|
||||
rmem_[0xB] = rmem_[0xA] = 0;
|
||||
wmem_[0xB] = wmem_[0xA] = 0;
|
||||
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
|
||||
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
|
||||
break;
|
||||
case oam_dma_src_vram:
|
||||
break;
|
||||
case oam_dma_src_off:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007-2010 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef MEMPTRS_H
|
||||
#define MEMPTRS_H
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
|
||||
enum OamDmaSrc { oam_dma_src_rom,
|
||||
oam_dma_src_sram,
|
||||
oam_dma_src_vram,
|
||||
oam_dma_src_wram,
|
||||
oam_dma_src_invalid,
|
||||
oam_dma_src_off, };
|
||||
|
||||
class MemPtrs
|
||||
{
|
||||
public:
|
||||
enum RamFlag
|
||||
{
|
||||
READ_EN = 1,
|
||||
WRITE_EN = 2,
|
||||
RTC_EN = 4
|
||||
};
|
||||
|
||||
MemPtrs();
|
||||
~MemPtrs();
|
||||
void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks);
|
||||
|
||||
const unsigned char * rmem(unsigned area) const
|
||||
{
|
||||
return rmem_[area];
|
||||
}
|
||||
|
||||
unsigned char * wmem(unsigned area) const
|
||||
{
|
||||
return wmem_[area];
|
||||
}
|
||||
|
||||
unsigned char * vramdata() const
|
||||
{
|
||||
return rambankdata_ - 0x4000;
|
||||
}
|
||||
|
||||
unsigned char * vramdataend() const
|
||||
{
|
||||
return rambankdata_;
|
||||
}
|
||||
|
||||
unsigned char * romdata() const
|
||||
{
|
||||
return memchunk_ + 0x4000;
|
||||
}
|
||||
|
||||
unsigned char * romdata(unsigned area) const
|
||||
{
|
||||
return romdata_[area];
|
||||
}
|
||||
|
||||
unsigned char * romdataend() const
|
||||
{
|
||||
return rambankdata_ - 0x4000;
|
||||
}
|
||||
|
||||
unsigned char * wramdata(unsigned area) const
|
||||
{
|
||||
return wramdata_[area];
|
||||
}
|
||||
|
||||
unsigned char * wramdataend() const
|
||||
{
|
||||
return wramdataend_;
|
||||
}
|
||||
|
||||
unsigned char * rambankdata() const
|
||||
{
|
||||
return rambankdata_;
|
||||
}
|
||||
|
||||
unsigned char * rambankdataend() const
|
||||
{
|
||||
return wramdata_[0];
|
||||
}
|
||||
|
||||
const unsigned char * rdisabledRam() const
|
||||
{
|
||||
return rdisabledRamw();
|
||||
}
|
||||
|
||||
const unsigned char * rsrambankptr() const
|
||||
{
|
||||
return rsrambankptr_;
|
||||
}
|
||||
|
||||
unsigned char * wsrambankptr() const
|
||||
{
|
||||
return wsrambankptr_;
|
||||
}
|
||||
|
||||
unsigned char * vrambankptr() const
|
||||
{
|
||||
return vrambankptr_;
|
||||
}
|
||||
|
||||
OamDmaSrc oamDmaSrc() const
|
||||
{
|
||||
return oamDmaSrc_;
|
||||
}
|
||||
|
||||
void setRombank0(unsigned bank);
|
||||
void setRombank(unsigned bank);
|
||||
void setRambank(unsigned ramFlags, unsigned rambank);
|
||||
void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; }
|
||||
void setWrambank(unsigned bank);
|
||||
void setOamDmaSrc(OamDmaSrc oamDmaSrc);
|
||||
|
||||
private:
|
||||
unsigned char *romdata_[2];
|
||||
unsigned char *wramdata_[2];
|
||||
const unsigned char *rmem_[0x10];
|
||||
unsigned char *wmem_[0x10];
|
||||
unsigned char *vrambankptr_;
|
||||
unsigned char *rsrambankptr_;
|
||||
unsigned char *wsrambankptr_;
|
||||
unsigned char *memchunk_;
|
||||
unsigned char *rambankdata_;
|
||||
unsigned char *wramdataend_;
|
||||
OamDmaSrc oamDmaSrc_;
|
||||
MemPtrs(const MemPtrs &);
|
||||
MemPtrs & operator=(const MemPtrs &);
|
||||
void disconnectOamDmaAreas();
|
||||
unsigned char * rdisabledRamw() const { return wramdataend_ ; }
|
||||
unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; }
|
||||
};
|
||||
|
||||
inline bool isCgb(const MemPtrs &memptrs)
|
||||
{
|
||||
return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,170 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "rtc.h"
|
||||
#include "../savestate.h"
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
|
||||
Rtc::Rtc()
|
||||
: activeData_(NULL),
|
||||
activeSet_(NULL),
|
||||
baseTime_(0),
|
||||
haltTime_(0),
|
||||
index_(5),
|
||||
dataDh_(0),
|
||||
dataDl_(0),
|
||||
dataH_(0),
|
||||
dataM_(0),
|
||||
dataS_(0),
|
||||
enabled_(false),
|
||||
lastLatchData_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void Rtc::doLatch()
|
||||
{
|
||||
uint64_t tmp = ((dataDh_ & 0x40) ? haltTime_ : std::time(0)) - baseTime_;
|
||||
|
||||
while (tmp > 0x1FF * 86400)
|
||||
{
|
||||
baseTime_ += 0x1FF * 86400;
|
||||
tmp -= 0x1FF * 86400;
|
||||
dataDh_ |= 0x80;
|
||||
}
|
||||
|
||||
dataDl_ = (tmp / 86400) & 0xFF;
|
||||
dataDh_ &= 0xFE;
|
||||
dataDh_ |= ((tmp / 86400) & 0x100) >> 8;
|
||||
tmp %= 86400;
|
||||
|
||||
dataH_ = tmp / 3600;
|
||||
tmp %= 3600;
|
||||
|
||||
dataM_ = tmp / 60;
|
||||
tmp %= 60;
|
||||
|
||||
dataS_ = tmp;
|
||||
}
|
||||
|
||||
void Rtc::doSwapActive()
|
||||
{
|
||||
if (!enabled_ || index_ > 4)
|
||||
{
|
||||
activeData_ = NULL;
|
||||
activeSet_ = NULL;
|
||||
}
|
||||
else switch (index_)
|
||||
{
|
||||
case 0x00:
|
||||
activeData_ = &dataS_;
|
||||
activeSet_ = &Rtc::setS;
|
||||
break;
|
||||
case 0x01:
|
||||
activeData_ = &dataM_;
|
||||
activeSet_ = &Rtc::setM;
|
||||
break;
|
||||
case 0x02:
|
||||
activeData_ = &dataH_;
|
||||
activeSet_ = &Rtc::setH;
|
||||
break;
|
||||
case 0x03:
|
||||
activeData_ = &dataDl_;
|
||||
activeSet_ = &Rtc::setDl;
|
||||
break;
|
||||
case 0x04:
|
||||
activeData_ = &dataDh_;
|
||||
activeSet_ = &Rtc::setDh;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Rtc::saveState(SaveState &state) const
|
||||
{
|
||||
state.rtc.baseTime = baseTime_;
|
||||
state.rtc.haltTime = haltTime_;
|
||||
state.rtc.dataDh = dataDh_;
|
||||
state.rtc.dataDl = dataDl_;
|
||||
state.rtc.dataH = dataH_;
|
||||
state.rtc.dataM = dataM_;
|
||||
state.rtc.dataS = dataS_;
|
||||
state.rtc.lastLatchData = lastLatchData_;
|
||||
}
|
||||
|
||||
void Rtc::loadState(const SaveState &state)
|
||||
{
|
||||
baseTime_ = state.rtc.baseTime;
|
||||
haltTime_ = state.rtc.haltTime;
|
||||
dataDh_ = state.rtc.dataDh;
|
||||
dataDl_ = state.rtc.dataDl;
|
||||
dataH_ = state.rtc.dataH;
|
||||
dataM_ = state.rtc.dataM;
|
||||
dataS_ = state.rtc.dataS;
|
||||
lastLatchData_ = state.rtc.lastLatchData;
|
||||
|
||||
doSwapActive();
|
||||
}
|
||||
|
||||
void Rtc::setDh(const unsigned new_dh)
|
||||
{
|
||||
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
|
||||
const uint64_t old_highdays = ((unixtime - baseTime_) / 86400) & 0x100;
|
||||
baseTime_ += old_highdays * 86400;
|
||||
baseTime_ -= ((new_dh & 0x1) << 8) * 86400;
|
||||
|
||||
if ((dataDh_ ^ new_dh) & 0x40)
|
||||
{
|
||||
if (new_dh & 0x40)
|
||||
haltTime_ = std::time(0);
|
||||
else
|
||||
baseTime_ += std::time(0) - haltTime_;
|
||||
}
|
||||
}
|
||||
|
||||
void Rtc::setDl(const unsigned new_lowdays)
|
||||
{
|
||||
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
|
||||
const uint64_t old_lowdays = ((unixtime - baseTime_) / 86400) & 0xFF;
|
||||
baseTime_ += old_lowdays * 86400;
|
||||
baseTime_ -= new_lowdays * 86400;
|
||||
}
|
||||
|
||||
void Rtc::setH(const unsigned new_hours)
|
||||
{
|
||||
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
|
||||
const uint64_t old_hours = ((unixtime - baseTime_) / 3600) % 24;
|
||||
baseTime_ += old_hours * 3600;
|
||||
baseTime_ -= new_hours * 3600;
|
||||
}
|
||||
|
||||
void Rtc::setM(const unsigned new_minutes)
|
||||
{
|
||||
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
|
||||
const uint64_t old_minutes = ((unixtime - baseTime_) / 60) % 60;
|
||||
baseTime_ += old_minutes * 60;
|
||||
baseTime_ -= new_minutes * 60;
|
||||
}
|
||||
|
||||
void Rtc::setS(const unsigned new_seconds)
|
||||
{
|
||||
const uint64_t unixtime = (dataDh_ & 0x40) ? haltTime_ : std::time(0);
|
||||
baseTime_ += (unixtime - baseTime_) % 60;
|
||||
baseTime_ -= new_seconds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef RTC_H
|
||||
#define RTC_H
|
||||
|
||||
#include <ctime>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class Rtc
|
||||
{
|
||||
public:
|
||||
Rtc();
|
||||
|
||||
const unsigned char* getActive() const
|
||||
{
|
||||
return activeData_;
|
||||
}
|
||||
|
||||
uint64_t& getBaseTime()
|
||||
{
|
||||
return baseTime_;
|
||||
}
|
||||
|
||||
void setBaseTime(const uint64_t baseTime)
|
||||
{
|
||||
baseTime_ = baseTime;
|
||||
}
|
||||
|
||||
void latch(const unsigned data)
|
||||
{
|
||||
if (!lastLatchData_ && data == 1)
|
||||
doLatch();
|
||||
|
||||
lastLatchData_ = data;
|
||||
}
|
||||
|
||||
void saveState(SaveState &state) const;
|
||||
void loadState(const SaveState &state);
|
||||
|
||||
void set(const bool enabled, unsigned bank)
|
||||
{
|
||||
bank &= 0xF;
|
||||
bank -= 8;
|
||||
|
||||
this->enabled_ = enabled;
|
||||
this->index_ = bank;
|
||||
|
||||
doSwapActive();
|
||||
}
|
||||
|
||||
void write(const unsigned data)
|
||||
{
|
||||
(this->*activeSet_)(data);
|
||||
*activeData_ = data;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned char *activeData_;
|
||||
void (Rtc::*activeSet_)(unsigned);
|
||||
uint64_t baseTime_;
|
||||
uint64_t haltTime_;
|
||||
unsigned char index_;
|
||||
unsigned char dataDh_;
|
||||
unsigned char dataDl_;
|
||||
unsigned char dataH_;
|
||||
unsigned char dataM_;
|
||||
unsigned char dataS_;
|
||||
bool enabled_;
|
||||
bool lastLatchData_;
|
||||
|
||||
void doLatch();
|
||||
void doSwapActive();
|
||||
void setDh(unsigned new_dh);
|
||||
void setDl(unsigned new_lowdays);
|
||||
void setH(unsigned new_hours);
|
||||
void setM(unsigned new_minutes);
|
||||
void setS(unsigned new_seconds);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,157 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2009 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef MINKEEPER_H
|
||||
#define MINKEEPER_H
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace MinKeeperUtil
|
||||
{
|
||||
template<int n> struct CeiledLog2 { enum { RESULT = 1 + CeiledLog2<(n + 1) / 2>::RESULT }; };
|
||||
template<> struct CeiledLog2<1> { enum { RESULT = 0 }; };
|
||||
|
||||
template<int v, int n> struct RoundedDiv2n { enum { RESULT = RoundedDiv2n<(v + 1) / 2, n - 1>::RESULT }; };
|
||||
template<int v> struct RoundedDiv2n<v,1> { enum { RESULT = v }; };
|
||||
|
||||
template<template<int> class T, int n> struct Sum { enum { RESULT = T<n-1>::RESULT + Sum<T, n-1>::RESULT }; };
|
||||
template<template<int> class T> struct Sum<T,0> { enum { RESULT = 0 }; };
|
||||
}
|
||||
|
||||
// Keeps track of minimum value identified by id as values change.
|
||||
// Higher ids prioritized (as min value) if values are equal. Can easily be reversed by swapping < for <=.
|
||||
// Higher ids can be faster to change when the number of ids isn't a power of 2.
|
||||
// Thus the ones that change more frequently should have higher ids if priority allows it.
|
||||
template<int ids>
|
||||
class MinKeeper
|
||||
{
|
||||
enum { LEVELS = MinKeeperUtil::CeiledLog2<ids>::RESULT };
|
||||
template<int l> struct Num { enum { RESULT = MinKeeperUtil::RoundedDiv2n<ids, LEVELS + 1 - l>::RESULT }; };
|
||||
template<int l> struct Sum { enum { RESULT = MinKeeperUtil::Sum<Num, l>::RESULT }; };
|
||||
|
||||
template<int id, int level>
|
||||
struct UpdateValue {
|
||||
enum { P = Sum<level-1>::RESULT + id };
|
||||
enum { C0 = Sum<level>::RESULT + id * 2 };
|
||||
|
||||
static void updateValue(MinKeeper<ids> *const s)
|
||||
{
|
||||
// GCC 4.3 generates better code with the ternary operator on i386.
|
||||
s->a[P] = (id * 2 + 1 == Num<level>::RESULT || s->values[s->a[C0]] < s->values[s->a[C0 + 1]]) ? s->a[C0] : s->a[C0 + 1];
|
||||
|
||||
UpdateValue<id / 2, level - 1>::updateValue(s);
|
||||
}
|
||||
};
|
||||
|
||||
template<int id>
|
||||
struct UpdateValue<id,0>
|
||||
{
|
||||
static void updateValue(MinKeeper<ids> *const s)
|
||||
{
|
||||
s->minValue_ = s->values[s->a[0]];
|
||||
}
|
||||
};
|
||||
|
||||
template<int id, int dummy> struct FillLut {
|
||||
static void fillLut(MinKeeper<ids> *const s)
|
||||
{
|
||||
s->updateValueLut[id] = updateValue<id>;
|
||||
FillLut<id-1,dummy>::fillLut(s);
|
||||
}
|
||||
};
|
||||
|
||||
template<int dummy> struct FillLut<-1,dummy> {
|
||||
static void fillLut(MinKeeper<ids> *const)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
unsigned long values[ids];
|
||||
unsigned long minValue_;
|
||||
void (*updateValueLut[Num<LEVELS-1>::RESULT])(MinKeeper<ids>*const);
|
||||
int a[Sum<LEVELS>::RESULT];
|
||||
|
||||
template<int id> static void updateValue(MinKeeper<ids> *const s);
|
||||
|
||||
public:
|
||||
MinKeeper(unsigned long initValue = 0xFFFFFFFF);
|
||||
|
||||
int min() const { return a[0]; }
|
||||
unsigned long minValue() const { return minValue_; }
|
||||
|
||||
template<int id>
|
||||
void setValue(const unsigned long cnt)
|
||||
{
|
||||
values[id] = cnt;
|
||||
updateValue<id / 2>(this);
|
||||
}
|
||||
|
||||
void setValue(const int id, const unsigned long cnt)
|
||||
{
|
||||
values[id] = cnt;
|
||||
updateValueLut[id >> 1](this);
|
||||
}
|
||||
|
||||
unsigned long value(const int id) const { return values[id]; }
|
||||
};
|
||||
|
||||
template<int ids>
|
||||
MinKeeper<ids>::MinKeeper(const unsigned long initValue)
|
||||
{
|
||||
std::fill(values, values + ids, initValue);
|
||||
|
||||
for (int i = 0; i < Num<LEVELS-1>::RESULT; ++i)
|
||||
{
|
||||
a[Sum<LEVELS-1>::RESULT + i] = (i * 2 + 1 == ids || values[i * 2] < values[i * 2 + 1]) ? i * 2 : i * 2 + 1;
|
||||
}
|
||||
|
||||
int n = Num<LEVELS-1>::RESULT;
|
||||
int off = Sum<LEVELS-1>::RESULT;
|
||||
|
||||
while (off)
|
||||
{
|
||||
const int pn = (n + 1) >> 1;
|
||||
const int poff = off - pn;
|
||||
|
||||
for (int i = 0; i < pn; ++i)
|
||||
{
|
||||
a[poff + i] = (i * 2 + 1 == n ||
|
||||
values[a[off + i * 2]] < values[a[off + i * 2 + 1]]) ?
|
||||
a[off + i * 2] : a[off + i * 2 + 1];
|
||||
}
|
||||
|
||||
off = poff;
|
||||
n = pn;
|
||||
}
|
||||
|
||||
minValue_ = values[a[0]];
|
||||
|
||||
FillLut<Num<LEVELS-1>::RESULT-1,0>::fillLut(this);
|
||||
}
|
||||
|
||||
template<int ids>
|
||||
template<int id>
|
||||
void MinKeeper<ids>::updateValue(MinKeeper<ids> *const s)
|
||||
{
|
||||
s->a[Sum<LEVELS-1>::RESULT + id] = (id * 2 + 1 == ids || s->values[id * 2] < s->values[id * 2 + 1]) ? id * 2 : id * 2 + 1;
|
||||
|
||||
UpdateValue<id / 2, LEVELS-1>::updateValue(s);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,229 @@
|
||||
//
|
||||
// Copyright (C) 2008 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SAVESTATE_H
|
||||
#define SAVESTATE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <cstddef>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class SaverList;
|
||||
|
||||
struct SaveState {
|
||||
template<typename T>
|
||||
class Ptr {
|
||||
public:
|
||||
Ptr() : ptr(0), size_(0) {}
|
||||
T* get() const { return ptr; }
|
||||
std::size_t size() const { return size_; }
|
||||
void set(T *p, std::size_t size) { ptr = p; size_ = size; }
|
||||
|
||||
friend class SaverList;
|
||||
friend void setInitState(SaveState &, bool, bool, bool);
|
||||
|
||||
private:
|
||||
T *ptr;
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
struct CPU {
|
||||
unsigned long cycleCounter;
|
||||
unsigned short pc;
|
||||
unsigned short sp;
|
||||
unsigned char a;
|
||||
unsigned char b;
|
||||
unsigned char c;
|
||||
unsigned char d;
|
||||
unsigned char e;
|
||||
unsigned char f;
|
||||
unsigned char h;
|
||||
unsigned char l;
|
||||
bool skip;
|
||||
} cpu;
|
||||
|
||||
struct Mem {
|
||||
Ptr<unsigned char> vram;
|
||||
Ptr<unsigned char> sram;
|
||||
Ptr<unsigned char> wram;
|
||||
Ptr<unsigned char> ioamhram;
|
||||
unsigned long divLastUpdate;
|
||||
unsigned long timaLastUpdate;
|
||||
unsigned long tmatime;
|
||||
unsigned long nextSerialtime;
|
||||
unsigned long lastOamDmaUpdate;
|
||||
unsigned long minIntTime;
|
||||
unsigned long unhaltTime;
|
||||
unsigned short rombank;
|
||||
unsigned short dmaSource;
|
||||
unsigned short dmaDestination;
|
||||
unsigned char rambank;
|
||||
unsigned char oamDmaPos;
|
||||
unsigned char HuC3RAMflag;
|
||||
unsigned char sachenOuterMask;
|
||||
/* Sachen MMC1: count of CPU reads of 0x0100..0x01FF observed
|
||||
* while the cartridge is in its boot-time locked state.
|
||||
* Preserved so a savestate captured mid-bootstrap restores
|
||||
* the cart to exactly the right point in its 48-reads-to-
|
||||
* unlock sequence; without it, a mid-boot save would resume
|
||||
* with count=0 and unlock 48 reads too late, breaking the
|
||||
* bootstrap's verify pass. Unused (and zero) for every
|
||||
* non-Sachen cartridge. */
|
||||
unsigned char sachenLockCount;
|
||||
#ifdef HAVE_NETWORK
|
||||
unsigned char serialize_value;
|
||||
bool serialize_is_fastcgb;
|
||||
#endif
|
||||
bool IME;
|
||||
bool halted;
|
||||
bool enableRam;
|
||||
bool rambankMode;
|
||||
bool hdmaTransfer;
|
||||
} mem;
|
||||
|
||||
struct PPU {
|
||||
Ptr<unsigned char> bgpData;
|
||||
Ptr<unsigned char> objpData;
|
||||
//SpriteMapper::OamReader
|
||||
Ptr<unsigned char> oamReaderBuf;
|
||||
Ptr<bool> oamReaderSzbuf;
|
||||
unsigned char dmgPalette[8 * 3];
|
||||
|
||||
unsigned long videoCycles;
|
||||
unsigned long enableDisplayM0Time;
|
||||
unsigned short lastM0Time;
|
||||
unsigned short nextM0Irq;
|
||||
unsigned short tileword;
|
||||
unsigned short ntileword;
|
||||
unsigned char spAttribList[10];
|
||||
unsigned char spByte0List[10];
|
||||
unsigned char spByte1List[10];
|
||||
unsigned char winYPos;
|
||||
unsigned char xpos;
|
||||
unsigned char endx;
|
||||
unsigned char reg0;
|
||||
unsigned char reg1;
|
||||
unsigned char attrib;
|
||||
unsigned char nattrib;
|
||||
unsigned char state;
|
||||
unsigned char nextSprite;
|
||||
unsigned char currentSprite;
|
||||
unsigned char lyc;
|
||||
unsigned char m0lyc;
|
||||
unsigned char oldWy;
|
||||
unsigned char winDrawState;
|
||||
unsigned char wscx;
|
||||
bool weMaster;
|
||||
bool pendingLcdstatIrq;
|
||||
} ppu;
|
||||
|
||||
struct SPU {
|
||||
struct Duty {
|
||||
unsigned long nextPosUpdate;
|
||||
unsigned char nr3;
|
||||
unsigned char pos;
|
||||
bool high;
|
||||
};
|
||||
|
||||
struct Env {
|
||||
unsigned long counter;
|
||||
unsigned char volume;
|
||||
};
|
||||
|
||||
struct LCounter {
|
||||
unsigned long counter;
|
||||
unsigned short lengthCounter;
|
||||
};
|
||||
|
||||
struct {
|
||||
struct {
|
||||
unsigned long counter;
|
||||
unsigned short shadow;
|
||||
unsigned char nr0;
|
||||
bool negging;
|
||||
} sweep;
|
||||
Duty duty;
|
||||
Env env;
|
||||
LCounter lcounter;
|
||||
unsigned char nr4;
|
||||
bool master;
|
||||
} ch1;
|
||||
|
||||
struct {
|
||||
Duty duty;
|
||||
Env env;
|
||||
LCounter lcounter;
|
||||
unsigned char nr4;
|
||||
bool master;
|
||||
} ch2;
|
||||
|
||||
struct {
|
||||
Ptr<unsigned char> waveRam;
|
||||
LCounter lcounter;
|
||||
unsigned long waveCounter;
|
||||
unsigned long lastReadTime;
|
||||
unsigned char nr3;
|
||||
unsigned char nr4;
|
||||
unsigned char wavePos;
|
||||
unsigned char sampleBuf;
|
||||
bool master;
|
||||
} ch3;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
unsigned long counter;
|
||||
unsigned short reg;
|
||||
} lfsr;
|
||||
Env env;
|
||||
LCounter lcounter;
|
||||
unsigned char nr4;
|
||||
bool master;
|
||||
} ch4;
|
||||
|
||||
unsigned long cycleCounter;
|
||||
} spu;
|
||||
|
||||
struct RTC {
|
||||
unsigned long baseTime;
|
||||
unsigned long haltTime;
|
||||
unsigned char dataDh;
|
||||
unsigned char dataDl;
|
||||
unsigned char dataH;
|
||||
unsigned char dataM;
|
||||
unsigned char dataS;
|
||||
bool lastLatchData;
|
||||
} rtc;
|
||||
|
||||
struct HuC3 {
|
||||
unsigned long baseTime;
|
||||
unsigned long haltTime;
|
||||
unsigned long dataTime;
|
||||
unsigned long writingTime;
|
||||
unsigned long irBaseCycle;
|
||||
bool halted;
|
||||
unsigned char shift;
|
||||
unsigned char ramValue;
|
||||
unsigned char modeflag;
|
||||
bool irReceivingPulse;
|
||||
} huc3;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SERIAL_IO_H
|
||||
#define SERIAL_IO_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class SerialIO
|
||||
{
|
||||
public:
|
||||
virtual ~SerialIO() {};
|
||||
|
||||
virtual bool check(unsigned char out, unsigned char& in, bool& fastCgb) = 0;
|
||||
virtual unsigned char send(unsigned char data, bool fastCgb) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "sound.h"
|
||||
#include "savestate.h"
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
/*
|
||||
Frame Sequencer
|
||||
|
||||
Step Length Ctr Vol Env Sweep
|
||||
- - - - - - - - - - - - - - - - - - - -
|
||||
0 Clock - Clock
|
||||
S 1 - Clock -
|
||||
2 Clock - -
|
||||
3 - - -
|
||||
4 Clock - Clock
|
||||
5 - - -
|
||||
6 Clock - -
|
||||
7 - - -
|
||||
- - - - - - - - - - - - - - - - - - - -
|
||||
Rate 256 Hz 64 Hz 128 Hz
|
||||
|
||||
S) start step on sound power on.
|
||||
*/
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
|
||||
PSG::PSG()
|
||||
: buffer_(0)
|
||||
, bufferSize_(0)
|
||||
, bufferPos_(0)
|
||||
, lastUpdate_(0)
|
||||
, soVol_(0)
|
||||
, rsum_(0x8000) // initialize to 0x8000 to prevent borrows from high word, xor away later
|
||||
, enabled_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void PSG::init(const bool cgb)
|
||||
{
|
||||
ch1_.init(cgb);
|
||||
ch3_.init(cgb);
|
||||
}
|
||||
|
||||
void PSG::reset()
|
||||
{
|
||||
ch1_.reset();
|
||||
ch2_.reset();
|
||||
ch3_.reset();
|
||||
ch4_.reset();
|
||||
}
|
||||
|
||||
void PSG::setStatePtrs(SaveState &state)
|
||||
{
|
||||
ch3_.setStatePtrs(state);
|
||||
}
|
||||
|
||||
void PSG::saveState(SaveState &state)
|
||||
{
|
||||
ch1_.saveState(state);
|
||||
ch2_.saveState(state);
|
||||
ch3_.saveState(state);
|
||||
ch4_.saveState(state);
|
||||
}
|
||||
|
||||
void PSG::loadState(const SaveState &state)
|
||||
{
|
||||
ch1_.loadState(state);
|
||||
ch2_.loadState(state);
|
||||
ch3_.loadState(state);
|
||||
ch4_.loadState(state);
|
||||
|
||||
lastUpdate_ = state.cpu.cycleCounter;
|
||||
setSoVolume(state.mem.ioamhram.get()[0x124]);
|
||||
mapSo(state.mem.ioamhram.get()[0x125]);
|
||||
enabled_ = state.mem.ioamhram.get()[0x126] >> 7 & 1;
|
||||
}
|
||||
|
||||
void PSG::accumulateChannels(const unsigned long cycles)
|
||||
{
|
||||
uint_least32_t *const buf = buffer_ + bufferPos_;
|
||||
std::memset(buf, 0, cycles * sizeof(uint_least32_t));
|
||||
ch1_.update(buf, soVol_, cycles);
|
||||
ch2_.update(buf, soVol_, cycles);
|
||||
ch3_.update(buf, soVol_, cycles);
|
||||
ch4_.update(buf, soVol_, cycles);
|
||||
}
|
||||
|
||||
void PSG::generateSamples(unsigned long const cycleCounter, bool const doubleSpeed)
|
||||
{
|
||||
unsigned long cycles = (cycleCounter - lastUpdate_) >> (1 + doubleSpeed);
|
||||
|
||||
if (cycles + bufferPos_ > bufferSize_)
|
||||
cycles = (bufferSize_ > bufferPos_) ? (bufferSize_ - bufferPos_) : 0;
|
||||
|
||||
lastUpdate_ += cycles << (1 + doubleSpeed);
|
||||
|
||||
if (cycles)
|
||||
accumulateChannels(cycles);
|
||||
|
||||
bufferPos_ += cycles;
|
||||
}
|
||||
|
||||
void PSG::resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed)
|
||||
{
|
||||
generateSamples(oldCc, doubleSpeed);
|
||||
lastUpdate_ = newCc - (oldCc - lastUpdate_);
|
||||
}
|
||||
|
||||
size_t PSG::fillBuffer()
|
||||
{
|
||||
uint_least32_t sum = rsum_;
|
||||
uint_least32_t *b = buffer_;
|
||||
unsigned n = bufferPos_;
|
||||
|
||||
if (unsigned n2 = n >> 3)
|
||||
{
|
||||
n -= n2 << 3;
|
||||
|
||||
do
|
||||
{
|
||||
sum += b[0];
|
||||
b[0] = sum ^ 0x8000;
|
||||
sum += b[1];
|
||||
b[1] = sum ^ 0x8000;
|
||||
sum += b[2];
|
||||
b[2] = sum ^ 0x8000;
|
||||
sum += b[3];
|
||||
b[3] = sum ^ 0x8000;
|
||||
sum += b[4];
|
||||
b[4] = sum ^ 0x8000;
|
||||
sum += b[5];
|
||||
b[5] = sum ^ 0x8000;
|
||||
sum += b[6];
|
||||
b[6] = sum ^ 0x8000;
|
||||
sum += b[7];
|
||||
b[7] = sum ^ 0x8000;
|
||||
|
||||
b += 8;
|
||||
} while (--n2);
|
||||
}
|
||||
|
||||
while (n--)
|
||||
{
|
||||
sum += *b;
|
||||
/* xor away the initial rsum value of 0x8000 (which prevents
|
||||
* borrows from the high word) from the low word */
|
||||
*b++ = sum ^ 0x8000;
|
||||
}
|
||||
|
||||
rsum_ = sum;
|
||||
|
||||
return bufferPos_;
|
||||
}
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
static const unsigned long so1Mul = 0x00000001;
|
||||
static const unsigned long so2Mul = 0x00010000;
|
||||
#else
|
||||
static const unsigned long so1Mul = 0x00010000;
|
||||
static const unsigned long so2Mul = 0x00000001;
|
||||
#endif
|
||||
|
||||
void PSG::setSoVolume(const unsigned nr50)
|
||||
{
|
||||
soVol_ = (((nr50 & 0x7) + 1) * so1Mul
|
||||
+ ((nr50 >> 4 & 0x7) + 1) * so2Mul) * 64;
|
||||
}
|
||||
|
||||
void PSG::mapSo(const unsigned nr51)
|
||||
{
|
||||
const unsigned long tmp = nr51 * so1Mul + (nr51 >> 4) * so2Mul;
|
||||
|
||||
ch1_.setSo((tmp & 0x00010001) * 0xFFFF);
|
||||
ch2_.setSo((tmp >> 1 & 0x00010001) * 0xFFFF);
|
||||
ch3_.setSo((tmp >> 2 & 0x00010001) * 0xFFFF);
|
||||
ch4_.setSo((tmp >> 3 & 0x00010001) * 0xFFFF);
|
||||
}
|
||||
|
||||
unsigned PSG::getStatus() const
|
||||
{
|
||||
return ch1_.isActive()
|
||||
| ch2_.isActive() << 1
|
||||
| ch3_.isActive() << 2
|
||||
| ch4_.isActive() << 3;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SOUND_H
|
||||
#define SOUND_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <cstddef>
|
||||
|
||||
#include "sound/channel1.h"
|
||||
#include "sound/channel2.h"
|
||||
#include "sound/channel3.h"
|
||||
#include "sound/channel4.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class PSG {
|
||||
public:
|
||||
PSG();
|
||||
void init(bool cgb);
|
||||
void reset();
|
||||
void setStatePtrs(SaveState &state);
|
||||
void saveState(SaveState &state);
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
void generateSamples(unsigned long cycleCounter, bool doubleSpeed);
|
||||
void resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed);
|
||||
std::size_t fillBuffer();
|
||||
void setBuffer(uint_least32_t *buf, std::size_t size) { buffer_ = buf; bufferSize_ = size; bufferPos_ = 0; }
|
||||
|
||||
bool isEnabled() const { return enabled_; }
|
||||
void setEnabled(bool value) { enabled_ = value; }
|
||||
|
||||
void setNr10(unsigned data) { ch1_.setNr0(data); }
|
||||
void setNr11(unsigned data) { ch1_.setNr1(data); }
|
||||
void setNr12(unsigned data) { ch1_.setNr2(data); }
|
||||
void setNr13(unsigned data) { ch1_.setNr3(data); }
|
||||
void setNr14(unsigned data) { ch1_.setNr4(data); }
|
||||
|
||||
void setNr21(unsigned data) { ch2_.setNr1(data); }
|
||||
void setNr22(unsigned data) { ch2_.setNr2(data); }
|
||||
void setNr23(unsigned data) { ch2_.setNr3(data); }
|
||||
void setNr24(unsigned data) { ch2_.setNr4(data); }
|
||||
|
||||
void setNr30(unsigned data) { ch3_.setNr0(data); }
|
||||
void setNr31(unsigned data) { ch3_.setNr1(data); }
|
||||
void setNr32(unsigned data) { ch3_.setNr2(data); }
|
||||
void setNr33(unsigned data) { ch3_.setNr3(data); }
|
||||
void setNr34(unsigned data) { ch3_.setNr4(data); }
|
||||
unsigned waveRamRead(unsigned index) const { return ch3_.waveRamRead(index); }
|
||||
void waveRamWrite(unsigned index, unsigned data) { ch3_.waveRamWrite(index, data); }
|
||||
|
||||
void setNr41(unsigned data) { ch4_.setNr1(data); }
|
||||
void setNr42(unsigned data) { ch4_.setNr2(data); }
|
||||
void setNr43(unsigned data) { ch4_.setNr3(data); }
|
||||
void setNr44(unsigned data) { ch4_.setNr4(data); }
|
||||
|
||||
void setSoVolume(unsigned nr50);
|
||||
void mapSo(unsigned nr51);
|
||||
unsigned getStatus() const;
|
||||
|
||||
private:
|
||||
Channel1 ch1_;
|
||||
Channel2 ch2_;
|
||||
Channel3 ch3_;
|
||||
Channel4 ch4_;
|
||||
uint_least32_t *buffer_;
|
||||
std::size_t bufferSize_;
|
||||
std::size_t bufferPos_;
|
||||
unsigned long lastUpdate_;
|
||||
unsigned long soVol_;
|
||||
uint_least32_t rsum_;
|
||||
bool enabled_;
|
||||
|
||||
void accumulateChannels(unsigned long cycles);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,262 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "channel1.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Channel1::SweepUnit::SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit)
|
||||
: disableMaster_(disabler)
|
||||
, dutyUnit_(dutyUnit)
|
||||
, shadow_(0)
|
||||
, nr0_(0)
|
||||
, negging_(false)
|
||||
, cgb_(false)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned Channel1::SweepUnit::calcFreq() {
|
||||
unsigned freq = shadow_ >> (nr0_ & 0x07);
|
||||
|
||||
if (nr0_ & 0x08) {
|
||||
freq = shadow_ - freq;
|
||||
negging_ = true;
|
||||
} else
|
||||
freq = shadow_ + freq;
|
||||
|
||||
if (freq & 2048)
|
||||
disableMaster_();
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
void Channel1::SweepUnit::event() {
|
||||
unsigned long const period = nr0_ >> 4 & 0x07;
|
||||
|
||||
if (period) {
|
||||
unsigned const freq = calcFreq();
|
||||
|
||||
if (!(freq & 2048) && (nr0_ & 0x07)) {
|
||||
shadow_ = freq;
|
||||
dutyUnit_.setFreq(freq, counter_);
|
||||
calcFreq();
|
||||
}
|
||||
|
||||
counter_ += period << 14;
|
||||
} else
|
||||
counter_ += 8ul << 14;
|
||||
}
|
||||
|
||||
void Channel1::SweepUnit::nr0Change(unsigned newNr0) {
|
||||
if (negging_ && !(newNr0 & 0x08))
|
||||
disableMaster_();
|
||||
|
||||
nr0_ = newNr0;
|
||||
}
|
||||
|
||||
void Channel1::SweepUnit::nr4Init(unsigned long const cc) {
|
||||
negging_ = false;
|
||||
shadow_ = dutyUnit_.freq();
|
||||
|
||||
unsigned const period = nr0_ >> 4 & 0x07;
|
||||
unsigned const shift = nr0_ & 0x07;
|
||||
|
||||
if (period | shift)
|
||||
counter_ = ((((cc + 2 + cgb_ * 2) >> 14) + (period ? period : 8)) << 14) + 2;
|
||||
else
|
||||
counter_ = counter_disabled;
|
||||
|
||||
if (shift)
|
||||
calcFreq();
|
||||
}
|
||||
|
||||
void Channel1::SweepUnit::reset() {
|
||||
counter_ = counter_disabled;
|
||||
}
|
||||
|
||||
void Channel1::SweepUnit::saveState(SaveState &state) const {
|
||||
state.spu.ch1.sweep.counter = counter_;
|
||||
state.spu.ch1.sweep.shadow = shadow_;
|
||||
state.spu.ch1.sweep.nr0 = nr0_;
|
||||
state.spu.ch1.sweep.negging = negging_;
|
||||
}
|
||||
|
||||
void Channel1::SweepUnit::loadState(SaveState const &state) {
|
||||
counter_ = std::max(state.spu.ch1.sweep.counter, state.spu.cycleCounter);
|
||||
shadow_ = state.spu.ch1.sweep.shadow;
|
||||
nr0_ = state.spu.ch1.sweep.nr0;
|
||||
negging_ = state.spu.ch1.sweep.negging;
|
||||
}
|
||||
|
||||
Channel1::Channel1()
|
||||
: staticOutputTest_(*this, dutyUnit_)
|
||||
, disableMaster_(master_, dutyUnit_)
|
||||
, lengthCounter_(disableMaster_, 0x3F)
|
||||
, envelopeUnit_(staticOutputTest_)
|
||||
, sweepUnit_(disableMaster_, dutyUnit_)
|
||||
, nextEventUnit_(0)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, nr4_(0)
|
||||
, master_(false)
|
||||
{
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setEvent() {
|
||||
nextEventUnit_ = &sweepUnit_;
|
||||
if (envelopeUnit_.counter() < nextEventUnit_->counter())
|
||||
nextEventUnit_ = &envelopeUnit_;
|
||||
if (lengthCounter_.counter() < nextEventUnit_->counter())
|
||||
nextEventUnit_ = &lengthCounter_;
|
||||
}
|
||||
|
||||
void Channel1::setNr0(unsigned data) {
|
||||
sweepUnit_.nr0Change(data);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr1(unsigned data) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
|
||||
dutyUnit_.nr1Change(data, cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr2(unsigned data) {
|
||||
if (envelopeUnit_.nr2Change(data))
|
||||
disableMaster_();
|
||||
else
|
||||
staticOutputTest_(cycleCounter_);
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr3(unsigned data) {
|
||||
dutyUnit_.nr3Change(data, cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
nr4_ = data;
|
||||
dutyUnit_.nr4Change(data, cycleCounter_);
|
||||
|
||||
if (data & 0x80) { // init-bit
|
||||
nr4_ &= 0x7F;
|
||||
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
|
||||
sweepUnit_.nr4Init(cycleCounter_);
|
||||
staticOutputTest_(cycleCounter_);
|
||||
}
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setSo(unsigned long soMask) {
|
||||
soMask_ = soMask;
|
||||
staticOutputTest_(cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
dutyUnit_.reset();
|
||||
envelopeUnit_.reset();
|
||||
sweepUnit_.reset();
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::init(bool cgb) {
|
||||
sweepUnit_.init(cgb);
|
||||
}
|
||||
|
||||
void Channel1::saveState(SaveState &state) {
|
||||
sweepUnit_.saveState(state);
|
||||
dutyUnit_.saveState(state.spu.ch1.duty, cycleCounter_);
|
||||
envelopeUnit_.saveState(state.spu.ch1.env);
|
||||
lengthCounter_.saveState(state.spu.ch1.lcounter);
|
||||
|
||||
state.spu.cycleCounter = cycleCounter_;
|
||||
state.spu.ch1.nr4 = nr4_;
|
||||
state.spu.ch1.master = master_;
|
||||
}
|
||||
|
||||
void Channel1::loadState(SaveState const &state) {
|
||||
sweepUnit_.loadState(state);
|
||||
dutyUnit_.loadState(state.spu.ch1.duty, state.mem.ioamhram.get()[0x111],
|
||||
state.spu.ch1.nr4, state.spu.cycleCounter);
|
||||
envelopeUnit_.loadState(state.spu.ch1.env, state.mem.ioamhram.get()[0x112],
|
||||
state.spu.cycleCounter);
|
||||
lengthCounter_.loadState(state.spu.ch1.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
nr4_ = state.spu.ch1.nr4;
|
||||
master_ = state.spu.ch1.master;
|
||||
}
|
||||
|
||||
void Channel1::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
|
||||
unsigned long const outLow = outBase * (0 - 15ul);
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
|
||||
for (;;) {
|
||||
unsigned long const outHigh = master_
|
||||
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
|
||||
: outLow;
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
|
||||
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
|
||||
while (dutyUnit_.counter() <= nextMajorEvent) {
|
||||
*buf = out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += dutyUnit_.counter() - cycleCounter_;
|
||||
cycleCounter_ = dutyUnit_.counter();
|
||||
|
||||
dutyUnit_.event();
|
||||
out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
*buf = out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (nextEventUnit_->counter() == nextMajorEvent) {
|
||||
nextEventUnit_->event();
|
||||
setEvent();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
dutyUnit_.resetCounters(cycleCounter_);
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
envelopeUnit_.resetCounters(cycleCounter_);
|
||||
sweepUnit_.resetCounters(cycleCounter_);
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SOUND_CHANNEL1_H
|
||||
#define SOUND_CHANNEL1_H
|
||||
|
||||
#include "duty_unit.h"
|
||||
#include "envelope_unit.h"
|
||||
#include "gbint.h"
|
||||
#include "length_counter.h"
|
||||
#include "master_disabler.h"
|
||||
#include "static_output_tester.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class Channel1 {
|
||||
public:
|
||||
Channel1();
|
||||
void setNr0(unsigned data);
|
||||
void setNr1(unsigned data);
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data);
|
||||
void setNr4(unsigned data);
|
||||
void setSo(unsigned long soMask);
|
||||
bool isActive() const { return master_; }
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
void reset();
|
||||
void init(bool cgb);
|
||||
void saveState(SaveState &state);
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
private:
|
||||
class SweepUnit : public SoundUnit {
|
||||
public:
|
||||
SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit);
|
||||
virtual void event();
|
||||
void nr0Change(unsigned newNr0);
|
||||
void nr4Init(unsigned long cycleCounter);
|
||||
void reset();
|
||||
void init(bool cgb) { cgb_ = cgb; }
|
||||
void saveState(SaveState &state) const;
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
private:
|
||||
MasterDisabler &disableMaster_;
|
||||
DutyUnit &dutyUnit_;
|
||||
unsigned short shadow_;
|
||||
unsigned char nr0_;
|
||||
bool negging_;
|
||||
bool cgb_;
|
||||
|
||||
unsigned calcFreq();
|
||||
};
|
||||
|
||||
friend class StaticOutputTester<Channel1, DutyUnit>;
|
||||
|
||||
StaticOutputTester<Channel1, DutyUnit> staticOutputTest_;
|
||||
DutyMasterDisabler disableMaster_;
|
||||
LengthCounter lengthCounter_;
|
||||
DutyUnit dutyUnit_;
|
||||
EnvelopeUnit envelopeUnit_;
|
||||
SweepUnit sweepUnit_;
|
||||
SoundUnit *nextEventUnit_;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned char nr4_;
|
||||
bool master_;
|
||||
|
||||
void setEvent();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,160 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "channel2.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Channel2::Channel2()
|
||||
: staticOutputTest_(*this, dutyUnit_)
|
||||
, disableMaster_(master_, dutyUnit_)
|
||||
, lengthCounter_(disableMaster_, 0x3F)
|
||||
, envelopeUnit_(staticOutputTest_)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, nr4_(0)
|
||||
, master_(false)
|
||||
{
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setEvent() {
|
||||
nextEventUnit = &envelopeUnit_;
|
||||
if (lengthCounter_.counter() < nextEventUnit->counter())
|
||||
nextEventUnit = &lengthCounter_;
|
||||
}
|
||||
|
||||
void Channel2::setNr1(unsigned data) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
|
||||
dutyUnit_.nr1Change(data, cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setNr2(unsigned data) {
|
||||
if (envelopeUnit_.nr2Change(data))
|
||||
disableMaster_();
|
||||
else
|
||||
staticOutputTest_(cycleCounter_);
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setNr3(unsigned data) {
|
||||
dutyUnit_.nr3Change(data, cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
nr4_ = data;
|
||||
|
||||
if (data & 0x80) { // init-bit
|
||||
nr4_ &= 0x7F;
|
||||
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
|
||||
staticOutputTest_(cycleCounter_);
|
||||
}
|
||||
|
||||
dutyUnit_.nr4Change(data, cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setSo(unsigned long soMask) {
|
||||
soMask_ = soMask;
|
||||
staticOutputTest_(cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
dutyUnit_.reset();
|
||||
envelopeUnit_.reset();
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::saveState(SaveState &state) {
|
||||
dutyUnit_.saveState(state.spu.ch2.duty, cycleCounter_);
|
||||
envelopeUnit_.saveState(state.spu.ch2.env);
|
||||
lengthCounter_.saveState(state.spu.ch2.lcounter);
|
||||
|
||||
state.spu.ch2.nr4 = nr4_;
|
||||
state.spu.ch2.master = master_;
|
||||
}
|
||||
|
||||
void Channel2::loadState(SaveState const &state) {
|
||||
dutyUnit_.loadState(state.spu.ch2.duty, state.mem.ioamhram.get()[0x116],
|
||||
state.spu.ch2.nr4, state.spu.cycleCounter);
|
||||
envelopeUnit_.loadState(state.spu.ch2.env, state.mem.ioamhram.get()[0x117],
|
||||
state.spu.cycleCounter);
|
||||
lengthCounter_.loadState(state.spu.ch2.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
nr4_ = state.spu.ch2.nr4;
|
||||
master_ = state.spu.ch2.master;
|
||||
}
|
||||
|
||||
void Channel2::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
|
||||
unsigned long const outLow = outBase * (0 - 15ul);
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
|
||||
for (;;) {
|
||||
unsigned long const outHigh = master_
|
||||
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
|
||||
: outLow;
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit->counter(), endCycles);
|
||||
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
|
||||
while (dutyUnit_.counter() <= nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += dutyUnit_.counter() - cycleCounter_;
|
||||
cycleCounter_ = dutyUnit_.counter();
|
||||
|
||||
dutyUnit_.event();
|
||||
out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (nextEventUnit->counter() == nextMajorEvent) {
|
||||
nextEventUnit->event();
|
||||
setEvent();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
dutyUnit_.resetCounters(cycleCounter_);
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
envelopeUnit_.resetCounters(cycleCounter_);
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SOUND_CHANNEL2_H
|
||||
#define SOUND_CHANNEL2_H
|
||||
|
||||
#include "duty_unit.h"
|
||||
#include "envelope_unit.h"
|
||||
#include "gbint.h"
|
||||
#include "length_counter.h"
|
||||
#include "static_output_tester.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class Channel2 {
|
||||
public:
|
||||
Channel2();
|
||||
void setNr1(unsigned data);
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data);
|
||||
void setNr4(unsigned data);
|
||||
void setSo(unsigned long soMask);
|
||||
bool isActive() const { return master_; }
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
void reset();
|
||||
void saveState(SaveState &state);
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
private:
|
||||
friend class StaticOutputTester<Channel2, DutyUnit>;
|
||||
|
||||
StaticOutputTester<Channel2, DutyUnit> staticOutputTest_;
|
||||
DutyMasterDisabler disableMaster_;
|
||||
LengthCounter lengthCounter_;
|
||||
DutyUnit dutyUnit_;
|
||||
EnvelopeUnit envelopeUnit_;
|
||||
SoundUnit *nextEventUnit;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned char nr4_;
|
||||
bool master_;
|
||||
|
||||
void setEvent();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "channel3.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
static inline unsigned toPeriod(unsigned nr3, unsigned nr4) {
|
||||
return 0x800 - ((nr4 << 8 & 0x700) | nr3);
|
||||
}
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Channel3::Channel3()
|
||||
: disableMaster_(master_, waveCounter_)
|
||||
, lengthCounter_(disableMaster_, 0xFF)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, waveCounter_(SoundUnit::counter_disabled)
|
||||
, lastReadTime_(0)
|
||||
, nr0_(0)
|
||||
, nr3_(0)
|
||||
, nr4_(0)
|
||||
, wavePos_(0)
|
||||
, rshift_(4)
|
||||
, sampleBuf_(0)
|
||||
, master_(false)
|
||||
, cgb_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void Channel3::setNr0(unsigned data) {
|
||||
nr0_ = data & 0x80;
|
||||
|
||||
if (!(data & 0x80))
|
||||
disableMaster_();
|
||||
}
|
||||
|
||||
void Channel3::setNr2(unsigned data) {
|
||||
rshift_ = (data >> 5 & 3U) - 1;
|
||||
if (rshift_ > 3)
|
||||
rshift_ = 4;
|
||||
}
|
||||
|
||||
void Channel3::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
nr4_ = data & 0x7F;
|
||||
|
||||
if (data & nr0_/* & 0x80*/) {
|
||||
if (!cgb_ && waveCounter_ == cycleCounter_ + 1) {
|
||||
unsigned const pos = ((wavePos_ + 1) & 0x1F) >> 1;
|
||||
|
||||
if (pos < 4)
|
||||
waveRam_[0] = waveRam_[pos];
|
||||
else
|
||||
std::memcpy(waveRam_, waveRam_ + (pos & ~3), 4);
|
||||
}
|
||||
|
||||
master_ = true;
|
||||
wavePos_ = 0;
|
||||
lastReadTime_ = waveCounter_ = cycleCounter_ + toPeriod(nr3_, data) + 3;
|
||||
}
|
||||
}
|
||||
|
||||
void Channel3::setSo(unsigned long soMask) {
|
||||
soMask_ = soMask;
|
||||
}
|
||||
|
||||
void Channel3::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
sampleBuf_ = 0;
|
||||
}
|
||||
|
||||
void Channel3::init(bool cgb) {
|
||||
cgb_ = cgb;
|
||||
}
|
||||
|
||||
void Channel3::setStatePtrs(SaveState &state) {
|
||||
state.spu.ch3.waveRam.set(waveRam_, sizeof waveRam_);
|
||||
}
|
||||
|
||||
void Channel3::saveState(SaveState &state) const {
|
||||
lengthCounter_.saveState(state.spu.ch3.lcounter);
|
||||
|
||||
state.spu.ch3.waveCounter = waveCounter_;
|
||||
state.spu.ch3.lastReadTime = lastReadTime_;
|
||||
state.spu.ch3.nr3 = nr3_;
|
||||
state.spu.ch3.nr4 = nr4_;
|
||||
state.spu.ch3.wavePos = wavePos_;
|
||||
state.spu.ch3.sampleBuf = sampleBuf_;
|
||||
state.spu.ch3.master = master_;
|
||||
}
|
||||
|
||||
void Channel3::loadState(SaveState const &state) {
|
||||
lengthCounter_.loadState(state.spu.ch3.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
waveCounter_ = std::max(state.spu.ch3.waveCounter, state.spu.cycleCounter);
|
||||
lastReadTime_ = state.spu.ch3.lastReadTime;
|
||||
nr3_ = state.spu.ch3.nr3;
|
||||
nr4_ = state.spu.ch3.nr4;
|
||||
wavePos_ = state.spu.ch3.wavePos & 0x1F;
|
||||
sampleBuf_ = state.spu.ch3.sampleBuf;
|
||||
master_ = state.spu.ch3.master;
|
||||
|
||||
nr0_ = state.mem.ioamhram.get()[0x11A] & 0x80;
|
||||
setNr2(state.mem.ioamhram.get()[0x11C]);
|
||||
}
|
||||
|
||||
void Channel3::updateWaveCounter(unsigned long const cc) {
|
||||
if (cc >= waveCounter_) {
|
||||
unsigned const period = toPeriod(nr3_, nr4_);
|
||||
unsigned long const periods = (cc - waveCounter_) / period;
|
||||
|
||||
lastReadTime_ = waveCounter_ + periods * period;
|
||||
waveCounter_ = lastReadTime_ + period;
|
||||
|
||||
wavePos_ += periods + 1;
|
||||
wavePos_ &= 0x1F;
|
||||
|
||||
sampleBuf_ = waveRam_[wavePos_ >> 1];
|
||||
}
|
||||
}
|
||||
|
||||
void Channel3::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
unsigned long const outBase = nr0_/* & 0x80*/ ? soBaseVol & soMask_ : 0;
|
||||
|
||||
if (outBase && rshift_ != 4) {
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
|
||||
for (;;) {
|
||||
unsigned long const nextMajorEvent =
|
||||
std::min(lengthCounter_.counter(), endCycles);
|
||||
unsigned long out = master_
|
||||
? ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul
|
||||
: 0 - 15ul;
|
||||
out *= outBase;
|
||||
|
||||
while (waveCounter_ <= nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += waveCounter_ - cycleCounter_;
|
||||
cycleCounter_ = waveCounter_;
|
||||
|
||||
lastReadTime_ = waveCounter_;
|
||||
waveCounter_ += toPeriod(nr3_, nr4_);
|
||||
++wavePos_;
|
||||
wavePos_ &= 0x1F;
|
||||
sampleBuf_ = waveRam_[wavePos_ >> 1];
|
||||
out = ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul;
|
||||
out *= outBase;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (lengthCounter_.counter() == nextMajorEvent) {
|
||||
lengthCounter_.event();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
unsigned long const out = outBase * (0 - 15ul);
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
cycleCounter_ += cycles;
|
||||
|
||||
while (lengthCounter_.counter() <= cycleCounter_) {
|
||||
updateWaveCounter(lengthCounter_.counter());
|
||||
lengthCounter_.event();
|
||||
}
|
||||
|
||||
updateWaveCounter(cycleCounter_);
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
|
||||
if (waveCounter_ != SoundUnit::counter_disabled)
|
||||
waveCounter_ -= SoundUnit::counter_max;
|
||||
|
||||
lastReadTime_ -= SoundUnit::counter_max;
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SOUND_CHANNEL3_H
|
||||
#define SOUND_CHANNEL3_H
|
||||
|
||||
#include "gbint.h"
|
||||
#include "length_counter.h"
|
||||
#include "master_disabler.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class Channel3 {
|
||||
public:
|
||||
Channel3();
|
||||
bool isActive() const { return master_; }
|
||||
void reset();
|
||||
void init(bool cgb);
|
||||
void setStatePtrs(SaveState &state);
|
||||
void saveState(SaveState &state) const;
|
||||
void loadState(const SaveState &state);
|
||||
void setNr0(unsigned data);
|
||||
void setNr1(unsigned data) { lengthCounter_.nr1Change(data, nr4_, cycleCounter_); }
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data) { nr3_ = data; }
|
||||
void setNr4(unsigned data);
|
||||
void setSo(unsigned long soMask);
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
|
||||
unsigned waveRamRead(unsigned index) const {
|
||||
if (master_) {
|
||||
if (!cgb_ && cycleCounter_ != lastReadTime_)
|
||||
return 0xFF;
|
||||
|
||||
index = wavePos_ >> 1;
|
||||
}
|
||||
|
||||
return waveRam_[index];
|
||||
}
|
||||
|
||||
void waveRamWrite(unsigned index, unsigned data) {
|
||||
if (master_) {
|
||||
if (!cgb_ && cycleCounter_ != lastReadTime_)
|
||||
return;
|
||||
|
||||
index = wavePos_ >> 1;
|
||||
}
|
||||
|
||||
waveRam_[index] = data;
|
||||
}
|
||||
|
||||
private:
|
||||
class Ch3MasterDisabler : public MasterDisabler {
|
||||
public:
|
||||
Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter_(wC) {}
|
||||
|
||||
virtual void operator()() {
|
||||
MasterDisabler::operator()();
|
||||
waveCounter_ = SoundUnit::counter_disabled;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned long &waveCounter_;
|
||||
};
|
||||
|
||||
unsigned char waveRam_[0x10];
|
||||
Ch3MasterDisabler disableMaster_;
|
||||
LengthCounter lengthCounter_;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned long waveCounter_;
|
||||
unsigned long lastReadTime_;
|
||||
unsigned char nr0_;
|
||||
unsigned char nr3_;
|
||||
unsigned char nr4_;
|
||||
unsigned char wavePos_;
|
||||
unsigned char rshift_;
|
||||
unsigned char sampleBuf_;
|
||||
bool master_;
|
||||
bool cgb_;
|
||||
|
||||
void updateWaveCounter(unsigned long cc);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,261 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "channel4.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
|
||||
static unsigned long toPeriod(unsigned const nr3) {
|
||||
unsigned s = (nr3 >> 4) + 3;
|
||||
unsigned r = nr3 & 7;
|
||||
|
||||
if (!r) {
|
||||
r = 1;
|
||||
--s;
|
||||
}
|
||||
|
||||
return r << s;
|
||||
}
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Channel4::Lfsr::Lfsr()
|
||||
: backupCounter_(counter_disabled)
|
||||
, reg_(0x7FFF)
|
||||
, nr3_(0)
|
||||
, master_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::updateBackupCounter(unsigned long const cc) {
|
||||
if (backupCounter_ <= cc) {
|
||||
unsigned long const period = toPeriod(nr3_);
|
||||
unsigned long periods = (cc - backupCounter_) / period + 1;
|
||||
backupCounter_ += periods * period;
|
||||
|
||||
if (master_ && nr3_ < 0xE0) {
|
||||
if (nr3_ & 8) {
|
||||
while (periods > 6) {
|
||||
unsigned const xored = (reg_ << 1 ^ reg_) & 0x7E;
|
||||
reg_ = (reg_ >> 6 & ~0x7E) | xored | xored << 8;
|
||||
periods -= 6;
|
||||
}
|
||||
|
||||
unsigned const xored = ((reg_ ^ reg_ >> 1) << (7 - periods)) & 0x7F;
|
||||
reg_ = (reg_ >> periods & ~(0x80 - (0x80 >> periods))) | xored | xored << 8;
|
||||
} else {
|
||||
while (periods > 15) {
|
||||
reg_ = reg_ ^ reg_ >> 1;
|
||||
periods -= 15;
|
||||
}
|
||||
|
||||
reg_ = reg_ >> periods | (((reg_ ^ reg_ >> 1) << (15 - periods)) & 0x7FFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::reviveCounter(unsigned long cc) {
|
||||
updateBackupCounter(cc);
|
||||
counter_ = backupCounter_;
|
||||
}
|
||||
|
||||
inline void Channel4::Lfsr::event() {
|
||||
if (nr3_ < 0xE0) {
|
||||
unsigned const shifted = reg_ >> 1;
|
||||
unsigned const xored = (reg_ ^ shifted) & 1;
|
||||
reg_ = shifted | xored << 14;
|
||||
|
||||
if (nr3_ & 8)
|
||||
reg_ = (reg_ & ~0x40) | xored << 6;
|
||||
}
|
||||
|
||||
counter_ += toPeriod(nr3_);
|
||||
backupCounter_ = counter_;
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::nr3Change(unsigned newNr3, unsigned long cc) {
|
||||
updateBackupCounter(cc);
|
||||
nr3_ = newNr3;
|
||||
counter_ = cc;
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::nr4Init(unsigned long cc) {
|
||||
disableMaster();
|
||||
updateBackupCounter(cc);
|
||||
master_ = true;
|
||||
backupCounter_ += 4;
|
||||
counter_ = backupCounter_;
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::reset(unsigned long cc) {
|
||||
nr3_ = 0;
|
||||
disableMaster();
|
||||
backupCounter_ = cc + toPeriod(nr3_);
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::resetCounters(unsigned long oldCc) {
|
||||
updateBackupCounter(oldCc);
|
||||
backupCounter_ -= counter_max;
|
||||
SoundUnit::resetCounters(oldCc);
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::saveState(SaveState &state, unsigned long cc) {
|
||||
updateBackupCounter(cc);
|
||||
state.spu.ch4.lfsr.counter = backupCounter_;
|
||||
state.spu.ch4.lfsr.reg = reg_;
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::loadState(SaveState const &state) {
|
||||
counter_ = backupCounter_ = std::max(state.spu.ch4.lfsr.counter, state.spu.cycleCounter);
|
||||
reg_ = state.spu.ch4.lfsr.reg;
|
||||
master_ = state.spu.ch4.master;
|
||||
nr3_ = state.mem.ioamhram.get()[0x122];
|
||||
}
|
||||
|
||||
Channel4::Channel4()
|
||||
: staticOutputTest_(*this, lfsr_)
|
||||
, disableMaster_(master_, lfsr_)
|
||||
, lengthCounter_(disableMaster_, 0x3F)
|
||||
, envelopeUnit_(staticOutputTest_)
|
||||
, nextEventUnit_(0)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, nr4_(0)
|
||||
, master_(false)
|
||||
{
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::setEvent() {
|
||||
nextEventUnit_ = &envelopeUnit_;
|
||||
if (lengthCounter_.counter() < nextEventUnit_->counter())
|
||||
nextEventUnit_ = &lengthCounter_;
|
||||
}
|
||||
|
||||
void Channel4::setNr1(unsigned data) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::setNr2(unsigned data) {
|
||||
if (envelopeUnit_.nr2Change(data))
|
||||
disableMaster_();
|
||||
else
|
||||
staticOutputTest_(cycleCounter_);
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
nr4_ = data;
|
||||
|
||||
if (data & 0x80) { // init-bit
|
||||
nr4_ &= 0x7F;
|
||||
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
|
||||
|
||||
if (master_)
|
||||
lfsr_.nr4Init(cycleCounter_);
|
||||
|
||||
staticOutputTest_(cycleCounter_);
|
||||
}
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::setSo(unsigned long soMask) {
|
||||
soMask_ = soMask;
|
||||
staticOutputTest_(cycleCounter_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
lfsr_.reset(cycleCounter_);
|
||||
envelopeUnit_.reset();
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::saveState(SaveState &state) {
|
||||
lfsr_.saveState(state, cycleCounter_);
|
||||
envelopeUnit_.saveState(state.spu.ch4.env);
|
||||
lengthCounter_.saveState(state.spu.ch4.lcounter);
|
||||
|
||||
state.spu.ch4.nr4 = nr4_;
|
||||
state.spu.ch4.master = master_;
|
||||
}
|
||||
|
||||
void Channel4::loadState(SaveState const &state) {
|
||||
lfsr_.loadState(state);
|
||||
envelopeUnit_.loadState(state.spu.ch4.env, state.mem.ioamhram.get()[0x121],
|
||||
state.spu.cycleCounter);
|
||||
lengthCounter_.loadState(state.spu.ch4.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
nr4_ = state.spu.ch4.nr4;
|
||||
master_ = state.spu.ch4.master;
|
||||
}
|
||||
|
||||
void Channel4::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
|
||||
unsigned long const outLow = outBase * (0 - 15ul);
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
|
||||
for (;;) {
|
||||
unsigned long const outHigh = outBase * (envelopeUnit_.getVolume() * 2 - 15ul);
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
|
||||
unsigned long out = lfsr_.isHighState() ? outHigh : outLow;
|
||||
|
||||
while (lfsr_.counter() <= nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += lfsr_.counter() - cycleCounter_;
|
||||
cycleCounter_ = lfsr_.counter();
|
||||
|
||||
lfsr_.event();
|
||||
out = lfsr_.isHighState() ? outHigh : outLow;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (nextEventUnit_->counter() == nextMajorEvent) {
|
||||
nextEventUnit_->event();
|
||||
setEvent();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
lfsr_.resetCounters(cycleCounter_);
|
||||
envelopeUnit_.resetCounters(cycleCounter_);
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SOUND_CHANNEL4_H
|
||||
#define SOUND_CHANNEL4_H
|
||||
|
||||
#include "envelope_unit.h"
|
||||
#include "gbint.h"
|
||||
#include "length_counter.h"
|
||||
#include "master_disabler.h"
|
||||
#include "static_output_tester.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class Channel4 {
|
||||
public:
|
||||
Channel4();
|
||||
void setNr1(unsigned data);
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data) { lfsr_.nr3Change(data, cycleCounter_); }
|
||||
void setNr4(unsigned data);
|
||||
void setSo(unsigned long soMask);
|
||||
bool isActive() const { return master_; }
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
void reset();
|
||||
void saveState(SaveState &state);
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
private:
|
||||
class Lfsr : public SoundUnit {
|
||||
public:
|
||||
Lfsr();
|
||||
virtual void event();
|
||||
virtual void resetCounters(unsigned long oldCc);
|
||||
bool isHighState() const { return ~reg_ & 1; }
|
||||
void nr3Change(unsigned newNr3, unsigned long cc);
|
||||
void nr4Init(unsigned long cc);
|
||||
void reset(unsigned long cc);
|
||||
void saveState(SaveState &state, unsigned long cc);
|
||||
void loadState(SaveState const &state);
|
||||
void disableMaster() { killCounter(); master_ = false; reg_ = 0x7FFF; }
|
||||
void killCounter() { counter_ = counter_disabled; }
|
||||
void reviveCounter(unsigned long cc);
|
||||
|
||||
private:
|
||||
unsigned long backupCounter_;
|
||||
unsigned short reg_;
|
||||
unsigned char nr3_;
|
||||
bool master_;
|
||||
|
||||
void updateBackupCounter(unsigned long cc);
|
||||
};
|
||||
|
||||
class Ch4MasterDisabler : public MasterDisabler {
|
||||
public:
|
||||
Ch4MasterDisabler(bool &m, Lfsr &lfsr) : MasterDisabler(m), lfsr_(lfsr) {}
|
||||
virtual void operator()() { MasterDisabler::operator()(); lfsr_.disableMaster(); }
|
||||
|
||||
private:
|
||||
Lfsr &lfsr_;
|
||||
};
|
||||
|
||||
friend class StaticOutputTester<Channel4, Lfsr>;
|
||||
|
||||
StaticOutputTester<Channel4, Lfsr> staticOutputTest_;
|
||||
Ch4MasterDisabler disableMaster_;
|
||||
LengthCounter lengthCounter_;
|
||||
EnvelopeUnit envelopeUnit_;
|
||||
Lfsr lfsr_;
|
||||
SoundUnit *nextEventUnit_;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned char nr4_;
|
||||
bool master_;
|
||||
|
||||
void setEvent();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "duty_unit.h"
|
||||
#include <algorithm>
|
||||
|
||||
static inline bool toOutState(unsigned duty, unsigned pos) {
|
||||
return 0x7EE18180 >> (duty * 8 + pos) & 1;
|
||||
}
|
||||
|
||||
static inline unsigned toPeriod(unsigned freq) {
|
||||
return (2048 - freq) * 2;
|
||||
}
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
DutyUnit::DutyUnit()
|
||||
: nextPosUpdate_(counter_disabled)
|
||||
, period_(4096)
|
||||
, pos_(0)
|
||||
, duty_(0)
|
||||
, inc_(0)
|
||||
, high_(false)
|
||||
, enableEvents_(true)
|
||||
{
|
||||
}
|
||||
|
||||
void DutyUnit::updatePos(unsigned long const cc) {
|
||||
if (cc >= nextPosUpdate_) {
|
||||
unsigned long const inc = (cc - nextPosUpdate_) / period_ + 1;
|
||||
nextPosUpdate_ += period_ * inc;
|
||||
pos_ += inc;
|
||||
pos_ &= 7;
|
||||
high_ = toOutState(duty_, pos_);
|
||||
}
|
||||
}
|
||||
|
||||
void DutyUnit::setCounter() {
|
||||
static unsigned char const nextStateDistance[4 * 8] = {
|
||||
7, 6, 5, 4, 3, 2, 1, 1,
|
||||
1, 6, 5, 4, 3, 2, 1, 2,
|
||||
1, 4, 3, 2, 1, 4, 3, 2,
|
||||
1, 6, 5, 4, 3, 2, 1, 2
|
||||
};
|
||||
|
||||
if (enableEvents_ && nextPosUpdate_ != counter_disabled) {
|
||||
unsigned const npos = (pos_ + 1) & 7;
|
||||
counter_ = nextPosUpdate_;
|
||||
inc_ = nextStateDistance[duty_ * 8 + npos];
|
||||
if (toOutState(duty_, npos) == high_) {
|
||||
counter_ += period_ * inc_;
|
||||
inc_ = nextStateDistance[duty_ * 8 + ((npos + inc_) & 7)];
|
||||
}
|
||||
} else
|
||||
counter_ = counter_disabled;
|
||||
}
|
||||
|
||||
void DutyUnit::setFreq(unsigned newFreq, unsigned long cc) {
|
||||
updatePos(cc);
|
||||
period_ = toPeriod(newFreq);
|
||||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::event() {
|
||||
static unsigned char const inc[] = {
|
||||
1, 7,
|
||||
2, 6,
|
||||
4, 4,
|
||||
6, 2,
|
||||
};
|
||||
|
||||
high_ ^= true;
|
||||
counter_ += inc_ * period_;
|
||||
inc_ = inc[duty_ * 2 + high_];
|
||||
}
|
||||
|
||||
void DutyUnit::nr1Change(unsigned newNr1, unsigned long cc) {
|
||||
updatePos(cc);
|
||||
duty_ = newNr1 >> 6;
|
||||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::nr3Change(unsigned newNr3, unsigned long cc) {
|
||||
setFreq((freq() & 0x700) | newNr3, cc);
|
||||
}
|
||||
|
||||
void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc) {
|
||||
setFreq((newNr4 << 8 & 0x700) | (freq() & 0xFF), cc);
|
||||
|
||||
if (newNr4 & 0x80) {
|
||||
nextPosUpdate_ = (cc & ~1ul) + period_ + 4;
|
||||
setCounter();
|
||||
}
|
||||
}
|
||||
|
||||
void DutyUnit::reset() {
|
||||
pos_ = 0;
|
||||
high_ = false;
|
||||
nextPosUpdate_ = counter_disabled;
|
||||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::saveState(SaveState::SPU::Duty &dstate, unsigned long const cc) {
|
||||
updatePos(cc);
|
||||
setCounter();
|
||||
dstate.nextPosUpdate = nextPosUpdate_;
|
||||
dstate.nr3 = freq() & 0xFF;
|
||||
dstate.pos = pos_;
|
||||
dstate.high = high_;
|
||||
}
|
||||
|
||||
void DutyUnit::loadState(SaveState::SPU::Duty const &dstate,
|
||||
unsigned const nr1, unsigned const nr4, unsigned long const cc) {
|
||||
nextPosUpdate_ = std::max(dstate.nextPosUpdate, cc);
|
||||
pos_ = dstate.pos & 7;
|
||||
high_ = dstate.high;
|
||||
duty_ = nr1 >> 6;
|
||||
period_ = toPeriod((nr4 << 8 & 0x700) | dstate.nr3);
|
||||
enableEvents_ = true;
|
||||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::resetCounters(unsigned long const oldCc) {
|
||||
if (nextPosUpdate_ == counter_disabled)
|
||||
return;
|
||||
|
||||
updatePos(oldCc);
|
||||
nextPosUpdate_ -= counter_max;
|
||||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::killCounter() {
|
||||
enableEvents_ = false;
|
||||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::reviveCounter(unsigned long const cc) {
|
||||
updatePos(cc);
|
||||
enableEvents_ = true;
|
||||
setCounter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef DUTY_UNIT_H
|
||||
#define DUTY_UNIT_H
|
||||
|
||||
#include "sound_unit.h"
|
||||
#include "master_disabler.h"
|
||||
#include "../savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class DutyUnit : public SoundUnit {
|
||||
public:
|
||||
DutyUnit();
|
||||
virtual void event();
|
||||
virtual void resetCounters(unsigned long oldCc);
|
||||
bool isHighState() const { return high_; }
|
||||
void nr1Change(unsigned newNr1, unsigned long cc);
|
||||
void nr3Change(unsigned newNr3, unsigned long cc);
|
||||
void nr4Change(unsigned newNr4, unsigned long cc);
|
||||
void reset();
|
||||
void saveState(SaveState::SPU::Duty &dstate, unsigned long cc);
|
||||
void loadState(SaveState::SPU::Duty const &dstate, unsigned nr1, unsigned nr4, unsigned long cc);
|
||||
void killCounter();
|
||||
void reviveCounter(unsigned long cc);
|
||||
|
||||
//intended for use by SweepUnit only.
|
||||
unsigned freq() const { return 2048 - (period_ >> 1); }
|
||||
void setFreq(unsigned newFreq, unsigned long cc);
|
||||
|
||||
private:
|
||||
unsigned long nextPosUpdate_;
|
||||
unsigned short period_;
|
||||
unsigned char pos_;
|
||||
unsigned char duty_;
|
||||
unsigned char inc_;
|
||||
bool high_;
|
||||
bool enableEvents_;
|
||||
|
||||
void setCounter();
|
||||
void setDuty(unsigned nr1);
|
||||
void updatePos(unsigned long cc);
|
||||
};
|
||||
|
||||
class DutyMasterDisabler : public MasterDisabler {
|
||||
public:
|
||||
DutyMasterDisabler(bool &m, DutyUnit &dutyUnit) : MasterDisabler(m), dutyUnit_(dutyUnit) {}
|
||||
virtual void operator()() { MasterDisabler::operator()(); dutyUnit_.killCounter(); }
|
||||
|
||||
private:
|
||||
DutyUnit &dutyUnit_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "envelope_unit.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
EnvelopeUnit::VolOnOffEvent EnvelopeUnit::nullEvent_;
|
||||
|
||||
EnvelopeUnit::EnvelopeUnit(VolOnOffEvent &volOnOffEvent)
|
||||
: volOnOffEvent_(volOnOffEvent)
|
||||
, nr2_(0)
|
||||
, volume_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void EnvelopeUnit::reset() {
|
||||
counter_ = counter_disabled;
|
||||
}
|
||||
|
||||
void EnvelopeUnit::saveState(SaveState::SPU::Env &estate) const {
|
||||
estate.counter = counter_;
|
||||
estate.volume = volume_;
|
||||
}
|
||||
|
||||
void EnvelopeUnit::loadState(SaveState::SPU::Env const &estate, unsigned nr2, unsigned long cc) {
|
||||
counter_ = std::max(estate.counter, cc);
|
||||
volume_ = estate.volume;
|
||||
nr2_ = nr2;
|
||||
}
|
||||
|
||||
void EnvelopeUnit::event() {
|
||||
unsigned long const period = nr2_ & 7;
|
||||
|
||||
if (period) {
|
||||
unsigned newVol = volume_;
|
||||
if (nr2_ & 8)
|
||||
++newVol;
|
||||
else
|
||||
--newVol;
|
||||
|
||||
if (newVol < 0x10U) {
|
||||
volume_ = newVol;
|
||||
if (volume_ < 2)
|
||||
volOnOffEvent_(counter_);
|
||||
|
||||
counter_ += period << 15;
|
||||
} else
|
||||
counter_ = counter_disabled;
|
||||
} else
|
||||
counter_ += 8ul << 15;
|
||||
}
|
||||
|
||||
bool EnvelopeUnit::nr2Change(unsigned const newNr2) {
|
||||
if (!(nr2_ & 7) && counter_ != counter_disabled)
|
||||
++volume_;
|
||||
else if (!(nr2_ & 8))
|
||||
volume_ += 2;
|
||||
|
||||
if ((nr2_ ^ newNr2) & 8)
|
||||
volume_ = 0x10 - volume_;
|
||||
|
||||
volume_ &= 0xF;
|
||||
nr2_ = newNr2;
|
||||
return !(newNr2 & 0xF8);
|
||||
}
|
||||
|
||||
bool EnvelopeUnit::nr4Init(unsigned long const cc) {
|
||||
unsigned long period = (nr2_ & 7) ? nr2_ & 7 : 8;
|
||||
|
||||
if (((cc + 2) & 0x7000) == 0x0000)
|
||||
++period;
|
||||
|
||||
counter_ = cc - ((cc - 0x1000) & 0x7FFF) + period * 0x8000;
|
||||
|
||||
volume_ = nr2_ >> 4;
|
||||
return !(nr2_ & 0xF8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef ENVELOPE_UNIT_H
|
||||
#define ENVELOPE_UNIT_H
|
||||
|
||||
#include "sound_unit.h"
|
||||
#include "../savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class EnvelopeUnit : public SoundUnit {
|
||||
public:
|
||||
struct VolOnOffEvent {
|
||||
virtual ~VolOnOffEvent() {}
|
||||
virtual void operator()(unsigned long /*cc*/) {}
|
||||
};
|
||||
|
||||
explicit EnvelopeUnit(VolOnOffEvent &volOnOffEvent = nullEvent_);
|
||||
void event();
|
||||
bool dacIsOn() const { return nr2_ & 0xF8; }
|
||||
unsigned getVolume() const { return volume_; }
|
||||
bool nr2Change(unsigned newNr2);
|
||||
bool nr4Init(unsigned long cycleCounter);
|
||||
void reset();
|
||||
void saveState(SaveState::SPU::Env &estate) const;
|
||||
void loadState(SaveState::SPU::Env const &estate, unsigned nr2, unsigned long cc);
|
||||
|
||||
private:
|
||||
static VolOnOffEvent nullEvent_;
|
||||
VolOnOffEvent &volOnOffEvent_;
|
||||
unsigned char nr2_;
|
||||
unsigned char volume_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "length_counter.h"
|
||||
#include "master_disabler.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
LengthCounter::LengthCounter(MasterDisabler &disabler, unsigned const mask)
|
||||
: disableMaster_(disabler)
|
||||
, lengthCounter_(0)
|
||||
, lengthMask_(mask)
|
||||
{
|
||||
nr1Change(0, 0, 0);
|
||||
}
|
||||
|
||||
void LengthCounter::event() {
|
||||
counter_ = counter_disabled;
|
||||
lengthCounter_ = 0;
|
||||
disableMaster_();
|
||||
}
|
||||
|
||||
void LengthCounter::nr1Change(unsigned const newNr1, unsigned const nr4, unsigned long const cc) {
|
||||
lengthCounter_ = (~newNr1 & lengthMask_) + 1;
|
||||
counter_ = (nr4 & 0x40)
|
||||
? ((cc >> 13) + lengthCounter_) << 13
|
||||
: static_cast<unsigned long>(counter_disabled);
|
||||
}
|
||||
|
||||
void LengthCounter::nr4Change(unsigned const oldNr4, unsigned const newNr4, unsigned long const cc) {
|
||||
if (counter_ != counter_disabled)
|
||||
lengthCounter_ = (counter_ >> 13) - (cc >> 13);
|
||||
|
||||
{
|
||||
unsigned dec = 0;
|
||||
|
||||
if (newNr4 & 0x40) {
|
||||
dec = ~cc >> 12 & 1;
|
||||
|
||||
if (!(oldNr4 & 0x40) && lengthCounter_) {
|
||||
if (!(lengthCounter_ -= dec))
|
||||
disableMaster_();
|
||||
}
|
||||
}
|
||||
|
||||
if ((newNr4 & 0x80) && !lengthCounter_)
|
||||
lengthCounter_ = lengthMask_ + 1 - dec;
|
||||
}
|
||||
|
||||
if ((newNr4 & 0x40) && lengthCounter_)
|
||||
counter_ = ((cc >> 13) + lengthCounter_) << 13;
|
||||
else
|
||||
counter_ = counter_disabled;
|
||||
}
|
||||
|
||||
void LengthCounter::saveState(SaveState::SPU::LCounter &lstate) const {
|
||||
lstate.counter = counter_;
|
||||
lstate.lengthCounter = lengthCounter_;
|
||||
}
|
||||
|
||||
void LengthCounter::loadState(SaveState::SPU::LCounter const &lstate, unsigned long const cc) {
|
||||
counter_ = std::max(lstate.counter, cc);
|
||||
lengthCounter_ = lstate.lengthCounter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef LENGTH_COUNTER_H
|
||||
#define LENGTH_COUNTER_H
|
||||
|
||||
#include "sound_unit.h"
|
||||
#include "../savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class MasterDisabler;
|
||||
|
||||
class LengthCounter : public SoundUnit {
|
||||
public:
|
||||
LengthCounter(MasterDisabler &disabler, unsigned lengthMask);
|
||||
virtual void event();
|
||||
void nr1Change(unsigned newNr1, unsigned nr4, unsigned long cc);
|
||||
void nr4Change(unsigned oldNr4, unsigned newNr4, unsigned long cc);
|
||||
void saveState(SaveState::SPU::LCounter &lstate) const;
|
||||
void loadState(SaveState::SPU::LCounter const &lstate, unsigned long cc);
|
||||
|
||||
private:
|
||||
MasterDisabler &disableMaster_;
|
||||
unsigned short lengthCounter_;
|
||||
unsigned char const lengthMask_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef MASTER_DISABLER_H
|
||||
#define MASTER_DISABLER_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class MasterDisabler {
|
||||
public:
|
||||
explicit MasterDisabler(bool &master) : master_(master) {}
|
||||
virtual ~MasterDisabler() {}
|
||||
virtual void operator()() { master_ = false; }
|
||||
|
||||
private:
|
||||
bool &master_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SOUND_UNIT_H
|
||||
#define SOUND_UNIT_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class SoundUnit {
|
||||
public:
|
||||
enum { counter_max = 0x80000000u, counter_disabled = 0xFFFFFFFFu };
|
||||
|
||||
virtual ~SoundUnit() {}
|
||||
virtual void event() = 0;
|
||||
|
||||
virtual void resetCounters(unsigned long /*oldCc*/) {
|
||||
if (counter_ != counter_disabled)
|
||||
counter_ -= counter_max;
|
||||
}
|
||||
|
||||
unsigned long counter() const { return counter_; }
|
||||
|
||||
protected:
|
||||
SoundUnit() : counter_(counter_disabled) {}
|
||||
unsigned long counter_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef STATIC_OUTPUT_TESTER_H
|
||||
#define STATIC_OUTPUT_TESTER_H
|
||||
|
||||
#include "envelope_unit.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
template<class Channel, class Unit>
|
||||
class StaticOutputTester : public EnvelopeUnit::VolOnOffEvent {
|
||||
public:
|
||||
StaticOutputTester(Channel const &ch, Unit &unit) : ch_(ch), unit_(unit) {}
|
||||
void operator()(unsigned long cc);
|
||||
|
||||
private:
|
||||
Channel const &ch_;
|
||||
Unit &unit_;
|
||||
};
|
||||
|
||||
template<class Channel, class Unit>
|
||||
void StaticOutputTester<Channel, Unit>::operator()(unsigned long cc) {
|
||||
if (ch_.soMask_ && ch_.master_ && ch_.envelopeUnit_.getVolume())
|
||||
unit_.reviveCounter(cc);
|
||||
else
|
||||
unit_.killCounter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,604 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "statesaver.h"
|
||||
#include "savestate.h"
|
||||
#include "gambatte-array.h"
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
class omemstream
|
||||
{
|
||||
public:
|
||||
omemstream(void *data) : wr_ptr(static_cast<uint8_t*>(data)), has_written(0) {}
|
||||
|
||||
void put(uint8_t data)
|
||||
{
|
||||
if (wr_ptr)
|
||||
*wr_ptr++ = data;
|
||||
has_written++;
|
||||
}
|
||||
|
||||
void write(const void *data, size_t size)
|
||||
{
|
||||
if (wr_ptr)
|
||||
{
|
||||
std::memcpy(wr_ptr, data, size);
|
||||
wr_ptr += size;
|
||||
}
|
||||
|
||||
has_written += size;
|
||||
}
|
||||
|
||||
size_t size() const { return has_written; }
|
||||
bool fail() const { return false; }
|
||||
bool good() const { return true; }
|
||||
|
||||
private:
|
||||
uint8_t *wr_ptr;
|
||||
size_t has_written;
|
||||
};
|
||||
|
||||
class imemstream
|
||||
{
|
||||
public:
|
||||
imemstream(const void *data, size_t size)
|
||||
: rd_ptr(static_cast<const uint8_t*>(data))
|
||||
, rd_end(static_cast<const uint8_t*>(data) + size)
|
||||
, has_read(0)
|
||||
, failed_(false)
|
||||
{}
|
||||
|
||||
uint8_t get()
|
||||
{
|
||||
/* On underflow return 0 and latch failure. Callers
|
||||
* cannot ignore failure in a way that would dereference
|
||||
* the read pointer because rd_ptr is never advanced past
|
||||
* rd_end. Previously this function unconditionally
|
||||
* dereferenced and incremented rd_ptr, allowing a
|
||||
* malformed savestate to read arbitrary memory past the
|
||||
* buffer end. */
|
||||
if (rd_ptr >= rd_end) {
|
||||
failed_ = true;
|
||||
return 0;
|
||||
}
|
||||
uint8_t ret = *rd_ptr++;
|
||||
has_read++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void read(void *data, size_t size)
|
||||
{
|
||||
size_t avail = (size_t)(rd_end - rd_ptr);
|
||||
size_t n = size < avail ? size : avail;
|
||||
if (n)
|
||||
std::memcpy(data, rd_ptr, n);
|
||||
/* Pad the tail with zeroes if the buffer is short, to
|
||||
* keep caller-visible behavior deterministic when a
|
||||
* truncated state is fed in. */
|
||||
if (n < size) {
|
||||
std::memset(static_cast<uint8_t*>(data) + n, 0, size - n);
|
||||
failed_ = true;
|
||||
}
|
||||
rd_ptr += n;
|
||||
has_read += size;
|
||||
}
|
||||
|
||||
void ignore(size_t len = 1)
|
||||
{
|
||||
size_t avail = (size_t)(rd_end - rd_ptr);
|
||||
size_t n = len < avail ? len : avail;
|
||||
if (n < len)
|
||||
failed_ = true;
|
||||
rd_ptr += n;
|
||||
has_read += len;
|
||||
}
|
||||
|
||||
void getline(char *data, size_t size, char delim = '\n')
|
||||
{
|
||||
size_t count = 0;
|
||||
while ((count < size - 1) && (rd_ptr < rd_end) && (*rd_ptr != delim))
|
||||
{
|
||||
*data++ = *rd_ptr++;
|
||||
has_read++;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (rd_ptr < rd_end) {
|
||||
rd_ptr++;
|
||||
has_read++;
|
||||
} else {
|
||||
failed_ = true;
|
||||
}
|
||||
*data = '\0';
|
||||
}
|
||||
|
||||
bool fail() const { return failed_; }
|
||||
bool good() const { return !failed_ && rd_ptr < rd_end; }
|
||||
|
||||
private:
|
||||
const uint8_t *rd_ptr;
|
||||
const uint8_t *rd_end;
|
||||
size_t has_read;
|
||||
bool failed_;
|
||||
};
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace gambatte;
|
||||
|
||||
enum AsciiChar {
|
||||
NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, TAB, LF, VT, FF, CR, SO, SI,
|
||||
DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FS, GS, RS, US,
|
||||
SP, XCL, QOT, HSH, DLR, PRC, AMP, APO, LPA, RPA, AST, PLU, COM, HYP, STP, DIV,
|
||||
NO0, NO1, NO2, NO3, NO4, NO5, NO6, NO7, NO8, NO9, CLN, SCL, LT, EQL, GT, QTN,
|
||||
AT, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O,
|
||||
P, Q, R, S, T, U, V, W, X, Y, Z, LBX, BSL, RBX, CAT, UND,
|
||||
ACN, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o,
|
||||
p, q, r, s, t, u, v, w, x, y, z, LBR, BAR, RBR, TLD, DEL
|
||||
};
|
||||
|
||||
struct Saver {
|
||||
const char *label;
|
||||
void (*save)(omemstream &file, const SaveState &state);
|
||||
void (*load)(imemstream &file, SaveState &state);
|
||||
unsigned char labelsize;
|
||||
};
|
||||
|
||||
static inline bool operator<(const Saver &l, const Saver &r) {
|
||||
return std::strcmp(l.label, r.label) < 0;
|
||||
}
|
||||
|
||||
static void put24(omemstream &file, const unsigned long data) {
|
||||
file.put(data >> 16 & 0xFF);
|
||||
file.put(data >> 8 & 0xFF);
|
||||
file.put(data & 0xFF);
|
||||
}
|
||||
|
||||
static void put32(omemstream &file, const unsigned long data) {
|
||||
file.put(data >> 24 & 0xFF);
|
||||
file.put(data >> 16 & 0xFF);
|
||||
file.put(data >> 8 & 0xFF);
|
||||
file.put(data & 0xFF);
|
||||
}
|
||||
|
||||
static void write(omemstream &file, const unsigned char data) {
|
||||
static const char inf[] = { 0x00, 0x00, 0x01 };
|
||||
|
||||
file.write(inf, sizeof(inf));
|
||||
file.put(data & 0xFF);
|
||||
}
|
||||
|
||||
static void write(omemstream &file, const unsigned short data) {
|
||||
static const char inf[] = { 0x00, 0x00, 0x02 };
|
||||
|
||||
file.write(inf, sizeof(inf));
|
||||
file.put(data >> 8 & 0xFF);
|
||||
file.put(data & 0xFF);
|
||||
}
|
||||
|
||||
static void write(omemstream &file, const unsigned long data) {
|
||||
static const char inf[] = { 0x00, 0x00, 0x04 };
|
||||
|
||||
file.write(inf, sizeof(inf));
|
||||
put32(file, data);
|
||||
}
|
||||
|
||||
static inline void write(omemstream &file, const bool data) {
|
||||
write(file, static_cast<unsigned char>(data));
|
||||
}
|
||||
|
||||
static void write(omemstream &file, const unsigned char *data, const unsigned long sz) {
|
||||
put24(file, sz);
|
||||
file.write(reinterpret_cast<const char*>(data), sz);
|
||||
}
|
||||
|
||||
static void write(omemstream &file, const bool *data, const unsigned long sz) {
|
||||
put24(file, sz);
|
||||
|
||||
for (unsigned long i = 0; i < sz; ++i)
|
||||
file.put(data[i]);
|
||||
}
|
||||
|
||||
static unsigned long get24(imemstream &file) {
|
||||
unsigned long tmp = file.get() & 0xFF;
|
||||
|
||||
tmp = tmp << 8 | (file.get() & 0xFF);
|
||||
|
||||
return tmp << 8 | (file.get() & 0xFF);
|
||||
}
|
||||
|
||||
static unsigned long read(imemstream &file) {
|
||||
unsigned long size = get24(file);
|
||||
|
||||
if (size > 4) {
|
||||
file.ignore(size - 4);
|
||||
size = 4;
|
||||
}
|
||||
|
||||
unsigned long out = 0;
|
||||
|
||||
switch (size) {
|
||||
case 4: out = (out | (file.get() & 0xFF)) << 8;
|
||||
case 3: out = (out | (file.get() & 0xFF)) << 8;
|
||||
case 2: out = (out | (file.get() & 0xFF)) << 8;
|
||||
case 1: out = out | (file.get() & 0xFF);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline void read(imemstream &file, unsigned char &data) {
|
||||
data = read(file) & 0xFF;
|
||||
}
|
||||
|
||||
static inline void read(imemstream &file, unsigned short &data) {
|
||||
data = read(file) & 0xFFFF;
|
||||
}
|
||||
|
||||
static inline void read(imemstream &file, unsigned long &data) {
|
||||
data = read(file);
|
||||
}
|
||||
|
||||
static inline void read(imemstream &file, bool &data) {
|
||||
data = read(file);
|
||||
}
|
||||
|
||||
static void read(imemstream &file, unsigned char *data, unsigned long sz) {
|
||||
const unsigned long size = get24(file);
|
||||
|
||||
if (size < sz)
|
||||
sz = size;
|
||||
|
||||
file.read(reinterpret_cast<char*>(data), sz);
|
||||
file.ignore(size - sz);
|
||||
|
||||
/* Previous code had:
|
||||
* if (static_cast<unsigned char>(0x100))
|
||||
* for (...) data[i] &= 0xFF;
|
||||
* The condition is always false (0x100 truncated to
|
||||
* unsigned char is 0), so the masking loop was dead.
|
||||
* Removed. unsigned char already holds 0..0xFF by definition. */
|
||||
}
|
||||
|
||||
static void read(imemstream &file, bool *data, unsigned long sz) {
|
||||
const unsigned long size = get24(file);
|
||||
|
||||
if (size < sz)
|
||||
sz = size;
|
||||
|
||||
for (unsigned long i = 0; i < sz; ++i)
|
||||
data[i] = file.get();
|
||||
|
||||
file.ignore(size - sz);
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class SaverList {
|
||||
public:
|
||||
typedef std::vector<Saver> list_t;
|
||||
typedef list_t::const_iterator const_iterator;
|
||||
|
||||
private:
|
||||
list_t list;
|
||||
unsigned char maxLabelsize_;
|
||||
|
||||
public:
|
||||
SaverList();
|
||||
const_iterator begin() const { return list.begin(); }
|
||||
const_iterator end() const { return list.end(); }
|
||||
unsigned char maxLabelsize() const { return maxLabelsize_; }
|
||||
};
|
||||
|
||||
static void pushSaver(SaverList::list_t &list, const char *label,
|
||||
void (*save)(omemstream &file, const SaveState &state),
|
||||
void (*load)(imemstream &file, SaveState &state), unsigned char labelsize) {
|
||||
const Saver saver = { label, save, load, labelsize };
|
||||
list.push_back(saver);
|
||||
}
|
||||
|
||||
SaverList::SaverList() {
|
||||
#define ADD(arg) do { \
|
||||
struct Func { \
|
||||
static void save(omemstream &file, const SaveState &state) { write(file, state.arg); } \
|
||||
static void load(imemstream &file, SaveState &state) { read(file, state.arg); } \
|
||||
}; \
|
||||
\
|
||||
pushSaver(list, label, Func::save, Func::load, sizeof label); \
|
||||
} while (0)
|
||||
|
||||
#define ADDPTR(arg) do { \
|
||||
struct Func { \
|
||||
static void save(omemstream &file, const SaveState &state) { write(file, state.arg.get(), state.arg.size()); } \
|
||||
static void load(imemstream &file, SaveState &state) { read(file, state.arg.ptr, state.arg.size()); } \
|
||||
}; \
|
||||
\
|
||||
pushSaver(list, label, Func::save, Func::load, sizeof label); \
|
||||
} while (0)
|
||||
|
||||
#define ADDARRAY(arg) do { \
|
||||
struct Func { \
|
||||
static void save(omemstream &file, const SaveState &state) { write(file, state.arg, sizeof(state.arg)); } \
|
||||
static void load(imemstream &file, SaveState &state) { read(file, state.arg, sizeof(state.arg)); } \
|
||||
}; \
|
||||
\
|
||||
pushSaver(list, label, Func::save, Func::load, sizeof label); \
|
||||
} while (0)
|
||||
|
||||
{ static const char label[] = { c,c, NUL }; ADD(cpu.cycleCounter); }
|
||||
{ static const char label[] = { p,c, NUL }; ADD(cpu.pc); }
|
||||
{ static const char label[] = { s,p, NUL }; ADD(cpu.sp); }
|
||||
{ static const char label[] = { a, NUL }; ADD(cpu.a); }
|
||||
{ static const char label[] = { b, NUL }; ADD(cpu.b); }
|
||||
{ static const char label[] = { c, NUL }; ADD(cpu.c); }
|
||||
{ static const char label[] = { d, NUL }; ADD(cpu.d); }
|
||||
{ static const char label[] = { e, NUL }; ADD(cpu.e); }
|
||||
{ static const char label[] = { f, NUL }; ADD(cpu.f); }
|
||||
{ static const char label[] = { h, NUL }; ADD(cpu.h); }
|
||||
{ static const char label[] = { l, NUL }; ADD(cpu.l); }
|
||||
{ static const char label[] = { s,k,i,p, NUL }; ADD(cpu.skip); }
|
||||
{ static const char label[] = { h,a,l,t, NUL }; ADD(mem.halted); }
|
||||
{ static const char label[] = { v,r,a,m, NUL }; ADDPTR(mem.vram); }
|
||||
{ static const char label[] = { s,r,a,m, NUL }; ADDPTR(mem.sram); }
|
||||
{ static const char label[] = { w,r,a,m, NUL }; ADDPTR(mem.wram); }
|
||||
{ static const char label[] = { h,r,a,m, NUL }; ADDPTR(mem.ioamhram); }
|
||||
{ static const char label[] = { l,d,i,v,u,p, NUL }; ADD(mem.divLastUpdate); }
|
||||
{ static const char label[] = { l,t,i,m,a,u,p, NUL }; ADD(mem.timaLastUpdate); }
|
||||
{ static const char label[] = { t,m,a,t,i,m,e, NUL }; ADD(mem.tmatime); }
|
||||
{ static const char label[] = { s,e,r,i,a,l,t, NUL }; ADD(mem.nextSerialtime); }
|
||||
{ static const char label[] = { l,o,d,m,a,u,p, NUL }; ADD(mem.lastOamDmaUpdate); }
|
||||
{ static const char label[] = { m,i,n,i,n,t,t, NUL }; ADD(mem.minIntTime); }
|
||||
{ static const char label[] = { u,n,h,a,l,t,t, NUL }; ADD(mem.unhaltTime); }
|
||||
{ static const char label[] = { r,o,m,b,a,n,k, NUL }; ADD(mem.rombank); }
|
||||
{ static const char label[] = { d,m,a,s,r,c, NUL }; ADD(mem.dmaSource); }
|
||||
{ static const char label[] = { d,m,a,d,s,t, NUL }; ADD(mem.dmaDestination); }
|
||||
{ static const char label[] = { r,a,m,b,a,n,k, NUL }; ADD(mem.rambank); }
|
||||
{ static const char label[] = { o,d,m,a,p,o,s, NUL }; ADD(mem.oamDmaPos); }
|
||||
#ifdef HAVE_NETWORK
|
||||
{ static const char label[] = { n,e,t,s,v, NUL }; ADD(mem.serialize_value); }
|
||||
{ static const char label[] = { n,e,t,s,f,c, NUL }; ADD(mem.serialize_is_fastcgb); }
|
||||
#endif
|
||||
{ static const char label[] = { i,m,e, NUL }; ADD(mem.IME); }
|
||||
{ static const char label[] = { s,r,a,m,o,n, NUL }; ADD(mem.enableRam); }
|
||||
{ static const char label[] = { r,a,m,b,m,o,d, NUL }; ADD(mem.rambankMode); }
|
||||
{ static const char label[] = { h,d,m,a, NUL }; ADD(mem.hdmaTransfer); }
|
||||
{ static const char label[] = { h,u,c,NO3,r,a,m, NUL }; ADD(mem.HuC3RAMflag); }
|
||||
{ static const char label[] = { s,c,h,n,o,m,k, NUL }; ADD(mem.sachenOuterMask); }
|
||||
{ static const char label[] = { s,c,h,n,l,c,t, NUL }; ADD(mem.sachenLockCount); }
|
||||
{ static const char label[] = { b,g,p, NUL }; ADDPTR(ppu.bgpData); }
|
||||
{ static const char label[] = { o,b,j,p, NUL }; ADDPTR(ppu.objpData); }
|
||||
{ static const char label[] = { s,p,o,s,b,u,f, NUL }; ADDPTR(ppu.oamReaderBuf); }
|
||||
{ static const char label[] = { s,p,s,z,b,u,f, NUL }; ADDPTR(ppu.oamReaderSzbuf); }
|
||||
{ static const char label[] = { d,m,g,p,a,l, NUL }; ADDARRAY(ppu.dmgPalette); }
|
||||
{ static const char label[] = { s,p,a,t,t,r, NUL }; ADDARRAY(ppu.spAttribList); }
|
||||
{ static const char label[] = { s,p,b,y,t,e,NO0, NUL }; ADDARRAY(ppu.spByte0List); }
|
||||
{ static const char label[] = { s,p,b,y,t,e,NO1, NUL }; ADDARRAY(ppu.spByte1List); }
|
||||
{ static const char label[] = { v,c,y,c,l,e,s, NUL }; ADD(ppu.videoCycles); }
|
||||
{ static const char label[] = { e,d,M,NO0,t,i,m, NUL }; ADD(ppu.enableDisplayM0Time); }
|
||||
{ static const char label[] = { m,NO0,t,i,m,e, NUL }; ADD(ppu.lastM0Time); }
|
||||
{ static const char label[] = { n,m,NO0,i,r,q, NUL }; ADD(ppu.nextM0Irq); }
|
||||
{ static const char label[] = { b,g,t,w, NUL }; ADD(ppu.tileword); }
|
||||
{ static const char label[] = { b,g,n,t,w, NUL }; ADD(ppu.ntileword); }
|
||||
{ static const char label[] = { w,i,n,y,p,o,s, NUL }; ADD(ppu.winYPos); }
|
||||
{ static const char label[] = { x,p,o,s, NUL }; ADD(ppu.xpos); }
|
||||
{ static const char label[] = { e,n,d,x, NUL }; ADD(ppu.endx); }
|
||||
{ static const char label[] = { p,p,u,r,NO0, NUL }; ADD(ppu.reg0); }
|
||||
{ static const char label[] = { p,p,u,r,NO1, NUL }; ADD(ppu.reg1); }
|
||||
{ static const char label[] = { b,g,a,t,r,b, NUL }; ADD(ppu.attrib); }
|
||||
{ static const char label[] = { b,g,n,a,t,r,b, NUL }; ADD(ppu.nattrib); }
|
||||
{ static const char label[] = { p,p,u,s,t,a,t, NUL }; ADD(ppu.state); }
|
||||
{ static const char label[] = { n,s,p,r,i,t,e, NUL }; ADD(ppu.nextSprite); }
|
||||
{ static const char label[] = { c,s,p,r,i,t,e, NUL }; ADD(ppu.currentSprite); }
|
||||
{ static const char label[] = { l,y,c, NUL }; ADD(ppu.lyc); }
|
||||
{ static const char label[] = { m,NO0,l,y,c, NUL }; ADD(ppu.m0lyc); }
|
||||
{ static const char label[] = { o,l,d,w,y, NUL }; ADD(ppu.oldWy); }
|
||||
{ static const char label[] = { w,i,n,d,r,a,w, NUL }; ADD(ppu.winDrawState); }
|
||||
{ static const char label[] = { w,s,c,x, NUL }; ADD(ppu.wscx); }
|
||||
{ static const char label[] = { w,e,m,a,s,t,r, NUL }; ADD(ppu.weMaster); }
|
||||
{ static const char label[] = { l,c,d,s,i,r,q, NUL }; ADD(ppu.pendingLcdstatIrq); }
|
||||
{ static const char label[] = { s,p,u,c,n,t,r, NUL }; ADD(spu.cycleCounter); }
|
||||
{ static const char label[] = { s,w,p,c,n,t,r, NUL }; ADD(spu.ch1.sweep.counter); }
|
||||
{ static const char label[] = { s,w,p,s,h,d,w, NUL }; ADD(spu.ch1.sweep.shadow); }
|
||||
{ static const char label[] = { s,w,p,n,e,g, NUL }; ADD(spu.ch1.sweep.negging); }
|
||||
{ static const char label[] = { d,u,t,NO1,c,t,r, NUL }; ADD(spu.ch1.duty.nextPosUpdate); }
|
||||
{ static const char label[] = { d,u,t,NO1,p,o,s, NUL }; ADD(spu.ch1.duty.pos); }
|
||||
{ static char const label[] = { d,u,t,NO1,h,i, NUL }; ADD(spu.ch1.duty.high); }
|
||||
{ static const char label[] = { e,n,v,NO1,c,t,r, NUL }; ADD(spu.ch1.env.counter); }
|
||||
{ static const char label[] = { e,n,v,NO1,v,o,l, NUL }; ADD(spu.ch1.env.volume); }
|
||||
{ static const char label[] = { l,e,n,NO1,c,t,r, NUL }; ADD(spu.ch1.lcounter.counter); }
|
||||
{ static const char label[] = { l,e,n,NO1,v,a,l, NUL }; ADD(spu.ch1.lcounter.lengthCounter); }
|
||||
{ static const char label[] = { n,r,NO1,NO0, NUL }; ADD(spu.ch1.sweep.nr0); }
|
||||
{ static const char label[] = { n,r,NO1,NO3, NUL }; ADD(spu.ch1.duty.nr3); }
|
||||
{ static const char label[] = { n,r,NO1,NO4, NUL }; ADD(spu.ch1.nr4); }
|
||||
{ static const char label[] = { c,NO1,m,a,s,t,r, NUL }; ADD(spu.ch1.master); }
|
||||
{ static const char label[] = { d,u,t,NO2,c,t,r, NUL }; ADD(spu.ch2.duty.nextPosUpdate); }
|
||||
{ static const char label[] = { d,u,t,NO2,p,o,s, NUL }; ADD(spu.ch2.duty.pos); }
|
||||
{ static char const label[] = { d,u,t,NO2,h,i, NUL }; ADD(spu.ch2.duty.high); }
|
||||
{ static const char label[] = { e,n,v,NO2,c,t,r, NUL }; ADD(spu.ch2.env.counter); }
|
||||
{ static const char label[] = { e,n,v,NO2,v,o,l, NUL }; ADD(spu.ch2.env.volume); }
|
||||
{ static const char label[] = { l,e,n,NO2,c,t,r, NUL }; ADD(spu.ch2.lcounter.counter); }
|
||||
{ static const char label[] = { l,e,n,NO2,v,a,l, NUL }; ADD(spu.ch2.lcounter.lengthCounter); }
|
||||
{ static const char label[] = { n,r,NO2,NO3, NUL }; ADD(spu.ch2.duty.nr3); }
|
||||
{ static const char label[] = { n,r,NO2,NO4, NUL }; ADD(spu.ch2.nr4); }
|
||||
{ static const char label[] = { c,NO2,m,a,s,t,r, NUL }; ADD(spu.ch2.master); }
|
||||
{ static const char label[] = { w,a,v,e,r,a,m, NUL }; ADDPTR(spu.ch3.waveRam); }
|
||||
{ static const char label[] = { l,e,n,NO3,c,t,r, NUL }; ADD(spu.ch3.lcounter.counter); }
|
||||
{ static const char label[] = { l,e,n,NO3,v,a,l, NUL }; ADD(spu.ch3.lcounter.lengthCounter); }
|
||||
{ static const char label[] = { w,a,v,e,c,t,r, NUL }; ADD(spu.ch3.waveCounter); }
|
||||
{ static const char label[] = { l,w,a,v,r,d,t, NUL }; ADD(spu.ch3.lastReadTime); }
|
||||
{ static const char label[] = { w,a,v,e,p,o,s, NUL }; ADD(spu.ch3.wavePos); }
|
||||
{ static const char label[] = { w,a,v,s,m,p,l, NUL }; ADD(spu.ch3.sampleBuf); }
|
||||
{ static const char label[] = { n,r,NO3,NO3, NUL }; ADD(spu.ch3.nr3); }
|
||||
{ static const char label[] = { n,r,NO3,NO4, NUL }; ADD(spu.ch3.nr4); }
|
||||
{ static const char label[] = { c,NO3,m,a,s,t,r, NUL }; ADD(spu.ch3.master); }
|
||||
{ static const char label[] = { l,f,s,r,c,t,r, NUL }; ADD(spu.ch4.lfsr.counter); }
|
||||
{ static const char label[] = { l,f,s,r,r,e,g, NUL }; ADD(spu.ch4.lfsr.reg); }
|
||||
{ static const char label[] = { e,n,v,NO4,c,t,r, NUL }; ADD(spu.ch4.env.counter); }
|
||||
{ static const char label[] = { e,n,v,NO4,v,o,l, NUL }; ADD(spu.ch4.env.volume); }
|
||||
{ static const char label[] = { l,e,n,NO4,c,t,r, NUL }; ADD(spu.ch4.lcounter.counter); }
|
||||
{ static const char label[] = { l,e,n,NO4,v,a,l, NUL }; ADD(spu.ch4.lcounter.lengthCounter); }
|
||||
{ static const char label[] = { n,r,NO4,NO4, NUL }; ADD(spu.ch4.nr4); }
|
||||
{ static const char label[] = { c,NO4,m,a,s,t,r, NUL }; ADD(spu.ch4.master); }
|
||||
{ static const char label[] = { r,t,c,b,a,s,e, NUL }; ADD(rtc.baseTime); }
|
||||
{ static const char label[] = { r,t,c,h,a,l,t, NUL }; ADD(rtc.haltTime); }
|
||||
{ static const char label[] = { r,t,c,d,h, NUL }; ADD(rtc.dataDh); }
|
||||
{ static const char label[] = { r,t,c,d,l, NUL }; ADD(rtc.dataDl); }
|
||||
{ static const char label[] = { r,t,c,h, NUL }; ADD(rtc.dataH); }
|
||||
{ static const char label[] = { r,t,c,m, NUL }; ADD(rtc.dataM); }
|
||||
{ static const char label[] = { r,t,c,s, NUL }; ADD(rtc.dataS); }
|
||||
{ static const char label[] = { r,t,c,l,l,d, NUL }; ADD(rtc.lastLatchData); }
|
||||
{ static char const label[] = { h,NO3,b,a,s,e,t, NUL }; ADD(huc3.baseTime); }
|
||||
{ static const char label[] = { h,NO3,h,a,l,t,t, NUL }; ADD(huc3.haltTime); }
|
||||
{ static const char label[] = { h,NO3,d,a,t,a,t, NUL }; ADD(huc3.dataTime); }
|
||||
{ static const char label[] = { h,NO3,w,r,i,t,t, NUL }; ADD(huc3.writingTime); }
|
||||
{ static const char label[] = { h,NO3,h,a,l,t, NUL }; ADD(huc3.halted); }
|
||||
{ static const char label[] = { h,NO3,s,h,f,t, NUL }; ADD(huc3.shift); }
|
||||
{ static const char label[] = { h,NO3,r,v, NUL }; ADD(huc3.ramValue); }
|
||||
{ static const char label[] = { h,NO3,m,f, NUL }; ADD(huc3.modeflag); }
|
||||
{ static const char label[] = { h,NO3,i,r,c,y, NUL }; ADD(huc3.irBaseCycle); }
|
||||
{ static const char label[] = { h,NO3,i,r,a,c, NUL }; ADD(huc3.irReceivingPulse); }
|
||||
|
||||
#undef ADD
|
||||
#undef ADDPTR
|
||||
#undef ADDARRAY
|
||||
|
||||
list.resize(list.size());
|
||||
std::sort(list.begin(), list.end());
|
||||
|
||||
maxLabelsize_ = 0;
|
||||
|
||||
for (std::size_t i = 0; i < list.size(); ++i) {
|
||||
if (list[i].labelsize > maxLabelsize_)
|
||||
maxLabelsize_ = list[i].labelsize;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static void writeSnapShot(omemstream &file) {
|
||||
put24(file, 0);
|
||||
}
|
||||
|
||||
/* Function-local static to make the construction lifetime
|
||||
* explicit. The previous translation-unit-scope static was
|
||||
* constructed at library load (before main / before any libretro
|
||||
* entry point) which made its construction order against other
|
||||
* library-load work implementation-defined. The list itself is
|
||||
* read-only after construction so concurrent reads are safe. */
|
||||
static const SaverList &saver_list(void) {
|
||||
static SaverList instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
void StateSaver::saveState(const SaveState &state, void *data) {
|
||||
omemstream file(data);
|
||||
|
||||
if (file.fail())
|
||||
return;
|
||||
|
||||
{ static const char ver[] = { 0, 1 }; file.write(ver, sizeof(ver)); }
|
||||
|
||||
writeSnapShot(file);
|
||||
|
||||
const SaverList &list = saver_list();
|
||||
for (SaverList::const_iterator it = list.begin(); it != list.end(); ++it) {
|
||||
file.write(it->label, it->labelsize);
|
||||
(*it->save)(file, state);
|
||||
}
|
||||
}
|
||||
|
||||
bool StateSaver::loadState(SaveState &state, const void *data, size_t size) {
|
||||
/* The bounds-checked imemstream needs the buffer end so it
|
||||
* can stop at it. Previously this constructor only took a
|
||||
* pointer; a malformed savestate could claim a 24-bit chunk
|
||||
* size of 0xFFFFFF (16 MB) and the parser would happily skip
|
||||
* past the end and read arbitrary memory. */
|
||||
imemstream file(data, size);
|
||||
|
||||
if (file.fail() || file.get() != 0)
|
||||
return false;
|
||||
|
||||
file.ignore();
|
||||
file.ignore(get24(file));
|
||||
|
||||
const SaverList &list = saver_list();
|
||||
const Array<char> labelbuf(list.maxLabelsize());
|
||||
const Saver labelbufSaver = { labelbuf, 0, 0, list.maxLabelsize() };
|
||||
|
||||
SaverList::const_iterator done = list.begin();
|
||||
|
||||
while (file.good() && done != list.end()) {
|
||||
file.getline(labelbuf, list.maxLabelsize(), NUL);
|
||||
|
||||
SaverList::const_iterator it = done;
|
||||
|
||||
if (std::strcmp(labelbuf, it->label)) {
|
||||
it = std::lower_bound(it + 1, list.end(), labelbufSaver);
|
||||
|
||||
if (it == list.end() || std::strcmp(labelbuf, it->label)) {
|
||||
file.ignore(get24(file));
|
||||
continue;
|
||||
}
|
||||
} else
|
||||
++done;
|
||||
|
||||
(*it->load)(file, state);
|
||||
}
|
||||
|
||||
state.cpu.cycleCounter &= 0x7FFFFFFF;
|
||||
state.spu.cycleCounter &= 0x7FFFFFFF;
|
||||
|
||||
/* If the parser tripped its failure latch the data was
|
||||
* malformed; reject the load so the caller doesn't end up
|
||||
* with a half-applied state. */
|
||||
return !file.fail();
|
||||
}
|
||||
|
||||
size_t StateSaver::stateSize(const SaveState &state) {
|
||||
omemstream file(0);
|
||||
|
||||
if (file.fail())
|
||||
return 0;
|
||||
|
||||
{ static const char ver[] = { 0, 1 }; file.write(ver, sizeof(ver)); }
|
||||
|
||||
writeSnapShot(file);
|
||||
|
||||
const SaverList &list = saver_list();
|
||||
for (SaverList::const_iterator it = list.begin(); it != list.end(); ++it) {
|
||||
file.write(it->label, it->labelsize);
|
||||
(*it->save)(file, state);
|
||||
}
|
||||
|
||||
return file.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef STATESAVER_H
|
||||
#define STATESAVER_H
|
||||
|
||||
#include "gbint.h"
|
||||
#include <string>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class StateSaver {
|
||||
StateSaver();
|
||||
|
||||
public:
|
||||
enum { SS_SHIFT = 2 };
|
||||
enum { SS_DIV = 1 << 2 };
|
||||
enum { SS_WIDTH = 160 >> SS_SHIFT };
|
||||
enum { SS_HEIGHT = 144 >> SS_SHIFT };
|
||||
|
||||
static void saveState(const SaveState &state, void *data);
|
||||
/* size is the size of the input buffer in bytes. The parser
|
||||
* uses it to bound every read so a malformed savestate cannot
|
||||
* cause an OOB read past the end of the buffer. */
|
||||
static bool loadState(SaveState &state, const void *data, size_t size);
|
||||
static size_t stateSize(const SaveState &state);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "tima.h"
|
||||
#include "savestate.h"
|
||||
|
||||
static unsigned char const timaClock[4] = { 10, 4, 6, 8 };
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Tima::Tima()
|
||||
: lastUpdate_(0)
|
||||
, tmatime_(disabled_time)
|
||||
, tima_(0)
|
||||
, tma_(0)
|
||||
, tac_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Tima::saveState(SaveState &state) const {
|
||||
state.mem.timaLastUpdate = lastUpdate_;
|
||||
state.mem.tmatime = tmatime_;
|
||||
}
|
||||
|
||||
void Tima::loadState(SaveState const &state, TimaInterruptRequester timaIrq) {
|
||||
lastUpdate_ = state.mem.timaLastUpdate;
|
||||
tmatime_ = state.mem.tmatime;
|
||||
tima_ = state.mem.ioamhram.get()[0x105];
|
||||
tma_ = state.mem.ioamhram.get()[0x106];
|
||||
tac_ = state.mem.ioamhram.get()[0x107];
|
||||
|
||||
unsigned long nextIrqEventTime = disabled_time;
|
||||
if (tac_ & 4) {
|
||||
nextIrqEventTime = tmatime_ != disabled_time && tmatime_ > state.cpu.cycleCounter
|
||||
? tmatime_
|
||||
: lastUpdate_ + ((256u - tima_) << timaClock[tac_ & 3]) + 3;
|
||||
}
|
||||
|
||||
timaIrq.setNextIrqEventTime(nextIrqEventTime);
|
||||
}
|
||||
|
||||
void Tima::resetCc(unsigned long const oldCc, unsigned long const newCc, TimaInterruptRequester timaIrq) {
|
||||
if (tac_ & 0x04) {
|
||||
updateIrq(oldCc, timaIrq);
|
||||
updateTima(oldCc);
|
||||
|
||||
unsigned long const dec = oldCc - newCc;
|
||||
lastUpdate_ -= dec;
|
||||
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() - dec);
|
||||
|
||||
if (tmatime_ != disabled_time)
|
||||
tmatime_ -= dec;
|
||||
}
|
||||
}
|
||||
|
||||
void Tima::updateTima(unsigned long const cc) {
|
||||
unsigned long const ticks = (cc - lastUpdate_) >> timaClock[tac_ & 3];
|
||||
lastUpdate_ += ticks << timaClock[tac_ & 3];
|
||||
|
||||
if (cc >= tmatime_) {
|
||||
if (cc >= tmatime_ + 4)
|
||||
tmatime_ = disabled_time;
|
||||
|
||||
tima_ = tma_;
|
||||
}
|
||||
|
||||
unsigned long tmp = tima_ + ticks;
|
||||
while (tmp > 0x100)
|
||||
tmp -= 0x100 - tma_;
|
||||
|
||||
if (tmp == 0x100) {
|
||||
tmp = 0;
|
||||
tmatime_ = lastUpdate_ + 3;
|
||||
|
||||
if (cc >= tmatime_) {
|
||||
if (cc >= tmatime_ + 4)
|
||||
tmatime_ = disabled_time;
|
||||
|
||||
tmp = tma_;
|
||||
}
|
||||
}
|
||||
|
||||
tima_ = tmp;
|
||||
}
|
||||
|
||||
void Tima::setTima(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
|
||||
if (tac_ & 0x04) {
|
||||
updateIrq(cc, timaIrq);
|
||||
updateTima(cc);
|
||||
|
||||
if (tmatime_ - cc < 4)
|
||||
tmatime_ = disabled_time;
|
||||
|
||||
timaIrq.setNextIrqEventTime(lastUpdate_ + ((256u - data) << timaClock[tac_ & 3]) + 3);
|
||||
}
|
||||
|
||||
tima_ = data;
|
||||
}
|
||||
|
||||
void Tima::setTma(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
|
||||
if (tac_ & 0x04) {
|
||||
updateIrq(cc, timaIrq);
|
||||
updateTima(cc);
|
||||
}
|
||||
|
||||
tma_ = data;
|
||||
}
|
||||
|
||||
void Tima::setTac(unsigned const data, unsigned long const cc, TimaInterruptRequester timaIrq) {
|
||||
if (tac_ ^ data) {
|
||||
unsigned long nextIrqEventTime = timaIrq.nextIrqEventTime();
|
||||
|
||||
if (tac_ & 0x04) {
|
||||
updateIrq(cc, timaIrq);
|
||||
updateTima(cc);
|
||||
|
||||
lastUpdate_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
tmatime_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
nextIrqEventTime -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
|
||||
if (cc >= nextIrqEventTime)
|
||||
timaIrq.flagIrq();
|
||||
|
||||
updateTima(cc);
|
||||
|
||||
tmatime_ = disabled_time;
|
||||
nextIrqEventTime = disabled_time;
|
||||
}
|
||||
|
||||
if (data & 4) {
|
||||
lastUpdate_ = (cc >> timaClock[data & 3]) << timaClock[data & 3];
|
||||
nextIrqEventTime = lastUpdate_ + ((256u - tima_) << timaClock[data & 3]) + 3;
|
||||
}
|
||||
|
||||
timaIrq.setNextIrqEventTime(nextIrqEventTime);
|
||||
}
|
||||
|
||||
tac_ = data;
|
||||
}
|
||||
|
||||
unsigned Tima::tima(unsigned long cc) {
|
||||
if (tac_ & 0x04)
|
||||
updateTima(cc);
|
||||
|
||||
return tima_;
|
||||
}
|
||||
|
||||
void Tima::doIrqEvent(TimaInterruptRequester timaIrq) {
|
||||
timaIrq.flagIrq();
|
||||
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime()
|
||||
+ ((256u - tma_) << timaClock[tac_ & 3]));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef TIMA_H
|
||||
#define TIMA_H
|
||||
|
||||
#include "interruptrequester.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class TimaInterruptRequester {
|
||||
public:
|
||||
explicit TimaInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {}
|
||||
void flagIrq() const { intreq_.flagIrq(4); }
|
||||
unsigned long nextIrqEventTime() const { return intreq_.eventTime(intevent_tima); }
|
||||
void setNextIrqEventTime(unsigned long time) const { intreq_.setEventTime<intevent_tima>(time); }
|
||||
|
||||
private:
|
||||
InterruptRequester &intreq_;
|
||||
};
|
||||
|
||||
class Tima {
|
||||
public:
|
||||
Tima();
|
||||
void saveState(SaveState &) const;
|
||||
void loadState(const SaveState &, TimaInterruptRequester timaIrq);
|
||||
void resetCc(unsigned long oldCc, unsigned long newCc, TimaInterruptRequester timaIrq);
|
||||
void setTima(unsigned tima, unsigned long cc, TimaInterruptRequester timaIrq);
|
||||
void setTma(unsigned tma, unsigned long cc, TimaInterruptRequester timaIrq);
|
||||
void setTac(unsigned tac, unsigned long cc, TimaInterruptRequester timaIrq);
|
||||
unsigned tima(unsigned long cc);
|
||||
void doIrqEvent(TimaInterruptRequester timaIrq);
|
||||
|
||||
private:
|
||||
unsigned long lastUpdate_;
|
||||
unsigned long tmatime_;
|
||||
unsigned char tima_;
|
||||
unsigned char tma_;
|
||||
unsigned char tac_;
|
||||
|
||||
void updateIrq(unsigned long const cc, TimaInterruptRequester timaIrq) {
|
||||
while (cc >= timaIrq.nextIrqEventTime())
|
||||
doIrqEvent(timaIrq);
|
||||
}
|
||||
|
||||
void updateTima(unsigned long cc);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,777 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "video.h"
|
||||
#include "savestate.h"
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
|
||||
void LCD::reset(const unsigned char *const oamram, unsigned char const *vram, const bool cgb)
|
||||
{
|
||||
ppu_.reset(oamram, vram, cgb);
|
||||
lycIrq_.setCgb(cgb);
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
static unsigned long mode2IrqSchedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter)
|
||||
{
|
||||
if (!(statReg & 0x20))
|
||||
return disabled_time;
|
||||
|
||||
unsigned next = lyCounter.time() - cycleCounter;
|
||||
|
||||
if (lyCounter.ly() >= 143 || (lyCounter.ly() == 142 && next <= 4) || (statReg & 0x08))
|
||||
next += (153u - lyCounter.ly()) * lyCounter.lineTime();
|
||||
else
|
||||
{
|
||||
if (next <= 4)
|
||||
next += lyCounter.lineTime();
|
||||
|
||||
next -= 4;
|
||||
}
|
||||
|
||||
return cycleCounter + next;
|
||||
}
|
||||
|
||||
static inline unsigned long m0IrqTimeFromXpos166Time(
|
||||
const unsigned long xpos166Time, const bool cgb, const bool ds)
|
||||
{
|
||||
return xpos166Time + cgb - ds;
|
||||
}
|
||||
|
||||
static inline unsigned long hdmaTimeFromM0Time(
|
||||
const unsigned long m0Time, const bool ds)
|
||||
{
|
||||
return m0Time + 1 - ds;
|
||||
}
|
||||
|
||||
static unsigned long nextHdmaTime(const unsigned long lastM0Time,
|
||||
const unsigned long nextM0Time, const unsigned long cycleCounter, const bool ds)
|
||||
{
|
||||
return cycleCounter < hdmaTimeFromM0Time(lastM0Time, ds)
|
||||
? hdmaTimeFromM0Time(lastM0Time, ds)
|
||||
: hdmaTimeFromM0Time(nextM0Time, ds);
|
||||
}
|
||||
|
||||
void LCD::setStatePtrs(SaveState &state)
|
||||
{
|
||||
state.ppu.bgpData.set( bgpData_, sizeof bgpData_);
|
||||
state.ppu.objpData.set(objpData_, sizeof objpData_);
|
||||
ppu_.setStatePtrs(state);
|
||||
}
|
||||
|
||||
void LCD::saveState(SaveState &state) const
|
||||
{
|
||||
state.mem.hdmaTransfer = hdmaIsEnabled();
|
||||
state.ppu.nextM0Irq = eventTimes_(MODE0_IRQ) - ppu_.now();
|
||||
state.ppu.pendingLcdstatIrq = eventTimes_(ONESHOT_LCDSTATIRQ) != disabled_time;
|
||||
|
||||
if (isCgb())
|
||||
std::memcpy(state.ppu.dmgPalette, dmgColorsGBC_, 8 * 3);
|
||||
|
||||
|
||||
lycIrq_.saveState(state);
|
||||
m0Irq_.saveState(state);
|
||||
ppu_.saveState(state);
|
||||
}
|
||||
|
||||
void LCD::loadState(const SaveState &state, const unsigned char *const oamram)
|
||||
{
|
||||
statReg_ = state.mem.ioamhram.get()[0x141];
|
||||
m2IrqStatReg_ = statReg_;
|
||||
m1IrqStatReg_ = statReg_;
|
||||
|
||||
ppu_.loadState(state, oamram);
|
||||
lycIrq_.loadState(state);
|
||||
m0Irq_.loadState(state);
|
||||
|
||||
if (ppu_.lcdc() & 0x80)
|
||||
{
|
||||
nextM0Time_.predictNextM0Time(ppu_);
|
||||
lycIrq_.reschedule(ppu_.lyCounter(), ppu_.now());
|
||||
|
||||
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(state.ppu.pendingLcdstatIrq
|
||||
? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
|
||||
eventTimes_.setm<ONESHOT_UPDATEWY2>(state.ppu.oldWy != state.mem.ioamhram.get()[0x14A]
|
||||
? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
|
||||
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
|
||||
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), ppu_.now()));
|
||||
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
|
||||
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, ppu_.now()));
|
||||
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), ppu_.now()));
|
||||
eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08) ? ppu_.now() + state.ppu.nextM0Irq : static_cast<unsigned long>(disabled_time));
|
||||
eventTimes_.setm<HDMA_REQ>(state.mem.hdmaTransfer
|
||||
? nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), ppu_.now(), isDoubleSpeed())
|
||||
: static_cast<unsigned long>(disabled_time));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
|
||||
eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
|
||||
}
|
||||
|
||||
if (isCgb())
|
||||
std::memcpy(dmgColorsGBC_, state.ppu.dmgPalette, 8 * 3);
|
||||
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
void LCD::refreshPalettes()
|
||||
{
|
||||
if (ppu_.cgb() && !ppu_.inDmgMode())
|
||||
{
|
||||
for (unsigned i = 0; i < 8 * 8; i += 2)
|
||||
{
|
||||
ppu_.bgPalette()[i >> 1] = gbcToRgb32( bgpData_[i] | bgpData_[i + 1] << 8);
|
||||
ppu_.spPalette()[i >> 1] = gbcToRgb32(objpData_[i] | objpData_[i + 1] << 8);
|
||||
}
|
||||
/* CGB mode: rebuild all 8 BG palette expansions, the CGB
|
||||
* renderer's hot do-while indexes [nattrib & 7] per tile. */
|
||||
for (unsigned p = 0; p < 8; ++p)
|
||||
ppu_.refreshBgPaletteExpansion(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ppu_.inDmgMode())
|
||||
{
|
||||
for (unsigned i = 0; i < 8 * 3; i += 2)
|
||||
dmgColorsRgb32_[i >> 1] = gbcToRgb32( dmgColorsGBC_[i] | dmgColorsGBC_[i + 1] << 8);
|
||||
}
|
||||
setDmgPalette(ppu_.bgPalette() , dmgColorsRgb32_ , bgpData_[0]);
|
||||
setDmgPalette(ppu_.spPalette() , dmgColorsRgb32_ + 4, objpData_[0]);
|
||||
setDmgPalette(ppu_.spPalette() + 4, dmgColorsRgb32_ + 8, objpData_[1]);
|
||||
/* DMG mode: only palette 0 is used by doFullTilesUnrolledDmg.
|
||||
* Rebuild slot 0; slots 1..7 are unused but stay zero-init. */
|
||||
ppu_.refreshBgPaletteExpansion(0);
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::resetCc(const unsigned long oldCc, const unsigned long newCc)
|
||||
{
|
||||
update(oldCc);
|
||||
ppu_.resetCc(oldCc, newCc);
|
||||
|
||||
if (ppu_.lcdc() & 0x80)
|
||||
{
|
||||
const unsigned long dec = oldCc - newCc;
|
||||
|
||||
nextM0Time_.invalidatePredictedNextM0Time();
|
||||
lycIrq_.reschedule(ppu_.lyCounter(), newCc);
|
||||
|
||||
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
|
||||
{
|
||||
if (eventTimes_(static_cast<MemEvent>(i)) != disabled_time)
|
||||
eventTimes_.set(static_cast<MemEvent>(i), eventTimes_(static_cast<MemEvent>(i)) - dec);
|
||||
}
|
||||
|
||||
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::speedChange(const unsigned long cycleCounter)
|
||||
{
|
||||
update(cycleCounter);
|
||||
ppu_.speedChange(cycleCounter);
|
||||
|
||||
if (ppu_.lcdc() & 0x80)
|
||||
{
|
||||
nextM0Time_.predictNextM0Time(ppu_);
|
||||
lycIrq_.reschedule(ppu_.lyCounter(), cycleCounter);
|
||||
|
||||
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
|
||||
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cycleCounter));
|
||||
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
|
||||
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cycleCounter));
|
||||
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cycleCounter));
|
||||
|
||||
if (eventTimes_(MODE0_IRQ) != disabled_time && eventTimes_(MODE0_IRQ) - cycleCounter > 1)
|
||||
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
|
||||
|
||||
if (hdmaIsEnabled() && eventTimes_(HDMA_REQ) - cycleCounter > 1)
|
||||
{
|
||||
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
|
||||
nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned long m0TimeOfCurrentLine(const unsigned long nextLyTime,
|
||||
const unsigned long lastM0Time, const unsigned long nextM0Time)
|
||||
{
|
||||
return nextM0Time < nextLyTime ? nextM0Time : lastM0Time;
|
||||
}
|
||||
|
||||
unsigned long LCD::m0TimeOfCurrentLine(const unsigned long cc)
|
||||
{
|
||||
if (cc >= nextM0Time_.predictedNextM0Time())
|
||||
{
|
||||
update(cc);
|
||||
nextM0Time_.predictNextM0Time(ppu_);
|
||||
}
|
||||
|
||||
return gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(), ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time());
|
||||
}
|
||||
|
||||
static bool isHdmaPeriod(const LyCounter &lyCounter,
|
||||
const unsigned long m0TimeOfCurrentLy, const unsigned long cycleCounter)
|
||||
{
|
||||
const unsigned timeToNextLy = lyCounter.time() - cycleCounter;
|
||||
|
||||
return /*(ppu_.lcdc & 0x80) && */lyCounter.ly() < 144 && timeToNextLy > 4
|
||||
&& cycleCounter >= hdmaTimeFromM0Time(m0TimeOfCurrentLy, lyCounter.isDoubleSpeed());
|
||||
}
|
||||
|
||||
void LCD::enableHdma(const unsigned long cycleCounter)
|
||||
{
|
||||
if (cycleCounter >= nextM0Time_.predictedNextM0Time())
|
||||
{
|
||||
update(cycleCounter);
|
||||
nextM0Time_.predictNextM0Time(ppu_);
|
||||
}
|
||||
else if (cycleCounter >= eventTimes_.nextEventTime())
|
||||
update(cycleCounter);
|
||||
|
||||
if (isHdmaPeriod(ppu_.lyCounter(),
|
||||
gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(),
|
||||
ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time()), cycleCounter))
|
||||
eventTimes_.flagHdmaReq();
|
||||
|
||||
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
|
||||
}
|
||||
|
||||
void LCD::disableHdma(const unsigned long cycleCounter)
|
||||
{
|
||||
if (cycleCounter >= eventTimes_.nextEventTime())
|
||||
update(cycleCounter);
|
||||
|
||||
eventTimes_.setm<HDMA_REQ>(disabled_time);
|
||||
}
|
||||
|
||||
bool LCD::vramAccessible(const unsigned long cc)
|
||||
{
|
||||
if (cc >= eventTimes_.nextEventTime())
|
||||
update(cc);
|
||||
|
||||
return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
|
||||
|| ppu_.lyCounter().lineCycles(cc) < 80U
|
||||
|| cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
|
||||
}
|
||||
|
||||
bool LCD::cgbpAccessible(const unsigned long cc)
|
||||
{
|
||||
if (cc >= eventTimes_.nextEventTime())
|
||||
update(cc);
|
||||
|
||||
return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
|
||||
|| ppu_.lyCounter().lineCycles(cc) < 80U + isDoubleSpeed()
|
||||
|| cc >= m0TimeOfCurrentLine(cc) + 3 - isDoubleSpeed();
|
||||
}
|
||||
|
||||
void LCD::doCgbBgColorChange(unsigned index, const unsigned data, const unsigned long cc)
|
||||
{
|
||||
if (cgbpAccessible(cc))
|
||||
{
|
||||
update(cc);
|
||||
doCgbColorChange(bgpData_, ppu_.bgPalette(), index, data);
|
||||
/* CGB BCPD writes one 16-bit colour entry at a time
|
||||
* (palette[index >> 1] in doCgbColorChange). Each CGB palette
|
||||
* has 4 entries x 2 bytes = 8 bytes, so the affected palette
|
||||
* is (index >> 1) >> 2 = index >> 3. Only that slot needs
|
||||
* its expansion rebuilt. */
|
||||
ppu_.refreshBgPaletteExpansion(index >> 3);
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::doCgbSpColorChange(unsigned index, const unsigned data, const unsigned long cc)
|
||||
{
|
||||
if (cgbpAccessible(cc))
|
||||
{
|
||||
update(cc);
|
||||
doCgbColorChange(objpData_, ppu_.spPalette(), index, data);
|
||||
}
|
||||
}
|
||||
|
||||
bool LCD::oamReadable(const unsigned long cc)
|
||||
{
|
||||
if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
|
||||
return true;
|
||||
|
||||
if (cc >= eventTimes_.nextEventTime())
|
||||
update(cc);
|
||||
|
||||
if (ppu_.lyCounter().lineCycles(cc) + 4 - ppu_.lyCounter().isDoubleSpeed() * 3u >= 456)
|
||||
return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
|
||||
|
||||
return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
|
||||
}
|
||||
|
||||
bool LCD::oamWritable(const unsigned long cc)
|
||||
{
|
||||
if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
|
||||
return true;
|
||||
|
||||
if (cc >= eventTimes_.nextEventTime())
|
||||
update(cc);
|
||||
|
||||
if (ppu_.lyCounter().lineCycles(cc) + 3 + ppu_.cgb() - ppu_.lyCounter().isDoubleSpeed() * 2u >= 456)
|
||||
return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
|
||||
|
||||
return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
|
||||
}
|
||||
|
||||
void LCD::mode3CyclesChange()
|
||||
{
|
||||
nextM0Time_.invalidatePredictedNextM0Time();
|
||||
|
||||
if (eventTimes_(MODE0_IRQ) != disabled_time
|
||||
&& eventTimes_(MODE0_IRQ) > m0IrqTimeFromXpos166Time(ppu_.now(), ppu_.cgb(), isDoubleSpeed())) {
|
||||
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
|
||||
}
|
||||
|
||||
if (eventTimes_(HDMA_REQ) != disabled_time
|
||||
&& eventTimes_(HDMA_REQ) > hdmaTimeFromM0Time(ppu_.lastM0Time(), isDoubleSpeed())) {
|
||||
nextM0Time_.predictNextM0Time(ppu_);
|
||||
eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::wxChange(const unsigned newValue, const unsigned long cycleCounter)
|
||||
{
|
||||
update(cycleCounter + isDoubleSpeed() + 1);
|
||||
ppu_.setWx(newValue);
|
||||
mode3CyclesChange();
|
||||
}
|
||||
|
||||
void LCD::wyChange(const unsigned newValue, const unsigned long cc)
|
||||
{
|
||||
update(cc + 1);
|
||||
ppu_.setWy(newValue);
|
||||
// mode3CyclesChange(); // should be safe to wait until after wy2 delay, because no mode3 events are close to when wy1 is read.
|
||||
|
||||
// wy2 is a delayed version of wy. really just slowness of ly == wy comparison.
|
||||
if (ppu_.cgb() && (ppu_.lcdc() & 0x80)) {
|
||||
eventTimes_.setm<ONESHOT_UPDATEWY2>(cc + 5);
|
||||
} else {
|
||||
update(cc + 2);
|
||||
ppu_.updateWy2();
|
||||
mode3CyclesChange();
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::scxChange(const unsigned newScx, const unsigned long cycleCounter) {
|
||||
update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
|
||||
ppu_.setScx(newScx);
|
||||
mode3CyclesChange();
|
||||
}
|
||||
|
||||
void LCD::scyChange(const unsigned newValue, const unsigned long cycleCounter) {
|
||||
update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
|
||||
ppu_.setScy(newValue);
|
||||
}
|
||||
|
||||
void LCD::oamChange(const unsigned long cc) {
|
||||
if (ppu_.lcdc() & 0x80) {
|
||||
update(cc);
|
||||
ppu_.oamChange(cc);
|
||||
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::oamChange(const unsigned char *const oamram, const unsigned long cc) {
|
||||
update(cc);
|
||||
ppu_.oamChange(oamram, cc);
|
||||
|
||||
if (ppu_.lcdc() & 0x80)
|
||||
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
|
||||
}
|
||||
|
||||
void LCD::lcdcChange(const unsigned data, const unsigned long cc) {
|
||||
const unsigned oldLcdc = ppu_.lcdc();
|
||||
update(cc);
|
||||
|
||||
if ((oldLcdc ^ data) & 0x80)
|
||||
{
|
||||
ppu_.setLcdc(data, cc);
|
||||
|
||||
if (data & 0x80) {
|
||||
lycIrq_.lcdReset();
|
||||
m0Irq_.lcdReset(statReg_, lycIrq_.lycReg());
|
||||
|
||||
if (lycIrq_.lycReg() == 0 && (statReg_ & 0x40))
|
||||
eventTimes_.flagIrq(2);
|
||||
|
||||
nextM0Time_.predictNextM0Time(ppu_);
|
||||
lycIrq_.reschedule(ppu_.lyCounter(), cc);
|
||||
|
||||
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
|
||||
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
|
||||
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
|
||||
eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cc));
|
||||
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cc));
|
||||
|
||||
if (statReg_ & 0x08)
|
||||
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
|
||||
|
||||
if (hdmaIsEnabled())
|
||||
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
|
||||
nextM0Time_.predictedNextM0Time(), cc, isDoubleSpeed()));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < NUM_MEM_EVENTS; ++i)
|
||||
eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
|
||||
}
|
||||
}
|
||||
else if (data & 0x80)
|
||||
{
|
||||
if (ppu_.cgb())
|
||||
{
|
||||
ppu_.setLcdc((oldLcdc & ~0x14) | (data & 0x14), cc);
|
||||
|
||||
if ((oldLcdc ^ data) & 0x04)
|
||||
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
|
||||
|
||||
update(cc + isDoubleSpeed() + 1);
|
||||
ppu_.setLcdc(data, cc + isDoubleSpeed() + 1);
|
||||
|
||||
if ((oldLcdc ^ data) & 0x20)
|
||||
mode3CyclesChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
ppu_.setLcdc(data, cc);
|
||||
|
||||
if ((oldLcdc ^ data) & 0x04)
|
||||
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
|
||||
|
||||
if ((oldLcdc ^ data) & 0x22)
|
||||
mode3CyclesChange();
|
||||
}
|
||||
}
|
||||
else
|
||||
ppu_.setLcdc(data, cc);
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct LyCnt {
|
||||
unsigned ly; int timeToNextLy;
|
||||
LyCnt(unsigned ly, int timeToNextLy) : ly(ly), timeToNextLy(timeToNextLy) {}
|
||||
};
|
||||
|
||||
static LyCnt const getLycCmpLy(LyCounter const &lyCounter, unsigned long cc) {
|
||||
unsigned ly = lyCounter.ly();
|
||||
int timeToNextLy = lyCounter.time() - cc;
|
||||
|
||||
if (ly == 153)
|
||||
{
|
||||
unsigned is_doublespeed = (unsigned)lyCounter.isDoubleSpeed();
|
||||
if (timeToNextLy - (448 << is_doublespeed) > 0)
|
||||
timeToNextLy -= (448 << is_doublespeed);
|
||||
else
|
||||
{
|
||||
ly = 0;
|
||||
timeToNextLy += lyCounter.lineTime();
|
||||
}
|
||||
}
|
||||
return LyCnt(ly, timeToNextLy);
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::lcdstatChange(const unsigned data, const unsigned long cc)
|
||||
{
|
||||
if (cc >= eventTimes_.nextEventTime())
|
||||
update(cc);
|
||||
|
||||
const unsigned old = statReg_;
|
||||
statReg_ = data;
|
||||
lycIrq_.statRegChange(data, ppu_.lyCounter(), cc);
|
||||
|
||||
if (ppu_.lcdc() & 0x80)
|
||||
{
|
||||
int const timeToNextLy = ppu_.lyCounter().time() - cc;
|
||||
LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
|
||||
|
||||
if (!ppu_.cgb())
|
||||
{
|
||||
if (ppu_.lyCounter().ly() < 144)
|
||||
{
|
||||
if (cc + 1 < m0TimeOfCurrentLine(cc))
|
||||
{
|
||||
if (lycCmp.ly == lycIrq_.lycReg() && !(old & 0x40))
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(old & 0x08) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(old & 0x10) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
}
|
||||
else if (data & ~old & 0x78)
|
||||
{
|
||||
bool const lycperiod = lycCmp.ly == lycIrq_.lycReg() && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4;
|
||||
|
||||
if (!(lycperiod && (old & 0x40)))
|
||||
{
|
||||
if (ppu_.lyCounter().ly() < 144)
|
||||
{
|
||||
if (cc + isDoubleSpeed() * 2 < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4)
|
||||
{
|
||||
if (lycperiod && (data & 0x40))
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
else if (!(old & 0x08))
|
||||
{
|
||||
if ((data & 0x08) || (lycperiod && (data & 0x40)))
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
}
|
||||
else if (!(old & 0x10))
|
||||
{
|
||||
if ((data & 0x10) && (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4))
|
||||
{
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
else if (lycperiod && (data & 0x40))
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
}
|
||||
|
||||
if ((data & 0x28) == 0x20 && !(old & 0x20)
|
||||
&& ((timeToNextLy <= 4 && ppu_.lyCounter().ly() < 143)
|
||||
|| (timeToNextLy == 456*2 && ppu_.lyCounter().ly() < 144)))
|
||||
{
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
}
|
||||
|
||||
if ((data & 0x08) && eventTimes_(MODE0_IRQ) == disabled_time)
|
||||
{
|
||||
update(cc);
|
||||
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
|
||||
}
|
||||
|
||||
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(data, ppu_.lyCounter(), cc));
|
||||
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
|
||||
}
|
||||
|
||||
m2IrqStatReg_ = eventTimes_(MODE2_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
|
||||
? data : (m2IrqStatReg_ & 0x10) | (statReg_ & ~0x10);
|
||||
m1IrqStatReg_ = eventTimes_(MODE1_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
|
||||
? data : (m1IrqStatReg_ & 0x08) | (statReg_ & ~0x08);
|
||||
|
||||
m0Irq_.statRegChange(data, eventTimes_(MODE0_IRQ), cc, ppu_.cgb());
|
||||
}
|
||||
|
||||
void LCD::lycRegChange(const unsigned data, const unsigned long cc)
|
||||
{
|
||||
unsigned const old = lycIrq_.lycReg();
|
||||
|
||||
if (data == old)
|
||||
return;
|
||||
|
||||
if (cc >= eventTimes_.nextEventTime())
|
||||
update(cc);
|
||||
|
||||
m0Irq_.lycRegChange(data, eventTimes_(MODE0_IRQ), cc, isDoubleSpeed(), ppu_.cgb());
|
||||
lycIrq_.lycRegChange(data, ppu_.lyCounter(), cc);
|
||||
|
||||
if (!(ppu_.lcdc() & 0x80))
|
||||
return;
|
||||
|
||||
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
|
||||
|
||||
int const timeToNextLy = ppu_.lyCounter().time() - cc;
|
||||
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
|
||||
|
||||
if ((statReg_ & 0x40) && data < 154
|
||||
&& (ppu_.lyCounter().ly() < 144
|
||||
? !(statReg_ & 0x08) || cc < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4 << ppu_.cgb()
|
||||
: !(statReg_ & 0x10) || (ppu_.lyCounter().ly() == 153 && timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)))
|
||||
{
|
||||
LyCnt lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
|
||||
|
||||
if (lycCmp.timeToNextLy <= 4 << ppu_.cgb())
|
||||
{
|
||||
lycCmp.ly = old != lycCmp.ly || (lycCmp.timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)
|
||||
? (lycCmp.ly == 153 ? 0 : lycCmp.ly + 1)
|
||||
: 0xFF; // simultaneous ly/lyc inc. lyc flag never goes low -> no trigger.
|
||||
}
|
||||
|
||||
if (data == lycCmp.ly)
|
||||
{
|
||||
if (ppu_.cgb() && !(bool)is_doublespeed)
|
||||
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(cc + 5);
|
||||
else
|
||||
eventTimes_.flagIrq(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned LCD::getStat(const unsigned lycReg, const unsigned long cc)
|
||||
{
|
||||
unsigned stat = 0;
|
||||
|
||||
if (ppu_.lcdc() & 0x80)
|
||||
{
|
||||
if (cc >= eventTimes_.nextEventTime())
|
||||
update(cc);
|
||||
|
||||
int const timeToNextLy = ppu_.lyCounter().time() - cc;
|
||||
unsigned is_doublespeed = (unsigned)isDoubleSpeed();
|
||||
|
||||
if (ppu_.lyCounter().ly() > 143)
|
||||
{
|
||||
if (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - is_doublespeed * 4)
|
||||
stat = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned const lineCycles = 456 - (timeToNextLy >> is_doublespeed);
|
||||
|
||||
if (lineCycles < 80)
|
||||
{
|
||||
if (!ppu_.inactivePeriodAfterDisplayEnable(cc))
|
||||
stat = 2;
|
||||
}
|
||||
else if (cc + is_doublespeed - ppu_.cgb() + 2 < m0TimeOfCurrentLine(cc))
|
||||
stat = 3;
|
||||
}
|
||||
|
||||
LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
|
||||
|
||||
if (lycReg == lycCmp.ly && lycCmp.timeToNextLy > 4 - is_doublespeed * 4)
|
||||
stat |= 4;
|
||||
}
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
inline void LCD::doMode2IrqEvent()
|
||||
{
|
||||
const unsigned ly = eventTimes_(LY_COUNT) - eventTimes_(MODE2_IRQ) < 8
|
||||
? (ppu_.lyCounter().ly() == 153 ? 0 : ppu_.lyCounter().ly() + 1)
|
||||
: ppu_.lyCounter().ly();
|
||||
|
||||
if ((ly != 0 || !(m2IrqStatReg_ & 0x10)) &&
|
||||
(!(m2IrqStatReg_ & 0x40) || (lycIrq_.lycReg() != 0 ? ly != (lycIrq_.lycReg() + 1U) : ly > 1)))
|
||||
eventTimes_.flagIrq(2);
|
||||
|
||||
m2IrqStatReg_ = statReg_;
|
||||
|
||||
if (!(statReg_ & 0x08))
|
||||
{
|
||||
unsigned long nextTime = eventTimes_(MODE2_IRQ) + ppu_.lyCounter().lineTime();
|
||||
|
||||
if (ly == 0)
|
||||
nextTime -= 4;
|
||||
else if (ly == 143)
|
||||
nextTime += ppu_.lyCounter().lineTime() * 10 + 4;
|
||||
|
||||
eventTimes_.setm<MODE2_IRQ>(nextTime);
|
||||
}
|
||||
else
|
||||
eventTimes_.setm<MODE2_IRQ>(eventTimes_(MODE2_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
|
||||
}
|
||||
|
||||
inline void LCD::event()
|
||||
{
|
||||
switch (eventTimes_.nextEvent())
|
||||
{
|
||||
case MEM_EVENT:
|
||||
switch (eventTimes_.nextMemEvent())
|
||||
{
|
||||
case MODE1_IRQ:
|
||||
eventTimes_.flagIrq((m1IrqStatReg_ & 0x18) == 0x10 ? 3 : 1);
|
||||
m1IrqStatReg_ = statReg_;
|
||||
eventTimes_.setm<MODE1_IRQ>(eventTimes_(MODE1_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
|
||||
break;
|
||||
|
||||
case LYC_IRQ:
|
||||
{
|
||||
unsigned char ifreg = 0;
|
||||
lycIrq_.doEvent(&ifreg, ppu_.lyCounter());
|
||||
eventTimes_.flagIrq(ifreg);
|
||||
eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
|
||||
}
|
||||
break;
|
||||
case SPRITE_MAP:
|
||||
eventTimes_.setm<SPRITE_MAP>(ppu_.doSpriteMapEvent(eventTimes_(SPRITE_MAP)));
|
||||
mode3CyclesChange();
|
||||
break;
|
||||
case HDMA_REQ:
|
||||
eventTimes_.flagHdmaReq();
|
||||
nextM0Time_.predictNextM0Time(ppu_);
|
||||
eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
|
||||
break;
|
||||
case MODE2_IRQ:
|
||||
doMode2IrqEvent();
|
||||
break;
|
||||
case MODE0_IRQ:
|
||||
{
|
||||
unsigned char ifreg = 0;
|
||||
m0Irq_.doEvent(&ifreg, ppu_.lyCounter().ly(), statReg_, lycIrq_.lycReg());
|
||||
eventTimes_.flagIrq(ifreg);
|
||||
}
|
||||
|
||||
eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08)
|
||||
? m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed())
|
||||
: static_cast<unsigned long>(disabled_time));
|
||||
break;
|
||||
case ONESHOT_LCDSTATIRQ:
|
||||
eventTimes_.flagIrq(2);
|
||||
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(disabled_time);
|
||||
break;
|
||||
case ONESHOT_UPDATEWY2:
|
||||
ppu_.updateWy2();
|
||||
mode3CyclesChange();
|
||||
eventTimes_.setm<ONESHOT_UPDATEWY2>(disabled_time);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case LY_COUNT:
|
||||
ppu_.doLyCountEvent();
|
||||
eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::update(const unsigned long cycleCounter)
|
||||
{
|
||||
if (!(ppu_.lcdc() & 0x80))
|
||||
return;
|
||||
|
||||
while (cycleCounter >= eventTimes_.nextEventTime())
|
||||
{
|
||||
ppu_.update(eventTimes_.nextEventTime());
|
||||
event();
|
||||
}
|
||||
|
||||
ppu_.update(cycleCounter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef VIDEO_H
|
||||
#define VIDEO_H
|
||||
|
||||
#include "interruptrequester.h"
|
||||
#include "video/lyc_irq.h"
|
||||
#include "video/m0_irq.h"
|
||||
#include "video/next_m0_time.h"
|
||||
#include "video/ppu.h"
|
||||
#include <memory>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class VideoInterruptRequester
|
||||
{
|
||||
public:
|
||||
explicit VideoInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {}
|
||||
void flagHdmaReq() const { gambatte::flagHdmaReq(intreq_); }
|
||||
void flagIrq(const unsigned bit) const { intreq_.flagIrq(bit); }
|
||||
void setNextEventTime(const unsigned long time) const { intreq_.setEventTime<intevent_video>(time); }
|
||||
|
||||
private:
|
||||
InterruptRequester &intreq_;
|
||||
};
|
||||
|
||||
class LCD
|
||||
{
|
||||
public:
|
||||
LCD(const unsigned char *oamram, const unsigned char *vram_in, VideoInterruptRequester memEventRequester);
|
||||
void reset(const unsigned char *oamram, unsigned char const *vram, bool cgb);
|
||||
void setStatePtrs(SaveState &state);
|
||||
void saveState(SaveState &state) const;
|
||||
void loadState(const SaveState &state, const unsigned char *oamram);
|
||||
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, video_pixel_t rgb32);
|
||||
void setVideoBuffer(video_pixel_t *videoBuf, int pitch);
|
||||
void setDmgMode(bool mode) { ppu_.setDmgMode(mode); }
|
||||
|
||||
void swapToDMG() {
|
||||
ppu_.setDmgMode(true);
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
void dmgBgPaletteChange(const unsigned data, const unsigned long cycleCounter) {
|
||||
update(cycleCounter);
|
||||
bgpData_[0] = data;
|
||||
setDmgPalette(ppu_.bgPalette(), dmgColorsRgb32_, data);
|
||||
ppu_.refreshBgPaletteExpansion(0);
|
||||
}
|
||||
|
||||
void dmgSpPalette1Change(const unsigned data, const unsigned long cycleCounter) {
|
||||
update(cycleCounter);
|
||||
objpData_[0] = data;
|
||||
setDmgPalette(ppu_.spPalette(), dmgColorsRgb32_ + 4, data);
|
||||
}
|
||||
|
||||
void dmgSpPalette2Change(const unsigned data, const unsigned long cycleCounter) {
|
||||
update(cycleCounter);
|
||||
objpData_[1] = data;
|
||||
setDmgPalette(ppu_.spPalette() + 4, dmgColorsRgb32_ + 8, data);
|
||||
}
|
||||
|
||||
void cgbBgColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) {
|
||||
if (bgpData_[index] != data) {
|
||||
doCgbBgColorChange(index, data, cycleCounter);
|
||||
if(index < 8)
|
||||
doCgbColorChange(dmgColorsGBC_, dmgColorsRgb32_, index, data);
|
||||
}
|
||||
}
|
||||
|
||||
void cgbSpColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) {
|
||||
if (objpData_[index] != data) {
|
||||
doCgbSpColorChange(index, data, cycleCounter);
|
||||
if(index < 8 * 2/*dmg has 2 sprite banks*/)
|
||||
doCgbColorChange(dmgColorsGBC_ + 8, dmgColorsRgb32_ + 4, index, data);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned cgbBgColorRead(const unsigned index, const unsigned long cycleCounter) {
|
||||
return (ppu_.cgb() & cgbpAccessible(cycleCounter)) ? bgpData_[index] : 0xFF;
|
||||
}
|
||||
|
||||
unsigned cgbSpColorRead(const unsigned index, const unsigned long cycleCounter) {
|
||||
return (ppu_.cgb() & cgbpAccessible(cycleCounter)) ? objpData_[index] : 0xFF;
|
||||
}
|
||||
|
||||
void updateScreen(bool blanklcd, unsigned long cc);
|
||||
void resetCc(unsigned long oldCC, unsigned long newCc);
|
||||
void speedChange(unsigned long cycleCounter);
|
||||
bool vramAccessible(unsigned long cycleCounter);
|
||||
bool oamReadable(unsigned long cycleCounter);
|
||||
bool oamWritable(unsigned long cycleCounter);
|
||||
void wxChange(unsigned newValue, unsigned long cycleCounter);
|
||||
void wyChange(unsigned newValue, unsigned long cycleCounter);
|
||||
void oamChange(unsigned long cycleCounter);
|
||||
void oamChange(const unsigned char *oamram, unsigned long cycleCounter);
|
||||
void scxChange(unsigned newScx, unsigned long cycleCounter);
|
||||
void scyChange(unsigned newValue, unsigned long cycleCounter);
|
||||
|
||||
void vramChange(const unsigned long cycleCounter) { update(cycleCounter); }
|
||||
|
||||
unsigned getStat(unsigned lycReg, unsigned long cycleCounter);
|
||||
|
||||
unsigned getLyReg(const unsigned long cycleCounter) {
|
||||
unsigned lyReg = 0;
|
||||
|
||||
if (ppu_.lcdc() & 0x80) {
|
||||
if (cycleCounter >= ppu_.lyCounter().time())
|
||||
update(cycleCounter);
|
||||
|
||||
lyReg = ppu_.lyCounter().ly();
|
||||
|
||||
if (lyReg == 153) {
|
||||
if (isDoubleSpeed()) {
|
||||
if (ppu_.lyCounter().time() - cycleCounter <= 456 * 2 - 8)
|
||||
lyReg = 0;
|
||||
} else
|
||||
lyReg = 0;
|
||||
} else if (ppu_.lyCounter().time() - cycleCounter <= 4)
|
||||
++lyReg;
|
||||
}
|
||||
|
||||
return lyReg;
|
||||
}
|
||||
|
||||
unsigned long nextMode1IrqTime() const { return eventTimes_(MODE1_IRQ); }
|
||||
|
||||
void lcdcChange(unsigned data, unsigned long cycleCounter);
|
||||
void lcdstatChange(unsigned data, unsigned long cycleCounter);
|
||||
void lycRegChange(unsigned data, unsigned long cycleCounter);
|
||||
|
||||
void enableHdma(unsigned long cycleCounter);
|
||||
void disableHdma(unsigned long cycleCounter);
|
||||
bool hdmaIsEnabled() const { return eventTimes_(HDMA_REQ) != disabled_time; }
|
||||
|
||||
void update(unsigned long cycleCounter);
|
||||
|
||||
bool isCgb() const { return ppu_.cgb(); }
|
||||
bool isDoubleSpeed() const { return ppu_.lyCounter().isDoubleSpeed(); }
|
||||
|
||||
void setColorCorrection(bool colorCorrection);
|
||||
void setColorCorrectionMode(unsigned colorCorrectionMode);
|
||||
void setColorCorrectionBrightness(float colorCorrectionBrightness);
|
||||
void setDarkFilterLevel(unsigned darkFilterLevel);
|
||||
video_pixel_t gbcToRgb32(const unsigned bgr15);
|
||||
private:
|
||||
enum Event { MEM_EVENT, LY_COUNT }; enum { NUM_EVENTS = LY_COUNT + 1 };
|
||||
enum MemEvent { ONESHOT_LCDSTATIRQ, ONESHOT_UPDATEWY2, MODE1_IRQ, LYC_IRQ, SPRITE_MAP,
|
||||
HDMA_REQ, MODE2_IRQ, MODE0_IRQ }; enum { NUM_MEM_EVENTS = MODE0_IRQ + 1 };
|
||||
|
||||
class EventTimes
|
||||
{
|
||||
public:
|
||||
explicit EventTimes(const VideoInterruptRequester memEventRequester) : memEventRequester_(memEventRequester) {}
|
||||
|
||||
Event nextEvent() const { return static_cast<Event>(eventMin_.min()); }
|
||||
unsigned long nextEventTime() const { return eventMin_.minValue(); }
|
||||
unsigned long operator()(const Event e) const { return eventMin_.value(e); }
|
||||
template<Event e> void set(const unsigned long time) { eventMin_.setValue<e>(time); }
|
||||
void set(const Event e, const unsigned long time) { eventMin_.setValue(e, time); }
|
||||
|
||||
MemEvent nextMemEvent() const { return static_cast<MemEvent>(memEventMin_.min()); }
|
||||
unsigned long nextMemEventTime() const { return memEventMin_.minValue(); }
|
||||
unsigned long operator()(const MemEvent e) const { return memEventMin_.value(e); }
|
||||
template<MemEvent e> void setm(const unsigned long time) { memEventMin_.setValue<e>(time); setMemEvent(); }
|
||||
void set(const MemEvent e, const unsigned long time) { memEventMin_.setValue(e, time); setMemEvent(); }
|
||||
|
||||
void flagIrq(const unsigned bit) { memEventRequester_.flagIrq(bit); }
|
||||
void flagHdmaReq() { memEventRequester_.flagHdmaReq(); }
|
||||
|
||||
private:
|
||||
MinKeeper<NUM_EVENTS> eventMin_;
|
||||
MinKeeper<NUM_MEM_EVENTS> memEventMin_;
|
||||
VideoInterruptRequester memEventRequester_;
|
||||
|
||||
void setMemEvent() {
|
||||
const unsigned long nmet = nextMemEventTime();
|
||||
eventMin_.setValue<MEM_EVENT>(nmet);
|
||||
memEventRequester_.setNextEventTime(nmet);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
PPU ppu_;
|
||||
video_pixel_t dmgColorsRgb32_[3 * 4];
|
||||
unsigned char dmgColorsGBC_[3 * 8];
|
||||
unsigned char bgpData_[8 * 8];
|
||||
unsigned char objpData_[8 * 8];
|
||||
|
||||
EventTimes eventTimes_;
|
||||
M0Irq m0Irq_;
|
||||
LycIrq lycIrq_;
|
||||
NextM0Time nextM0Time_;
|
||||
|
||||
unsigned char statReg_;
|
||||
unsigned char m2IrqStatReg_;
|
||||
unsigned char m1IrqStatReg_;
|
||||
|
||||
static void setDmgPalette(video_pixel_t *palette, const video_pixel_t *dmgColors, unsigned data);
|
||||
void setDmgPaletteColor(unsigned index, video_pixel_t rgb32);
|
||||
|
||||
void setDBuffer();
|
||||
void refreshPalettes();
|
||||
|
||||
void doMode2IrqEvent();
|
||||
void event();
|
||||
|
||||
unsigned long m0TimeOfCurrentLine(unsigned long cc);
|
||||
bool cgbpAccessible(unsigned long cycleCounter);
|
||||
|
||||
void mode3CyclesChange();
|
||||
void doCgbBgColorChange(unsigned index, unsigned data, unsigned long cycleCounter);
|
||||
void doCgbSpColorChange(unsigned index, unsigned data, unsigned long cycleCounter);
|
||||
|
||||
bool colorCorrection;
|
||||
unsigned colorCorrectionMode;
|
||||
float colorCorrectionBrightness;
|
||||
unsigned darkFilterLevel;
|
||||
void doCgbColorChange(unsigned char *const pdata,
|
||||
video_pixel_t *const palette, unsigned index, const unsigned data);
|
||||
|
||||
void darkenRgb(float &r, float &g, float &b);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef LCDDEF_H
|
||||
#define LCDDEF_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
enum { lcdc_bgen = 0x01,
|
||||
lcdc_objen = 0x02,
|
||||
lcdc_obj2x = 0x04,
|
||||
lcdc_tdsel = 0x10,
|
||||
lcdc_we = 0x20,
|
||||
lcdc_en = 0x80 };
|
||||
|
||||
enum { lcdstat_lycflag = 0x04,
|
||||
lcdstat_m0irqen = 0x08,
|
||||
lcdstat_m1irqen = 0x10,
|
||||
lcdstat_m2irqen = 0x20,
|
||||
lcdstat_lycirqen = 0x40 };
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "ly_counter.h"
|
||||
#include "../savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
LyCounter::LyCounter()
|
||||
: time_(0)
|
||||
, lineTime_(0)
|
||||
, ly_(0)
|
||||
, ds_(false)
|
||||
{
|
||||
setDoubleSpeed(false);
|
||||
reset(0, 0);
|
||||
}
|
||||
|
||||
void LyCounter::doEvent() {
|
||||
++ly_;
|
||||
if (ly_ == 154)
|
||||
ly_ = 0;
|
||||
|
||||
time_ = time_ + lineTime_;
|
||||
}
|
||||
|
||||
unsigned long LyCounter::nextLineCycle(unsigned const lineCycle, unsigned long const cc) const {
|
||||
unsigned long tmp = time_ + (lineCycle << (unsigned)ds_);
|
||||
if (tmp - cc > lineTime_)
|
||||
tmp -= lineTime_;
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
unsigned long LyCounter::nextFrameCycle(unsigned long const frameCycle, unsigned long const cc) const {
|
||||
unsigned long tmp = time_ + (((153U - ly()) * 456U + frameCycle) << (unsigned)ds_);
|
||||
if (tmp - cc > 70224U << (unsigned)ds_)
|
||||
tmp -= 70224U << (unsigned)ds_;
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void LyCounter::reset(unsigned long videoCycles, unsigned long lastUpdate) {
|
||||
ly_ = videoCycles / 456;
|
||||
time_ = lastUpdate + ((456 - (videoCycles - ly_ * 456ul)) << (unsigned)isDoubleSpeed());
|
||||
}
|
||||
|
||||
void LyCounter::setDoubleSpeed(bool ds) {
|
||||
ds_ = ds;
|
||||
lineTime_ = 456U << (unsigned)ds;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef LY_COUNTER_H
|
||||
#define LY_COUNTER_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
|
||||
class LyCounter {
|
||||
public:
|
||||
LyCounter();
|
||||
void doEvent();
|
||||
bool isDoubleSpeed() const { return ds_; }
|
||||
|
||||
unsigned long frameCycles(unsigned long cc) const {
|
||||
return ly_ * 456ul + lineCycles(cc);
|
||||
}
|
||||
|
||||
unsigned lineCycles(unsigned long cc) const {
|
||||
return 456u - ((time_ - cc) >> (unsigned)isDoubleSpeed());
|
||||
}
|
||||
|
||||
unsigned lineTime() const { return lineTime_; }
|
||||
unsigned ly() const { return ly_; }
|
||||
unsigned long nextLineCycle(unsigned lineCycle, unsigned long cycleCounter) const;
|
||||
unsigned long nextFrameCycle(unsigned long frameCycle, unsigned long cycleCounter) const;
|
||||
void reset(unsigned long videoCycles, unsigned long lastUpdate);
|
||||
void setDoubleSpeed(bool ds);
|
||||
unsigned long time() const { return time_; }
|
||||
|
||||
private:
|
||||
unsigned long time_;
|
||||
unsigned short lineTime_;
|
||||
unsigned char ly_;
|
||||
bool ds_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "lyc_irq.h"
|
||||
#include "counterdef.h"
|
||||
#include "lcddef.h"
|
||||
#include "ly_counter.h"
|
||||
#include "savestate.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
LycIrq::LycIrq()
|
||||
: time_(disabled_time)
|
||||
, lycRegSrc_(0)
|
||||
, statRegSrc_(0)
|
||||
, lycReg_(0)
|
||||
, statReg_(0)
|
||||
, cgb_(false)
|
||||
{
|
||||
}
|
||||
|
||||
static unsigned long schedule(unsigned statReg,
|
||||
unsigned lycReg, LyCounter const &lyCounter, unsigned long cc) {
|
||||
return (statReg & lcdstat_lycirqen) && lycReg < 154
|
||||
? lyCounter.nextFrameCycle(lycReg ? lycReg * 456 : 153 * 456 + 8, cc)
|
||||
: static_cast<unsigned long>(disabled_time);
|
||||
}
|
||||
|
||||
void LycIrq::regChange(unsigned const statReg,
|
||||
unsigned const lycReg, LyCounter const &lyCounter, unsigned long const cc) {
|
||||
unsigned long const timeSrc = schedule(statReg, lycReg, lyCounter, cc);
|
||||
statRegSrc_ = statReg;
|
||||
lycRegSrc_ = lycReg;
|
||||
time_ = std::min(time_, timeSrc);
|
||||
|
||||
if (cgb_) {
|
||||
if (time_ - cc > 8 || (timeSrc != time_ && time_ - cc > 4U - lyCounter.isDoubleSpeed() * 4U))
|
||||
lycReg_ = lycReg;
|
||||
|
||||
if (time_ - cc > 4U - lyCounter.isDoubleSpeed() * 4U)
|
||||
statReg_ = statReg;
|
||||
} else {
|
||||
if (time_ - cc > 4 || timeSrc != time_)
|
||||
lycReg_ = lycReg;
|
||||
|
||||
if (time_ - cc > 4 || lycReg_ != 0)
|
||||
statReg_ = statReg;
|
||||
|
||||
statReg_ = (statReg_ & lcdstat_lycirqen) | (statReg & ~lcdstat_lycirqen);
|
||||
}
|
||||
}
|
||||
|
||||
static bool lycIrqBlockedByM2OrM1StatIrq(unsigned ly, unsigned statreg) {
|
||||
return ly - 1u < 144u - 1u
|
||||
? statreg & lcdstat_m2irqen
|
||||
: statreg & lcdstat_m1irqen;
|
||||
}
|
||||
|
||||
void LycIrq::doEvent(unsigned char *const ifreg, LyCounter const &lyCounter) {
|
||||
if ((statReg_ | statRegSrc_) & lcdstat_lycirqen) {
|
||||
unsigned cmpLy = lyCounter.time() - time_ < lyCounter.lineTime() ? 0 : lyCounter.ly();
|
||||
if (lycReg_ == cmpLy && !lycIrqBlockedByM2OrM1StatIrq(lycReg_, statReg_))
|
||||
*ifreg |= 2;
|
||||
}
|
||||
|
||||
lycReg_ = lycRegSrc_;
|
||||
statReg_ = statRegSrc_;
|
||||
time_ = schedule(statReg_, lycReg_, lyCounter, time_);
|
||||
}
|
||||
|
||||
void LycIrq::loadState(SaveState const &state) {
|
||||
lycRegSrc_ = state.mem.ioamhram.get()[0x145];
|
||||
statRegSrc_ = state.mem.ioamhram.get()[0x141];
|
||||
lycReg_ = state.ppu.lyc;
|
||||
statReg_ = statRegSrc_;
|
||||
}
|
||||
|
||||
void LycIrq::saveState(SaveState &state) const {
|
||||
state.ppu.lyc = lycReg_;
|
||||
}
|
||||
|
||||
void LycIrq::reschedule(LyCounter const &lyCounter, unsigned long cc) {
|
||||
time_ = std::min(schedule(statReg_ , lycReg_ , lyCounter, cc),
|
||||
schedule(statRegSrc_, lycRegSrc_, lyCounter, cc));
|
||||
}
|
||||
|
||||
void LycIrq::lcdReset() {
|
||||
statReg_ = statRegSrc_;
|
||||
lycReg_ = lycRegSrc_;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef VIDEO_LYC_IRQ_H
|
||||
#define VIDEO_LYC_IRQ_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct SaveState;
|
||||
class LyCounter;
|
||||
|
||||
class LycIrq {
|
||||
public:
|
||||
LycIrq();
|
||||
void doEvent(unsigned char *ifreg, LyCounter const &lyCounter);
|
||||
unsigned lycReg() const { return lycRegSrc_; }
|
||||
void loadState(SaveState const &state);
|
||||
void saveState(SaveState &state) const;
|
||||
unsigned long time() const { return time_; }
|
||||
void setCgb(bool cgb) { cgb_ = cgb; }
|
||||
void lcdReset();
|
||||
void reschedule(LyCounter const &lyCounter, unsigned long cc);
|
||||
|
||||
void statRegChange(unsigned statReg, LyCounter const &lyCounter, unsigned long cc) {
|
||||
regChange(statReg, lycRegSrc_, lyCounter, cc);
|
||||
}
|
||||
|
||||
void lycRegChange(unsigned lycReg, LyCounter const &lyCounter, unsigned long cc) {
|
||||
regChange(statRegSrc_, lycReg, lyCounter, cc);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned long time_;
|
||||
unsigned char lycRegSrc_;
|
||||
unsigned char statRegSrc_;
|
||||
unsigned char lycReg_;
|
||||
unsigned char statReg_;
|
||||
bool cgb_;
|
||||
|
||||
void regChange(unsigned statReg, unsigned lycReg,
|
||||
LyCounter const &lyCounter, unsigned long cc);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef M0_IRQ_H
|
||||
#define M0_IRQ_H
|
||||
|
||||
#include "lcddef.h"
|
||||
#include "../savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class M0Irq {
|
||||
public:
|
||||
M0Irq()
|
||||
: statReg_(0)
|
||||
, lycReg_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void lcdReset(unsigned statReg, unsigned lycReg) {
|
||||
statReg_ = statReg;
|
||||
lycReg_ = lycReg;
|
||||
}
|
||||
|
||||
void statRegChange(unsigned statReg,
|
||||
unsigned long nextM0IrqTime, unsigned long cc, bool cgb) {
|
||||
if (nextM0IrqTime - cc > cgb * 2U)
|
||||
statReg_ = statReg;
|
||||
}
|
||||
|
||||
void lycRegChange(unsigned lycReg,
|
||||
unsigned long nextM0IrqTime, unsigned long cc,
|
||||
bool ds, bool cgb) {
|
||||
if (nextM0IrqTime - cc > cgb * 5 + 1U - ds)
|
||||
lycReg_ = lycReg;
|
||||
}
|
||||
|
||||
void doEvent(unsigned char *ifreg, unsigned ly, unsigned statReg, unsigned lycReg) {
|
||||
if (((statReg_ | statReg) & lcdstat_m0irqen)
|
||||
&& (!(statReg_ & lcdstat_lycirqen) || ly != lycReg_)) {
|
||||
*ifreg |= 2;
|
||||
}
|
||||
|
||||
statReg_ = statReg;
|
||||
lycReg_ = lycReg;
|
||||
}
|
||||
|
||||
void saveState(SaveState &state) const {
|
||||
state.ppu.m0lyc = lycReg_;
|
||||
}
|
||||
|
||||
void loadState(SaveState const &state) {
|
||||
lycReg_ = state.ppu.m0lyc;
|
||||
statReg_ = state.mem.ioamhram.get()[0x141];
|
||||
}
|
||||
|
||||
unsigned statReg() const { return statReg_; }
|
||||
|
||||
private:
|
||||
unsigned char statReg_;
|
||||
unsigned char lycReg_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "next_m0_time.h"
|
||||
#include "ppu.h"
|
||||
|
||||
void gambatte::NextM0Time::predictNextM0Time(PPU const &ppu) {
|
||||
predictedNextM0Time_ = ppu.predictedNextXposTime(167);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef NEXT_M0_TIME_H_
|
||||
#define NEXT_M0_TIME_H_
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class NextM0Time {
|
||||
public:
|
||||
NextM0Time() : predictedNextM0Time_(0) {}
|
||||
void predictNextM0Time(class PPU const &v);
|
||||
void invalidatePredictedNextM0Time() { predictedNextM0Time_ = 0; }
|
||||
unsigned predictedNextM0Time() const { return predictedNextM0Time_; }
|
||||
|
||||
private:
|
||||
unsigned predictedNextM0Time_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// Copyright (C) 2010 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef PPU_H
|
||||
#define PPU_H
|
||||
|
||||
#include "lcddef.h"
|
||||
#include "ly_counter.h"
|
||||
#include "sprite_mapper.h"
|
||||
#include "gbint.h"
|
||||
#include "gambatte.h"
|
||||
#include <cstddef>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class PPUFrameBuf {
|
||||
public:
|
||||
PPUFrameBuf() : buf_(0), fbline_(nullfbline()), pitch_(0) {}
|
||||
video_pixel_t * fb() const { return buf_; }
|
||||
video_pixel_t * fbline() const { return fbline_; }
|
||||
std::ptrdiff_t pitch() const { return pitch_; }
|
||||
void setBuf(video_pixel_t *buf, std::ptrdiff_t pitch) { buf_ = buf; pitch_ = pitch; fbline_ = nullfbline(); }
|
||||
void setFbline(unsigned ly) { fbline_ = buf_ ? buf_ + std::ptrdiff_t(ly) * pitch_ : nullfbline(); }
|
||||
|
||||
private:
|
||||
video_pixel_t *buf_;
|
||||
video_pixel_t *fbline_;
|
||||
std::ptrdiff_t pitch_;
|
||||
|
||||
/* Shared 160-pixel scratch used as a destination when no real
|
||||
* framebuf is attached. With DUAL_MODE compiled in, both PPU
|
||||
* instances would write to this same buffer; the libretro
|
||||
* core is single-threaded so there is no data race, but it is
|
||||
* shared mutable state and nothing in the current code path
|
||||
* actually reads it back. Make it a non-static member of
|
||||
* PPUFrameBuf if DUAL_MODE is ever revived as a real feature. */
|
||||
static video_pixel_t * nullfbline() { static video_pixel_t nullfbline_[160]; return nullfbline_; }
|
||||
};
|
||||
|
||||
struct PPUPriv;
|
||||
|
||||
struct PPUState {
|
||||
void (*f)(PPUPriv &v);
|
||||
unsigned (*predictCyclesUntilXpos_f)(PPUPriv const &v, int targetxpos, unsigned cycles);
|
||||
unsigned char id;
|
||||
};
|
||||
|
||||
struct PPUPriv {
|
||||
video_pixel_t bgPalette[8 * 4];
|
||||
video_pixel_t spPalette[8 * 4];
|
||||
/* Precomputed BG palette expansion.
|
||||
*
|
||||
* Indexed by [palette_id][byte][lane]: each byte of ntileword
|
||||
* packs four 2-bit palette indices (lanes 0..3 in byte order),
|
||||
* so each bgPaletteExpanded[p][byte] holds the four resolved
|
||||
* colours for that nibble of the tile word. Both the DMG and
|
||||
* CGB renderers issue two 16-byte loads + two 16-byte stores
|
||||
* per 8-pixel tile instead of eight dependent scalar loads from
|
||||
* bgPalette.
|
||||
*
|
||||
* DMG mode uses only palette 0. CGB mode picks palette
|
||||
* (nattrib & 7) per tile. The table is rebuilt only when a BG
|
||||
* palette changes: BGP / refresh / doCgbBgColorChange paths.
|
||||
*
|
||||
* Size: 8 * 256 * 4 * sizeof(video_pixel_t). For the default
|
||||
* libretro u32 build that's 32 KiB; for the VIDEO_RGB565 /
|
||||
* VIDEO_ABGR1555 u16 builds it's 16 KiB. Modern x86 L1d is
|
||||
* 32-48 KiB and Cortex-A7/A53/A72 L1d is 32 KiB, so the worst
|
||||
* case fits. In practice each frame uses only a few palettes
|
||||
* actively, so the hot working set is much smaller than the
|
||||
* full table. */
|
||||
video_pixel_t bgPaletteExpanded[8][256][4];
|
||||
struct Sprite { unsigned char spx, oampos, line, attrib; } spriteList[11];
|
||||
unsigned short spwordList[11];
|
||||
unsigned char nextSprite;
|
||||
unsigned char currentSprite;
|
||||
|
||||
unsigned char const *vram;
|
||||
PPUState const *nextCallPtr;
|
||||
|
||||
unsigned long now;
|
||||
unsigned long lastM0Time;
|
||||
long cycles;
|
||||
|
||||
unsigned tileword;
|
||||
unsigned ntileword;
|
||||
|
||||
SpriteMapper spriteMapper;
|
||||
LyCounter lyCounter;
|
||||
PPUFrameBuf framebuf;
|
||||
|
||||
unsigned char lcdc;
|
||||
unsigned char scy;
|
||||
unsigned char scx;
|
||||
unsigned char wy;
|
||||
unsigned char wy2;
|
||||
unsigned char wx;
|
||||
unsigned char winDrawState;
|
||||
unsigned char wscx;
|
||||
unsigned char winYPos;
|
||||
unsigned char reg0;
|
||||
unsigned char reg1;
|
||||
unsigned char attrib;
|
||||
unsigned char nattrib;
|
||||
unsigned char xpos;
|
||||
unsigned char endx;
|
||||
|
||||
bool cgb;
|
||||
bool dmgMode;
|
||||
bool weMaster;
|
||||
|
||||
PPUPriv(NextM0Time &nextM0Time, unsigned char const *oamram, unsigned char const *vram);
|
||||
};
|
||||
|
||||
class PPU {
|
||||
public:
|
||||
PPU(NextM0Time &nextM0Time, unsigned char const *oamram, unsigned char const *vram)
|
||||
: p_(nextM0Time, oamram, vram)
|
||||
{
|
||||
}
|
||||
|
||||
video_pixel_t * bgPalette() { return p_.bgPalette; }
|
||||
|
||||
/* Recompute bgPaletteExpanded[palette_id] from the current
|
||||
* bgPalette[palette_id*4 .. palette_id*4+3]. DMG mode uses
|
||||
* palette 0 only. Called by the LCD on every BG palette write
|
||||
* (BGP register, CGB BCPD register, refresh / savestate paths).
|
||||
* Idempotent and side-effect-free. */
|
||||
void refreshBgPaletteExpansion(unsigned const palette_id)
|
||||
{
|
||||
video_pixel_t const *const pal = p_.bgPalette + palette_id * 4;
|
||||
video_pixel_t const c0 = pal[0];
|
||||
video_pixel_t const c1 = pal[1];
|
||||
video_pixel_t const c2 = pal[2];
|
||||
video_pixel_t const c3 = pal[3];
|
||||
video_pixel_t const cs[4] = { c0, c1, c2, c3 };
|
||||
for (int b = 0; b < 256; ++b) {
|
||||
p_.bgPaletteExpanded[palette_id][b][0] = cs[ b & 3];
|
||||
p_.bgPaletteExpanded[palette_id][b][1] = cs[(b >> 2) & 3];
|
||||
p_.bgPaletteExpanded[palette_id][b][2] = cs[(b >> 4) & 3];
|
||||
p_.bgPaletteExpanded[palette_id][b][3] = cs[ b >> 6 ];
|
||||
}
|
||||
}
|
||||
bool cgb() const { return p_.cgb; }
|
||||
void setDmgMode(bool mode) { p_.dmgMode = mode; }
|
||||
bool inDmgMode() const { return p_.dmgMode; }
|
||||
void doLyCountEvent() { p_.lyCounter.doEvent(); }
|
||||
unsigned long doSpriteMapEvent(unsigned long time) { return p_.spriteMapper.doEvent(time); }
|
||||
PPUFrameBuf const & frameBuf() const { return p_.framebuf; }
|
||||
|
||||
bool inactivePeriodAfterDisplayEnable(unsigned long cc) const {
|
||||
return p_.spriteMapper.inactivePeriodAfterDisplayEnable(cc);
|
||||
}
|
||||
|
||||
unsigned long lastM0Time() const { return p_.lastM0Time; }
|
||||
unsigned lcdc() const { return p_.lcdc; }
|
||||
void loadState(SaveState const &state, unsigned char const *oamram);
|
||||
LyCounter const & lyCounter() const { return p_.lyCounter; }
|
||||
unsigned long now() const { return p_.now; }
|
||||
void oamChange(unsigned long cc) { p_.spriteMapper.oamChange(cc); }
|
||||
void oamChange(unsigned char const *oamram, unsigned long cc) { p_.spriteMapper.oamChange(oamram, cc); }
|
||||
unsigned long predictedNextXposTime(unsigned xpos) const;
|
||||
void reset(unsigned char const *oamram, unsigned char const *vram, bool cgb);
|
||||
void resetCc(unsigned long oldCc, unsigned long newCc);
|
||||
void saveState(SaveState &ss) const;
|
||||
void setFrameBuf(video_pixel_t *buf, std::ptrdiff_t pitch) { p_.framebuf.setBuf(buf, pitch); }
|
||||
void setLcdc(unsigned lcdc, unsigned long cc);
|
||||
void setScx(unsigned scx) { p_.scx = scx; }
|
||||
void setScy(unsigned scy) { p_.scy = scy; }
|
||||
void setStatePtrs(SaveState &ss) { p_.spriteMapper.setStatePtrs(ss); }
|
||||
void setWx(unsigned wx) { p_.wx = wx; }
|
||||
void setWy(unsigned wy) { p_.wy = wy; }
|
||||
void updateWy2() { p_.wy2 = p_.wy; }
|
||||
void speedChange(unsigned long cycleCounter);
|
||||
video_pixel_t * spPalette() { return p_.spPalette; }
|
||||
void update(unsigned long cc);
|
||||
|
||||
private:
|
||||
PPUPriv p_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#include "sprite_mapper.h"
|
||||
#include "counterdef.h"
|
||||
#include "next_m0_time.h"
|
||||
#include "../insertion_sort.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
class SpxLess {
|
||||
public:
|
||||
explicit SpxLess(unsigned char const *spxlut) : spxlut_(spxlut) {}
|
||||
|
||||
bool operator()(unsigned char lhs, unsigned char rhs) const {
|
||||
return spxlut_[lhs] < spxlut_[rhs];
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned char const *const spxlut_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
SpriteMapper::OamReader::OamReader(LyCounter const &lyCounter, unsigned char const *oamram)
|
||||
: lyCounter_(lyCounter)
|
||||
, oamram_(oamram)
|
||||
, cgb_(false)
|
||||
{
|
||||
reset(oamram, false);
|
||||
}
|
||||
|
||||
void SpriteMapper::OamReader::reset(unsigned char const *const oamram, bool const cgb) {
|
||||
oamram_ = oamram;
|
||||
cgb_ = cgb;
|
||||
setLargeSpritesSrc(false);
|
||||
lu_ = 0;
|
||||
lastChange_ = 0xFF;
|
||||
std::fill(szbuf_, szbuf_ + 40, largeSpritesSrc_);
|
||||
|
||||
unsigned pos = 0;
|
||||
unsigned distance = 80;
|
||||
while (distance--) {
|
||||
buf_[pos] = oamram[((pos * 2) & ~3) | (pos & 1)];
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned toPosCycles(unsigned long const cc, LyCounter const &lyCounter) {
|
||||
unsigned lc = lyCounter.lineCycles(cc) + 3 - lyCounter.isDoubleSpeed() * 3u;
|
||||
if (lc >= 456)
|
||||
lc -= 456;
|
||||
|
||||
return lc;
|
||||
}
|
||||
|
||||
void SpriteMapper::OamReader::update(unsigned long const cc) {
|
||||
if (cc > lu_) {
|
||||
if (changed()) {
|
||||
unsigned const lulc = toPosCycles(lu_, lyCounter_);
|
||||
unsigned pos = std::min(lulc, 80u);
|
||||
unsigned distance = 80;
|
||||
unsigned is_doublespeed = (unsigned)lyCounter_.isDoubleSpeed();
|
||||
|
||||
if ((cc - lu_) >> is_doublespeed < 456)
|
||||
{
|
||||
unsigned cclc = toPosCycles(cc, lyCounter_);
|
||||
distance = std::min(cclc, 80u) - pos + (cclc < lulc ? 80 : 0);
|
||||
}
|
||||
|
||||
{
|
||||
unsigned targetDistance =
|
||||
lastChange_ - pos + (lastChange_ <= pos ? 80 : 0);
|
||||
if (targetDistance <= distance) {
|
||||
distance = targetDistance;
|
||||
lastChange_ = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
while (distance--) {
|
||||
if (!(pos & 1)) {
|
||||
if (pos == 80)
|
||||
pos = 0;
|
||||
|
||||
if (cgb_)
|
||||
szbuf_[pos >> 1] = largeSpritesSrc_;
|
||||
|
||||
buf_[pos ] = oamram_[pos * 2 ];
|
||||
buf_[pos + 1] = oamram_[pos * 2 + 1];
|
||||
} else
|
||||
szbuf_[pos >> 1] = (szbuf_[pos >> 1] & cgb_) | largeSpritesSrc_;
|
||||
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
lu_ = cc;
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteMapper::OamReader::change(unsigned long cc) {
|
||||
update(cc);
|
||||
lastChange_ = std::min(toPosCycles(lu_, lyCounter_), 80u);
|
||||
}
|
||||
|
||||
void SpriteMapper::OamReader::setStatePtrs(SaveState &state) {
|
||||
state.ppu.oamReaderBuf.set(buf_, sizeof buf_);
|
||||
state.ppu.oamReaderSzbuf.set(szbuf_, sizeof szbuf_ / sizeof *szbuf_);
|
||||
}
|
||||
|
||||
void SpriteMapper::OamReader::loadState(SaveState const &ss, unsigned char const *const oamram) {
|
||||
oamram_ = oamram;
|
||||
largeSpritesSrc_ = ss.mem.ioamhram.get()[0x140] >> 2 & 1;
|
||||
lu_ = ss.ppu.enableDisplayM0Time;
|
||||
change(lu_);
|
||||
}
|
||||
|
||||
void SpriteMapper::OamReader::enableDisplay(unsigned long cc)
|
||||
{
|
||||
std::memset(buf_, 0x00, sizeof buf_);
|
||||
std::fill(szbuf_, szbuf_ + 40, false);
|
||||
unsigned is_doublespeed = (unsigned)lyCounter_.isDoubleSpeed();
|
||||
lu_ = cc + (80 << is_doublespeed);
|
||||
lastChange_ = 80;
|
||||
}
|
||||
|
||||
SpriteMapper::SpriteMapper(NextM0Time &nextM0Time,
|
||||
LyCounter const &lyCounter,
|
||||
unsigned char const *oamram)
|
||||
: nextM0Time_(nextM0Time)
|
||||
, oamReader_(lyCounter, oamram)
|
||||
{
|
||||
clearMap();
|
||||
}
|
||||
|
||||
void SpriteMapper::reset(unsigned char const *oamram, bool cgb) {
|
||||
oamReader_.reset(oamram, cgb);
|
||||
clearMap();
|
||||
}
|
||||
|
||||
void SpriteMapper::clearMap() {
|
||||
std::memset(num_, need_sorting_mask, sizeof num_);
|
||||
}
|
||||
|
||||
void SpriteMapper::mapSprites() {
|
||||
clearMap();
|
||||
|
||||
for (unsigned i = 0x00; i < 0x50; i += 2) {
|
||||
int const spriteHeight = 8 << (unsigned)largeSprites(i >> 1);
|
||||
unsigned const bottomPos = posbuf()[i] - (17u - spriteHeight);
|
||||
|
||||
if (bottomPos < 143u + spriteHeight) {
|
||||
unsigned const startly = std::max(int(bottomPos) + 1 - spriteHeight, 0);
|
||||
unsigned char *map = spritemap_ + startly * 10;
|
||||
unsigned char *n = num_ + startly;
|
||||
unsigned char *const nend = num_ + std::min(bottomPos, 143u) + 1;
|
||||
|
||||
do {
|
||||
if (*n < need_sorting_mask + 10)
|
||||
map[(*n)++ - need_sorting_mask] = i;
|
||||
|
||||
map += 10;
|
||||
} while (++n != nend);
|
||||
}
|
||||
}
|
||||
|
||||
nextM0Time_.invalidatePredictedNextM0Time();
|
||||
}
|
||||
|
||||
void SpriteMapper::sortLine(unsigned const ly) const {
|
||||
num_[ly] &= ~need_sorting_mask;
|
||||
insertionSort(spritemap_ + ly * 10, spritemap_ + ly * 10 + num_[ly],
|
||||
SpxLess(posbuf() + 1));
|
||||
}
|
||||
|
||||
unsigned long SpriteMapper::doEvent(unsigned long const time) {
|
||||
oamReader_.update(time);
|
||||
mapSprites();
|
||||
return oamReader_.changed()
|
||||
? time + oamReader_.lineTime()
|
||||
: static_cast<unsigned long>(disabled_time);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
|
||||
#ifndef SPRITE_MAPPER_H
|
||||
#define SPRITE_MAPPER_H
|
||||
|
||||
#include "ly_counter.h"
|
||||
#include "../savestate.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
class NextM0Time;
|
||||
|
||||
class SpriteMapper {
|
||||
public:
|
||||
SpriteMapper(NextM0Time &nextM0Time,
|
||||
LyCounter const &lyCounter,
|
||||
unsigned char const *oamram);
|
||||
void reset(unsigned char const *oamram, bool cgb);
|
||||
unsigned long doEvent(unsigned long time);
|
||||
bool largeSprites(unsigned spNo) const { return oamReader_.largeSprites(spNo); }
|
||||
unsigned numSprites(unsigned ly) const { return num_[ly] & ~need_sorting_mask; }
|
||||
void oamChange(unsigned long cc) { oamReader_.change(cc); }
|
||||
void oamChange(unsigned char const *oamram, unsigned long cc) { oamReader_.change(oamram, cc); }
|
||||
unsigned char const * oamram() const { return oamReader_.oam(); }
|
||||
unsigned char const * posbuf() const { return oamReader_.spritePosBuf(); }
|
||||
void preSpeedChange(unsigned long cc) { oamReader_.update(cc); }
|
||||
void postSpeedChange(unsigned long cc) { oamReader_.change(cc); }
|
||||
|
||||
void resetCycleCounter(unsigned long oldCc, unsigned long newCc) {
|
||||
oamReader_.update(oldCc);
|
||||
oamReader_.resetCycleCounter(oldCc, newCc);
|
||||
}
|
||||
|
||||
void setLargeSpritesSource(bool src) { oamReader_.setLargeSpritesSrc(src); }
|
||||
|
||||
unsigned char const * sprites(unsigned ly) const {
|
||||
if (num_[ly] & need_sorting_mask)
|
||||
sortLine(ly);
|
||||
|
||||
return spritemap_ + ly * 10;
|
||||
}
|
||||
|
||||
void setStatePtrs(SaveState &state) { oamReader_.setStatePtrs(state); }
|
||||
void enableDisplay(unsigned long cc) { oamReader_.enableDisplay(cc); }
|
||||
void saveState(SaveState &state) const { oamReader_.saveState(state); }
|
||||
|
||||
void loadState(SaveState const &state, unsigned char const *oamram) {
|
||||
oamReader_.loadState(state, oamram);
|
||||
mapSprites();
|
||||
}
|
||||
|
||||
bool inactivePeriodAfterDisplayEnable(unsigned long cc) const {
|
||||
return oamReader_.inactivePeriodAfterDisplayEnable(cc);
|
||||
}
|
||||
|
||||
static unsigned long schedule(LyCounter const &lyCounter, unsigned long cc) {
|
||||
return lyCounter.nextLineCycle(80, cc);
|
||||
}
|
||||
|
||||
private:
|
||||
class OamReader {
|
||||
public:
|
||||
OamReader(LyCounter const &lyCounter, unsigned char const *oamram);
|
||||
void reset(unsigned char const *oamram, bool cgb);
|
||||
void change(unsigned long cc);
|
||||
void change(unsigned char const *oamram, unsigned long cc) { change(cc); oamram_ = oamram; }
|
||||
bool changed() const { return lastChange_ != 0xFF; }
|
||||
bool largeSprites(unsigned spNo) const { return szbuf_[spNo]; }
|
||||
unsigned char const * oam() const { return oamram_; }
|
||||
void resetCycleCounter(unsigned long oldCc, unsigned long newCc) { lu_ -= oldCc - newCc; }
|
||||
void setLargeSpritesSrc(bool src) { largeSpritesSrc_ = src; }
|
||||
void update(unsigned long cc);
|
||||
unsigned char const * spritePosBuf() const { return buf_; }
|
||||
void setStatePtrs(SaveState &state);
|
||||
void enableDisplay(unsigned long cc);
|
||||
void saveState(SaveState &state) const { state.ppu.enableDisplayM0Time = lu_; }
|
||||
void loadState(SaveState const &ss, unsigned char const *oamram);
|
||||
bool inactivePeriodAfterDisplayEnable(unsigned long cc) const { return cc < lu_; }
|
||||
unsigned lineTime() const { return lyCounter_.lineTime(); }
|
||||
|
||||
private:
|
||||
unsigned char buf_[80];
|
||||
bool szbuf_[40];
|
||||
LyCounter const &lyCounter_;
|
||||
unsigned char const *oamram_;
|
||||
unsigned long lu_;
|
||||
unsigned char lastChange_;
|
||||
bool largeSpritesSrc_;
|
||||
bool cgb_;
|
||||
};
|
||||
|
||||
enum { need_sorting_mask = 0x80 };
|
||||
|
||||
mutable unsigned char spritemap_[144 * 10];
|
||||
mutable unsigned char num_[144];
|
||||
NextM0Time &nextM0Time_;
|
||||
OamReader oamReader_;
|
||||
|
||||
void clearMap();
|
||||
void mapSprites();
|
||||
void sortLine(unsigned ly) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,321 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2007 by Sindre Aamås *
|
||||
* aamas@stud.ntnu.no *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#include "video.h"
|
||||
#include "savestate.h"
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
/* GBC colour correction factors */
|
||||
#define GBC_CC_LUM 0.94f
|
||||
#define GBC_CC_R 0.82f
|
||||
#define GBC_CC_G 0.665f
|
||||
#define GBC_CC_B 0.73f
|
||||
#define GBC_CC_RG 0.125f
|
||||
#define GBC_CC_RB 0.195f
|
||||
#define GBC_CC_GR 0.24f
|
||||
#define GBC_CC_GB 0.075f
|
||||
#define GBC_CC_BR -0.06f
|
||||
#define GBC_CC_BG 0.21f
|
||||
|
||||
namespace gambatte
|
||||
{
|
||||
void LCD::setDmgPaletteColor(const unsigned index, const video_pixel_t rgb32)
|
||||
{
|
||||
dmgColorsRgb32_[index] = rgb32;
|
||||
}
|
||||
|
||||
void LCD::setDmgPalette(video_pixel_t *const palette, const video_pixel_t *const dmgColors, const unsigned data)
|
||||
{
|
||||
palette[0] = dmgColors[data & 3];
|
||||
palette[1] = dmgColors[data >> 2 & 3];
|
||||
palette[2] = dmgColors[data >> 4 & 3];
|
||||
palette[3] = dmgColors[data >> 6 & 3];
|
||||
}
|
||||
|
||||
void LCD::setColorCorrection(bool colorCorrection_)
|
||||
{
|
||||
colorCorrection = colorCorrection_;
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
void LCD::setColorCorrectionMode(unsigned colorCorrectionMode_)
|
||||
{
|
||||
// Note: We only have two modes, so could make 'colorCorrectionMode'
|
||||
// a bool... but may want to add other modes in the future
|
||||
// (e.g. a special GBA colour correction mode for when the GBA
|
||||
// flag is set in Shantae/Zelda: Oracle/etc.)
|
||||
colorCorrectionMode = colorCorrectionMode_;
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
void LCD::setColorCorrectionBrightness(float colorCorrectionBrightness_)
|
||||
{
|
||||
colorCorrectionBrightness = colorCorrectionBrightness_;
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
void LCD::setDarkFilterLevel(unsigned darkFilterLevel_)
|
||||
{
|
||||
darkFilterLevel = darkFilterLevel_;
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
LCD::LCD(const unsigned char *const oamram, const unsigned char *const vram, const VideoInterruptRequester memEventRequester) :
|
||||
ppu_(nextM0Time_, oamram, vram),
|
||||
eventTimes_(memEventRequester),
|
||||
statReg_(0),
|
||||
m2IrqStatReg_(0),
|
||||
m1IrqStatReg_(0),
|
||||
/* Color-correction defaults must be initialized BEFORE the
|
||||
* constructor body runs, because setColorCorrection(true)
|
||||
* below calls refreshPalettes() -> gbcToRgb32(), which
|
||||
* reads colorCorrectionMode/colorCorrectionBrightness/
|
||||
* darkFilterLevel. Previously these members were left
|
||||
* uninitialized at this point and gbcToRgb32 invoked UB
|
||||
* during construction. The values get overwritten by
|
||||
* check_variables() once core options are read, but the
|
||||
* construction-time UB still fires under UBSan. */
|
||||
colorCorrection(true),
|
||||
colorCorrectionMode(0),
|
||||
colorCorrectionBrightness(0.5f),
|
||||
darkFilterLevel(0)
|
||||
{
|
||||
std::memset( bgpData_, 0, sizeof bgpData_);
|
||||
std::memset(objpData_, 0, sizeof objpData_);
|
||||
/* dmgColorsGBC_ is consumed by refreshPalettes() in the
|
||||
* CGB-running-DMG-game path; if uninitialized it would
|
||||
* produce garbage colours until the game wrote a palette. */
|
||||
std::memset(dmgColorsGBC_, 0, sizeof dmgColorsGBC_);
|
||||
|
||||
for (std::size_t i = 0; i < sizeof(dmgColorsRgb32_) / sizeof(dmgColorsRgb32_[0]); ++i)
|
||||
{
|
||||
#ifdef VIDEO_RGB565
|
||||
uint16_t dmgColors[4]={0xFFFF, //11111 111111 11111
|
||||
0xAD55, //10101 101010 10101
|
||||
0x52AA, //01010 010101 01010
|
||||
0x0000};//00000 000000 00000
|
||||
setDmgPaletteColor(i, dmgColors[i&3]);
|
||||
#elif defined(VIDEO_ABGR1555)
|
||||
uint16_t dmgColors[4]={0xEFFF, //11111 111111 11111
|
||||
0x56B5, //10101 10101 10101
|
||||
0x294A, //01010 01010 01010
|
||||
0x0000};//00000 000000 00000
|
||||
setDmgPaletteColor(i, dmgColors[i&3]);
|
||||
#else
|
||||
setDmgPaletteColor(i, (3 - (i & 3)) * 85 * 0x010101);
|
||||
#endif
|
||||
}
|
||||
|
||||
reset(oamram, vram, false);
|
||||
/* PPUFrameBuf is default-constructed with NULL buffer and
|
||||
* pitch 0; the frontend will call setVideoBuffer with the
|
||||
* real buffer via cpu.setVideoBuffer once retro_run begins,
|
||||
* so the previous setVideoBuffer(0, 160) here was a no-op
|
||||
* with a confusing pitch. */
|
||||
|
||||
setColorCorrection(true);
|
||||
}
|
||||
|
||||
void LCD::doCgbColorChange(unsigned char *const pdata,
|
||||
video_pixel_t *const palette, unsigned index, const unsigned data)
|
||||
{
|
||||
pdata[index] = data;
|
||||
index >>= 1;
|
||||
palette[index] = gbcToRgb32(pdata[index << 1] | pdata[(index << 1) + 1] << 8);
|
||||
}
|
||||
|
||||
void LCD::setVideoBuffer(video_pixel_t *const videoBuf, const int pitch)
|
||||
{
|
||||
ppu_.setFrameBuf(videoBuf, pitch);
|
||||
}
|
||||
|
||||
static void clear(video_pixel_t *buf, const unsigned long color, const int dpitch)
|
||||
{
|
||||
unsigned lines = 144;
|
||||
|
||||
while (lines--)
|
||||
{
|
||||
std::fill_n(buf, 160, color);
|
||||
buf += dpitch;
|
||||
}
|
||||
}
|
||||
|
||||
void LCD::setDmgPaletteColor(const unsigned palNum, const unsigned colorNum, const video_pixel_t rgb32)
|
||||
{
|
||||
if (palNum > 2 || colorNum > 3)
|
||||
return;
|
||||
|
||||
setDmgPaletteColor(palNum * 4 | colorNum, rgb32);
|
||||
refreshPalettes();
|
||||
}
|
||||
|
||||
void LCD::updateScreen(const bool blanklcd, const unsigned long cycleCounter)
|
||||
{
|
||||
update(cycleCounter);
|
||||
|
||||
if (blanklcd && ppu_.frameBuf().fb())
|
||||
{
|
||||
const video_pixel_t color = ppu_.cgb() ? gbcToRgb32(0xFFFF) : dmgColorsRgb32_[0];
|
||||
clear(ppu_.frameBuf().fb(), color, ppu_.frameBuf().pitch());
|
||||
}
|
||||
}
|
||||
|
||||
// RGB range: [0,1]
|
||||
void LCD::darkenRgb(float &r, float &g, float &b)
|
||||
{
|
||||
// Note: This is *very* approximate...
|
||||
// - Should be done in linear colour space. It isn't.
|
||||
// - Should alter brightness by performing an RGB->HSL->RGB
|
||||
// conversion. We just do simple linear scaling instead.
|
||||
// Basically, this is intended for use on devices that are
|
||||
// too weak to run shaders (i.e. why would you want a 'dark filter'
|
||||
// if your device supports proper LCD shaders?). We therefore
|
||||
// cut corners for the sake of performance...
|
||||
//
|
||||
// Constants
|
||||
// > Luminosity factors: photometric/digital ITU BT.709
|
||||
static const float lumaR = 0.2126f;
|
||||
static const float lumaG = 0.7152f;
|
||||
static const float lumaB = 0.0722f;
|
||||
// Calculate luminosity
|
||||
float luma = (lumaR * r) + (lumaG * g) + (lumaB * b);
|
||||
// Get 'darkness' scaling factor
|
||||
// > User set 'dark filter' level scaled by current luminosity
|
||||
// (i.e. lighter colours affected more than darker colours)
|
||||
float darkFactor = 1.0f - ((static_cast<float>(darkFilterLevel) * 0.01f) * luma);
|
||||
darkFactor = darkFactor < 0.0f ? 0.0f : darkFactor;
|
||||
// Perform scaling...
|
||||
r = r * darkFactor;
|
||||
g = g * darkFactor;
|
||||
b = b * darkFactor;
|
||||
}
|
||||
|
||||
video_pixel_t LCD::gbcToRgb32(const unsigned bgr15)
|
||||
{
|
||||
const unsigned r = bgr15 & 0x1F;
|
||||
const unsigned g = bgr15 >> 5 & 0x1F;
|
||||
const unsigned b = bgr15 >> 10 & 0x1F;
|
||||
|
||||
unsigned rFinal = 0;
|
||||
unsigned gFinal = 0;
|
||||
unsigned bFinal = 0;
|
||||
|
||||
// Constants
|
||||
// (Don't know whether floating-point rounding modes can be changed
|
||||
// dynamically at run time, so can't rely on constant folding of
|
||||
// inline [float / float] expressions - just play it safe...)
|
||||
static const float rgbMax = 31.0;
|
||||
static const float rgbMaxInv = 1.0 / rgbMax;
|
||||
|
||||
bool isDark = false;
|
||||
|
||||
if (colorCorrection)
|
||||
{
|
||||
if (colorCorrectionMode == 1)
|
||||
{
|
||||
// Use fast (inaccurate) Gambatte default method
|
||||
rFinal = ((r * 13) + (g * 2) + b) >> 4;
|
||||
gFinal = ((g * 3) + b) >> 2;
|
||||
bFinal = ((r * 3) + (g * 2) + (b * 11)) >> 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use Pokefan531's "gold standard" GBC colour correction
|
||||
// (https://forums.libretro.com/t/real-gba-and-ds-phat-colors/1540/190)
|
||||
// NB: The results produced by this implementation are ever so slightly
|
||||
// different from the output of the gbc-colour shader. This is due to the
|
||||
// fact that we have to tolerate rounding errors here that are simply not
|
||||
// an issue when tweaking the final image with a post-processing shader.
|
||||
// *However:* the difference is so tiny small that 99.9% of users will
|
||||
// never notice, and the result is still 100x better than the 'fast'
|
||||
// colour correction method.
|
||||
//
|
||||
// Constants
|
||||
static const float targetGamma = 2.2;
|
||||
static const float displayGammaInv = 1.0 / targetGamma;
|
||||
// Perform gamma expansion
|
||||
float adjustedGamma = targetGamma - colorCorrectionBrightness;
|
||||
float rFloat = std::pow(static_cast<float>(r) * rgbMaxInv, adjustedGamma);
|
||||
float gFloat = std::pow(static_cast<float>(g) * rgbMaxInv, adjustedGamma);
|
||||
float bFloat = std::pow(static_cast<float>(b) * rgbMaxInv, adjustedGamma);
|
||||
// Perform colour mangling
|
||||
float rCorrect = GBC_CC_LUM * ((GBC_CC_R * rFloat) + (GBC_CC_GR * gFloat) + (GBC_CC_BR * bFloat));
|
||||
float gCorrect = GBC_CC_LUM * ((GBC_CC_RG * rFloat) + (GBC_CC_G * gFloat) + (GBC_CC_BG * bFloat));
|
||||
float bCorrect = GBC_CC_LUM * ((GBC_CC_RB * rFloat) + (GBC_CC_GB * gFloat) + (GBC_CC_B * bFloat));
|
||||
// Range check...
|
||||
rCorrect = rCorrect > 0.0f ? rCorrect : 0.0f;
|
||||
gCorrect = gCorrect > 0.0f ? gCorrect : 0.0f;
|
||||
bCorrect = bCorrect > 0.0f ? bCorrect : 0.0f;
|
||||
// Perform gamma compression
|
||||
rCorrect = std::pow(rCorrect, displayGammaInv);
|
||||
gCorrect = std::pow(gCorrect, displayGammaInv);
|
||||
bCorrect = std::pow(bCorrect, displayGammaInv);
|
||||
// Range check...
|
||||
rCorrect = rCorrect > 1.0f ? 1.0f : rCorrect;
|
||||
gCorrect = gCorrect > 1.0f ? 1.0f : gCorrect;
|
||||
bCorrect = bCorrect > 1.0f ? 1.0f : bCorrect;
|
||||
// Perform image darkening, if required
|
||||
if (darkFilterLevel > 0)
|
||||
{
|
||||
darkenRgb(rCorrect, gCorrect, bCorrect);
|
||||
isDark = true;
|
||||
}
|
||||
// Convert back to 5bit unsigned
|
||||
rFinal = static_cast<unsigned>((rCorrect * rgbMax) + 0.5) & 0x1F;
|
||||
gFinal = static_cast<unsigned>((gCorrect * rgbMax) + 0.5) & 0x1F;
|
||||
bFinal = static_cast<unsigned>((bCorrect * rgbMax) + 0.5) & 0x1F;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rFinal = r;
|
||||
gFinal = g;
|
||||
bFinal = b;
|
||||
}
|
||||
|
||||
// Perform image darkening, if required and we haven't
|
||||
// already done it during colour correction
|
||||
if (darkFilterLevel > 0 && !isDark)
|
||||
{
|
||||
// Convert colour range from [0,0x1F] to [0,1]
|
||||
float rDark = static_cast<float>(rFinal) * rgbMaxInv;
|
||||
float gDark = static_cast<float>(gFinal) * rgbMaxInv;
|
||||
float bDark = static_cast<float>(bFinal) * rgbMaxInv;
|
||||
// Perform image darkening
|
||||
darkenRgb(rDark, gDark, bDark);
|
||||
// Convert back to 5bit unsigned
|
||||
rFinal = static_cast<unsigned>((rDark * rgbMax) + 0.5) & 0x1F;
|
||||
gFinal = static_cast<unsigned>((gDark * rgbMax) + 0.5) & 0x1F;
|
||||
bFinal = static_cast<unsigned>((bDark * rgbMax) + 0.5) & 0x1F;
|
||||
}
|
||||
|
||||
#ifdef VIDEO_RGB565
|
||||
return rFinal << 11 | gFinal << 6 | bFinal;
|
||||
#elif defined(VIDEO_ABGR1555)
|
||||
return bFinal << 10 | gFinal << 5 | rFinal;
|
||||
#else
|
||||
const unsigned r8 = (rFinal * 255 + 15) / 31;
|
||||
const unsigned g8 = (gFinal * 255 + 15) / 31;
|
||||
const unsigned b8 = (bFinal * 255 + 15) / 31;
|
||||
return (r8 << 16) | (g8 << 8) | b8;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2357,6 +2357,10 @@ open_builtin_menu(const char *menu_name, int keep_idx)
|
||||
snprintf(label, sizeof(label), "%s Tetris", ICON_GAM);
|
||||
add_menu_item(label, "tetris", 0);
|
||||
}
|
||||
if (access("/usr/bin/gambatte", X_OK) == 0) {
|
||||
snprintf(label, sizeof(label), "%s Game Boy (Gambatte)", ICON_GAM);
|
||||
add_menu_item(label, "gambatte", 0);
|
||||
}
|
||||
const char *rdir = getenv("XDG_RUNTIME_DIR");
|
||||
char gcache[256];
|
||||
snprintf(gcache, sizeof(gcache), "%s/sxmo_games.cache", (rdir && rdir[0]) ? rdir : "/run/user/10000");
|
||||
|
||||
Reference in New Issue
Block a user