sf2000/tools/bootLogoChanger.html
2023-05-09 19:05:19 +01:00

270 lines
12 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Data Frog SF2000 Boot Logo Changer</title>
<meta name="viewport" content="width=device-width">
<style type="text/css">
body {
background-color: rgb(240, 235, 220);
color: rgb(50, 40, 20);
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
}
a, a:visited, a:hover, a:active {
color: rgb(50, 40, 20);
}
hr {
border: 1px solid rgb(50, 40, 20);
margin: 2em 0;
}
p.errorMessage, p.infoMessage {
border-radius: 10px;
padding: 10px;
margin: 20px;
}
p.errorMessage {
background-color: rgb(200, 65, 65);
border: 1px dashed rgb(255, 255, 255);
color: rgb(255, 255, 255);
}
p.infoMessage {
background-color: rgb(65, 160, 65);
border: 1px dashed rgb(255, 255, 255);
color: rgb(255, 255, 255);
}
@media (prefers-color-scheme: dark) {
body {
background-color: rgb(70, 75, 100);
color: rgb(190, 190, 200);
}
a, a:visited, a:hover, a:active {
color: rgb(190, 190, 200);
}
hr {
border: 1px solid rgb(190, 190, 200);
}
p.errorMessage {
background-color: rgb(130, 85, 75);
border-color: rgb(245, 200, 200);
color: rgb(245, 200, 200);
}
p.infoMessage {
background-color: rgb(75, 130, 85);
border: 1px dashed rgb(200, 245, 200);
color: rgb(200, 245, 200);
}
}
h1:first-child {
text-align: center;
}
p:last-child {
text-align: center;
}
</style>
</head>
<body>
<h1>Data Frog SF2000 Boot Logo Changer</h1>
<p>This tool can be used to alter the boot logo on an SF2000 handheld gaming console. Please note this tool is provided as-is, and no support will be given if this corrupts your device's bios; make sure you have backups of anything you care about before messing with your device's critical files! 🙂</p>
<hr>
<section id="bisrvSection">
<h2>Step 1: Select Original <code>bisrv.asd</code></h2>
<p>Select the <code>bisrv.asd</code> file whose boot logo you want to modify. You should probably make a backup of the file first, just in case! You can find the <code>bisrv.asd</code> file in the <code>bios</code> folder on your microSD card.</p>
<form id="bisrvForm" action="#">
<label>Open <code>bisrv.asd</code>: <input id="bisrvSelector" type="file" onchange="bisrvLoad(event.target.files[0])"></label>
</form>
<div id="bisrvOutput"></div>
</section>
<hr>
<section id="imageSection">
<h2>Step 2: Select New Logo Image File</h2>
<p>Select the image file you want to use as the new boot logo. It must be 256 pixels wide and 100 pixels tall. Only PNG and JPEG image types are supported. For PNG files, transparency is ignored; also, you might want to make sure your PNG image has any embedded gamma or colour profile information removed first using a metadata scrubbing tool, otherwise the colour output from <i>this</i> tool might be incorrect. The image is displayed centred on a black background, so you may want to factor that into your design as well.</p>
<form id="imageForm" action="#">
<label>Open image: <input id="imageSelector" type="file" onchange="imageLoad(event.target.files[0])" disabled></label>
</form>
<div id="imageOutput"></div>
</section>
<hr>
<section id="downloadSection">
<h2>Step 3: Download Updated <code>bisrv.asd</code></h2>
<p>Click the download button for the updated <code>bisrv.asd</code> file; use it to replace the one in the <code>bios</code> folder on your SF2000's microSD card.</p>
<form id="downloadForm" action="#">
<input id="downloadButton" type="button" value="Download" onclick="download()" disabled>
</form>
</section>
<script type="text/javascript">
// Global variables...
var bisrvData; // Used to store the binary data from the bisrv.asd file
var logoOffset; // Will contain the offset of the boot logo within the bisrv.asd file
var newLogoData; // Used to store the little-endian RGB565 binary data of the new boot logo
function bisrvLoad(file) {
var frBisrv = new FileReader();
frBisrv.onload = function(event) {
// Read the provided file's data into an array...
var data = new Uint8Array(event.target.result);
// Let's check the data to see if it looks like one of the known bisrv.asd versions...
if (data.length == 12647452) {
// That's the correct length for the original March 28th version of the file
logoOffset = 0x9B9030;
bisrvData = data;
document.getElementById("bisrvOutput").innerHTML = "<p class=\"infoMessage\">INFO: March 28th bisrv.asd detected</p>";
}
else if (data.length == 12648068) {
// That's the correct length for the April 20th version of the file
logoOffset = 0x9B91D8;
bisrvData = data;
document.getElementById("bisrvOutput").innerHTML = "<p class=\"infoMessage\">INFO: April 20th bisrv.asd detected</p>";
}
else {
document.getElementById("bisrvOutput").innerHTML = "<p class=\"errorMessage\">ERROR: The selected file does not appear to be a known bisrv.asd file!</p>";
document.getElementById("imageSelector").setAttribute("disabled", "");
document.getElementById("downloadButton").setAttribute("disabled", "");
bisrvData = undefined;
logoOffset = undefined;
return;
}
// If we're here we've got a good file, so enable the input for step 2 (image selection)...
document.getElementById("imageSelector").removeAttribute("disabled");
};
frBisrv.readAsArrayBuffer(file);
}
function imageLoad(file) {
var frImage = new FileReader();
frImage.onload = function(event) {
// First check to make sure the selected file's data URL includes a PNG or JPEG data type...
if (!event.target.result.includes("data:image/png;") && !event.target.result.includes("data:image/jpeg;")) {
document.getElementById("imageOutput").innerHTML = "<p class=\"errorMessage\">ERROR: The selected file does not appear to be a PNG or JPEG image file!</p>";
document.getElementById("downloadButton").setAttribute("disabled", "");
return;
}
// Create an image and set its src to our data URL; this triggers the onload event if
// it's a valid image file...
var img = new Image;
img.src = event.target.result;
img.onload = function() {
// Check to make sure the image has the right dimensions for the boot logo...
if (img.width != 256 || img.height != 100) {
document.getElementById("imageOutput").innerHTML = "<p class=\"errorMessage\">ERROR: The selected image does not have dimensions of 256x100px!</p>";
document.getElementById("downloadButton").setAttribute("disabled", "");
return;
}
// Create a virtual canvas, and load it up with our image file...
var canv = document.createElement("canvas");
var cont = canv.getContext("2d");
cont.canvas.width = 512;
cont.canvas.height = 200;
// Draw our image to the canvas, which will allow us to get data about the image...
cont.drawImage(img, 0, 0, 256, 100);
var data = cont.getImageData(0, 0, 256, 100).data;
// Now we're going to scale up the image to 512x200 (the internal image size used
// within the bios); we do this manually to get "nearest neighbour" scaling...
for (var x = 0; x < img.width; ++x) {
for (var y = 0; y < img.height; ++y) {
var i = (y*img.width + x)*4;
var r = data[i];
var g = data[i+1];
var b = data[i+2];
cont.fillStyle = "rgb("+ r +", " + g + ", " + b + ")";
cont.fillRect(x*2, y*2, 2, 2);
}
}
data = cont.getImageData(0, 0, 512, 200).data;
// Loop through the image data, and convert it to little-endian RGB565. First,
// we'll store the raw RGB565-converted integers in an array, one entry per pixel...
var intArray = [];
var pixelCount = 0;
for (var i = 0; i < data.length; i += 4){
// Read in the raw source RGB colours from the image data stream...
var red = data[i];
var green = data[i+1];
var blue = data[i+2];
// Use some shifting and masking to get a big-endian version of the RGB565 colour
// and store it in our array before moving on...
intArray[pixelCount] = ((red & 248)<<8) + ((green & 252)<<3) + (blue>>3);
pixelCount++;
}
// Create a data buffer and a data view; we'll use the view to convert our int
// array data to little-endian format (the "true" below) to be stored in the buffer...
var buffer = new ArrayBuffer(intArray.length * 2);
var dataView = new DataView(buffer);
for (var i = 0; i < intArray.length; i++) {
dataView.setInt16(i * 2, intArray[i], true);
}
// Use the buffer to fill a Uint8Array, which we'll assign to our global...
newLogoData = new Uint8Array(buffer);
// We should be all done with this step; enable the download button and give the
// user some UI feedback...
document.getElementById("imageOutput").innerHTML = "<p class=\"infoMessage\">INFO: Image successfully converted to RGB565 little-endian data stream.</p>";
document.getElementById("downloadButton").removeAttribute("disabled");
}
};
frImage.readAsDataURL(file);
}
function download() {
// So, we should have the original bisrv.asd data in bisrvData; and we should have
// the offset to the original logo data in logoOffset; and finally, we should have
// our new logo's binary data in newLogoData. All we need to do is replace the old
// data bytes with the new bytes, re-calculate the CRC32 bytes for the modified
// file and set them in the data, and send the data to the user's browser. Easy!
// First, replace the logo data...
for (var i = 0; i < newLogoData.length; i++) {
bisrvData[logoOffset + i] = newLogoData[i];
}
// Next, we calculate a new CRC32 for the updated bisrv.asd and apply it; credit
// to osaka#9664 for this code!
var c;
var tabCRC32 = new Int32Array(256);
for (var i = 0; i < 256; i++) {
c = i << 24;
for (var j = 0; j < 8; j++) {
c = c & (1 << 31) ? c << 1 ^ 0x4c11db7 : c << 1;
}
tabCRC32[i] = c;
}
c = ~0;
for (var i = 512; i < bisrvData.length; i++) {
c = c << 8 ^ tabCRC32[c >>> 24 ^ bisrvData[i]];
}
bisrvData[0x18c] = c & 255;
bisrvData[0x18d] = c >>> 8 & 255;
bisrvData[0x18e] = c >>> 16 & 255;
bisrvData[0x18f] = c >>> 24;
// And finally, send the data to the user's browser as a file download...
var link = document.createElement("a");
link.href = window.URL.createObjectURL(new Blob([bisrvData], {type: "application/octet-stream"}));
link.download = "bisrv.asd";
link.style.display = "none";
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(link.href);
document.body.removeChild(link);
}
</script>
<hr>
<p><a rel="license" href="http://creativecommons.org/publicdomain/zero/1.0/">CC0</a>: public domain. Version 1.0, 20230509.1</p>
</body>
</html>