Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Created April 19, 2021 21:36
Show Gist options
  • Save MCJack123/4eb02b57de9fcb9c6e62b9b459ada8e6 to your computer and use it in GitHub Desktop.
Save MCJack123/4eb02b57de9fcb9c6e62b9b459ada8e6 to your computer and use it in GitHub Desktop.
Render CraftOS-PC raw terminal packets to PNG (requires png++)
// Compile together with https://github.com/MCJack123/craftos2/blob/master/src/font.c
#include <iostream>
#include <sstream>
#include <algorithm>
#include <array>
#include <numeric>
#include <png++/png.hpp>
extern "C" {
struct font_image {
unsigned int width;
unsigned int height;
unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */
unsigned char pixel_data[128 * 175 * 2 + 1];
};
extern struct font_image font_image;
}
struct Rect {
int x;
int y;
int w;
int h;
};
static constexpr unsigned fontWidth = 6;
static constexpr unsigned fontHeight = 9;
static constexpr unsigned fontScale = 2;
static constexpr unsigned charScale = 2;
static constexpr unsigned dpiScale = 1;
static constexpr unsigned charWidth = fontWidth * 2/fontScale * charScale;
static constexpr unsigned charHeight = fontHeight * 2/fontScale * charScale;
static unsigned short width;
static unsigned short height;
static png::image<png::rgb_pixel> font_img(font_image.width, font_image.height);
static void blit(const png::image<png::rgb_pixel>& src, Rect srcrect, png::image<png::rgb_pixel>& dest, Rect destrect, png::rgb_pixel colormod) {
const int xratio = destrect.w / srcrect.w;
const int yratio = destrect.h / srcrect.h;
for (int y = 0; y < destrect.h; y += yratio)
for (int i = 0; i < yratio; i++)
for (int x = 0; x < destrect.w; x += xratio)
for (int j = 0; j < xratio; j++)
if (src.get_pixel(srcrect.x + (x / xratio), srcrect.y + (y / yratio)).red > 64)
dest.set_pixel(destrect.x + x + j, destrect.y + y + i, colormod);
}
static void fill(png::image<png::rgb_pixel>& img, Rect rect, png::rgb_pixel color) {
for (int y = rect.y; y < rect.y + rect.h; y++)
for (int x = rect.x; x < rect.x + rect.w; x++)
img.set_pixel(x, y, color);
}
static void drawChar(png::image<png::rgb_pixel>& img, unsigned char c, int x, int y, png::rgb_pixel fg, png::rgb_pixel bg, bool transparent) {
Rect srcrect = {
(int)(((fontWidth + 2))*(c & 0x0F)+1),
(int)(((fontHeight + 2))*(c >> 4)+1),
(int)(fontWidth),
(int)(fontHeight)
};
Rect destrect = {
(int)(x * charWidth * dpiScale + 2 * charScale * dpiScale),
(int)(y * charHeight * dpiScale + 2 * charScale * dpiScale),
(int)(fontWidth * charScale * dpiScale),
(int)(fontHeight * charScale * dpiScale)
};
if (!transparent) fill(img, destrect, bg);
if (c != ' ' && c != '\0') blit(font_img, srcrect, img, destrect, fg);
}
inline png::rgb_pixel fromrgb16(unsigned short val) {
return png::rgb_pixel((val >> 8) & 0xF8, (val >> 7) & 0xFC, (val & 0x1F) << 3);
}
static std::string base64_decode(const std::string &in) {
std::string out;
std::vector<int> T(256,-1);
for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
int val=0, valb=-8;
for (unsigned char c : in) {
if (T[c] == -1) break;
val = (val << 6) + T[c];
valb += 6;
if (valb >= 0) {
out.push_back(char((val>>valb)&0xFF));
valb -= 8;
}
}
return out;
}
static std::array<unsigned int, 256> generate_crc_lookup_table() noexcept {
auto const reversed_polynomial = unsigned int{0xEDB88320uL};
struct byte_checksum {
unsigned n = 0;
unsigned int operator()() noexcept {
auto checksum = static_cast<unsigned int>(n++);
for (auto i = 0; i < 8; ++i)
checksum = (checksum >> 1) ^ ((checksum & 0x1u) ? reversed_polynomial : 0);
return checksum;
}
};
auto table = std::array<unsigned int, 256>{};
std::generate(table.begin(), table.end(), byte_checksum{});
return table;
}
template <typename InputIterator>
static unsigned int crc(InputIterator first, InputIterator last) {
static auto const table = generate_crc_lookup_table();
return unsigned int{0xFFFFFFFFuL} & ~std::accumulate(first, last,
~unsigned int{0} & unsigned int{0xFFFFFFFFuL},
[](unsigned int checksum, unsigned char value)
{ return table[(checksum ^ value) & 0xFFu] ^ (checksum >> 8); });
}
int main(int argc, const char * argv[]) {
for (int y = 0; y < font_image.height; y++)
for (int x = 0; x < font_image.width; x++)
font_img.set_pixel(x, y, fromrgb16(((unsigned short*)font_image.pixel_data)[y*font_image.width+x]));
std::string data;
std::getline(std::cin, data);
if (data.substr(0, 4) != "!CPC") {
std::cerr << "Invalid format packet\n";
return 2;
}
int packet_size = std::stoi(data.substr(4, 4), nullptr, 16);
std::string payload = base64_decode(data.substr(8, packet_size));
unsigned int this_checksum = crc(data.begin() + 8, data.begin() + 8 + packet_size);
unsigned int expected_checksum = std::stoul(data.substr(packet_size + 8, 8), nullptr, 16);
if (this_checksum != expected_checksum) {
std::cerr << "Invalid checksum (expected " << expected_checksum << ", got " << this_checksum << ")\n";
return 2;
}
std::stringstream ss(payload);
unsigned char type = ss.get();
ss.get();
if (type != 0) {
std::cerr << "Not a display packet\n";
return 2;
}
int gfxmode = ss.get();
if (gfxmode > 2) {
std::cerr << "Unknown graphics mode\n";
return 2;
}
bool cursor = ss.get();
unsigned short cursorX, cursorY;
ss.read((char*)&width, 2);
ss.read((char*)&height, 2);
ss.read((char*)&cursorX, 2);
ss.read((char*)&cursorY, 2);
ss.get(); // ignoring grayscale
ss.get(); ss.get(); ss.get();
png::rgb_pixel palette[256];
png::image<png::rgb_pixel> img(width * 12 + 8, height * 18 + 8);
unsigned char * pixels = new unsigned char[width*height*54];
unsigned char * screen = new unsigned char[width*height];
unsigned char * colors = new unsigned char[width*height];
if (gfxmode) {
unsigned char c, n;
int total = 0;
while (total < width * height) {
c = ss.get();
n = ss.get();
memset(screen + total, c, n);
total += n;
}
} else {
unsigned char c, n;
int total = 0;
while (total < width * height) {
c = ss.get();
n = ss.get();
memset(screen + total, c, n);
total += n;
}
total = 0;
while (total < width * height) {
c = ss.get();
n = ss.get();
memset(colors + total, c, n);
total += n;
}
}
for (int i = 0; i < (gfxmode == 2 ? 256 : 16); i++) {
palette[i].red = ss.get();
palette[i].green = ss.get();
palette[i].blue = ss.get();
}
Rect br = {0, 0, width * 12 + 8, height * 18 + 8};
fill(img, br, palette[15]);
if (gfxmode) {
for (int y = 0; y < height * 9; y++) {
for (int x = 0; x < width * 6; x++) {
Rect r = {x * 2 + 4, y * 2 + 4, 2, 2};
fill(img, r, palette[pixels[y*width*6+x]]);
}
}
} else {
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
drawChar(img, screen[y*width+x], x, y, palette[colors[y*width+x] & 15], palette[colors[y*width+x] >> 4], false);
if (cursor) drawChar(img, '_', cursorX, cursorY, palette[15], palette[0], true);
}
delete[] pixels;
delete[] colors;
delete[] screen;
if (argc > 1) img.write(argv[1]);
else img.write_stream(std::cout);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment