Task 763: .VB File Format
Task 763: .VB File Format
File Format Specifications for the .VB File Format
The .VB file format is associated with Nintendo Virtual Boy ROM images. It consists of a binary dump of the cartridge ROM data, with file sizes typically being powers of 2 (ranging from 256 KB to 2 MB for commercial titles). The format includes a fixed header located at the end of the file, starting at offset (file size - 32 bytes). This header contains metadata about the game.
List of all the properties of this file format intrinsic to its file system:
- Game Title: 20 bytes, encoded in Shift-JIS, representing the name of the game.
- Reserved: 5 bytes, typically filled with zeros for padding.
- Maker Code: 2 bytes, ASCII-encoded identifier for the game's developer.
- Game Code: 4 bytes, ASCII-encoded unique identifier for the game.
- Version: 1 byte, unsigned integer indicating the minor software version (major version is implicitly 1).
Two direct download links for files of format .VB:
- http://www.planetvb.com/content/downloads/roms/NikoChan Battle (J) (Prototype).zip (This archive contains the Faceball prototype .VB ROM file.)
- http://www.planetvb.com/content/downloads/roms/Faceball.Remastered.v1.0.zip (This archive contains the Faceball Remastered homebrew .VB ROM file.)
Ghost blog embedded HTML JavaScript that allows a user to drag and drop a file of format .VB and dump to screen all these properties:
Drag and Drop .VB File
Python class that can open any file of format .VB and decode, read, write, and print to console all the properties from the above list:
import sys
class VBFileHandler:
def __init__(self, filename):
self.filename = filename
self.data = None
self.header_offset = None
self.properties = {}
def read(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
self.header_offset = len(self.data) - 32
if self.header_offset < 0:
raise ValueError("File too small to contain header.")
# Decode properties
title_bytes = self.data[self.header_offset:self.header_offset + 20]
reserved_bytes = self.data[self.header_offset + 20:self.header_offset + 25]
maker_code_bytes = self.data[self.header_offset + 25:self.header_offset + 27]
game_code_bytes = self.data[self.header_offset + 27:self.header_offset + 31]
version = self.data[self.header_offset + 31]
self.properties = {
'Game Title': title_bytes.decode('shift-jis', errors='ignore').rstrip('\x00').strip(),
'Reserved': ' '.join(f'{b:02x}' for b in reserved_bytes),
'Maker Code': maker_code_bytes.decode('ascii', errors='ignore').strip(),
'Game Code': game_code_bytes.decode('ascii', errors='ignore').strip(),
'Version': version
}
def print_properties(self):
if not self.properties:
raise ValueError("Properties not read yet.")
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, new_properties=None):
if self.data is None:
raise ValueError("File not read yet.")
if new_properties:
self._update_properties(new_properties)
with open(self.filename, 'wb') as f:
f.write(self.data)
def _update_properties(self, new_properties):
mutable_data = bytearray(self.data)
if 'Game Title' in new_properties:
title = new_properties['Game Title'].encode('shift-jis')[:20].ljust(20, b'\x00')
mutable_data[self.header_offset:self.header_offset + 20] = title
if 'Reserved' in new_properties:
reserved = bytes.fromhex(new_properties['Reserved'])[:5].ljust(5, b'\x00')
mutable_data[self.header_offset + 20:self.header_offset + 25] = reserved
if 'Maker Code' in new_properties:
maker = new_properties['Maker Code'].encode('ascii')[:2].ljust(2, b'\x00')
mutable_data[self.header_offset + 25:self.header_offset + 27] = maker
if 'Game Code' in new_properties:
game_code = new_properties['Game Code'].encode('ascii')[:4].ljust(4, b'\x00')
mutable_data[self.header_offset + 27:self.header_offset + 31] = game_code
if 'Version' in new_properties:
version = new_properties['Version'] & 0xFF
mutable_data[self.header_offset + 31] = version
self.data = bytes(mutable_data)
# Example usage:
# handler = VBFileHandler('example.vb')
# handler.read()
# handler.print_properties()
# handler.write({'Version': 2})
Java class that can open any file of format .VB and decode, read, write, and print to console all the properties from the above list:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class VBFileHandler {
private String filename;
private byte[] data;
private int headerOffset;
private Map<String, Object> properties = new HashMap<>();
public VBFileHandler(String filename) {
this.filename = filename;
}
public void read() throws IOException {
data = Files.readAllBytes(Paths.get(filename));
headerOffset = data.length - 32;
if (headerOffset < 0) {
throw new IOException("File too small to contain header.");
}
// Decode properties
byte[] titleBytes = new byte[20];
System.arraycopy(data, headerOffset, titleBytes, 0, 20);
byte[] reservedBytes = new byte[5];
System.arraycopy(data, headerOffset + 20, reservedBytes, 0, 5);
byte[] makerCodeBytes = new byte[2];
System.arraycopy(data, headerOffset + 25, makerCodeBytes, 0, 2);
byte[] gameCodeBytes = new byte[4];
System.arraycopy(data, headerOffset + 27, gameCodeBytes, 0, 4);
int version = data[headerOffset + 31] & 0xFF;
properties.put("Game Title", new String(titleBytes, "Shift_JIS").trim().replaceAll("\0", ""));
StringBuilder reservedStr = new StringBuilder();
for (byte b : reservedBytes) {
reservedStr.append(String.format("%02x ", b));
}
properties.put("Reserved", reservedStr.toString().trim());
properties.put("Maker Code", new String(makerCodeBytes, StandardCharsets.US_ASCII).trim());
properties.put("Game Code", new String(gameCodeBytes, StandardCharsets.US_ASCII).trim());
properties.put("Version", version);
}
public void printProperties() {
if (properties.isEmpty()) {
throw new IllegalStateException("Properties not read yet.");
}
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(Map<String, Object> newProperties) throws IOException {
if (data == null) {
throw new IllegalStateException("File not read yet.");
}
if (newProperties != null) {
updateProperties(newProperties);
}
Files.write(Paths.get(filename), data);
}
private void updateProperties(Map<String, Object> newProperties) {
if (newProperties.containsKey("Game Title")) {
byte[] title = ((String) newProperties.get("Game Title")).getBytes(StandardCharsets.UTF_8);
byte[] paddedTitle = new byte[20];
System.arraycopy(title, 0, paddedTitle, 0, Math.min(20, title.length));
System.arraycopy(paddedTitle, 0, data, headerOffset, 20);
}
if (newProperties.containsKey("Reserved")) {
String[] hex = ((String) newProperties.get("Reserved")).split(" ");
byte[] reserved = new byte[5];
for (int i = 0; i < Math.min(5, hex.length); i++) {
reserved[i] = (byte) Integer.parseInt(hex[i], 16);
}
System.arraycopy(reserved, 0, data, headerOffset + 20, 5);
}
if (newProperties.containsKey("Maker Code")) {
byte[] maker = ((String) newProperties.get("Maker Code")).getBytes(StandardCharsets.US_ASCII);
byte[] paddedMaker = new byte[2];
System.arraycopy(maker, 0, paddedMaker, 0, Math.min(2, maker.length));
System.arraycopy(paddedMaker, 0, data, headerOffset + 25, 2);
}
if (newProperties.containsKey("Game Code")) {
byte[] gameCode = ((String) newProperties.get("Game Code")).getBytes(StandardCharsets.US_ASCII);
byte[] paddedGameCode = new byte[4];
System.arraycopy(gameCode, 0, paddedGameCode, 0, Math.min(4, gameCode.length));
System.arraycopy(paddedGameCode, 0, data, headerOffset + 27, 4);
}
if (newProperties.containsKey("Version")) {
int version = (Integer) newProperties.get("Version");
data[headerOffset + 31] = (byte) (version & 0xFF);
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// VBFileHandler handler = new VBFileHandler("example.vb");
// handler.read();
// handler.printProperties();
// Map<String, Object> updates = new HashMap<>();
// updates.put("Version", 2);
// handler.write(updates);
// }
}
JavaScript class that can open any file of format .VB and decode, read, write, and print to console all the properties from the above list (assuming Node.js environment):
const fs = require('fs');
const iconv = require('iconv-lite'); // Install via npm for Shift-JIS decoding
class VBFileHandler {
constructor(filename) {
this.filename = filename;
this.data = null;
this.headerOffset = null;
this.properties = {};
}
read() {
this.data = fs.readFileSync(this.filename);
this.headerOffset = this.data.length - 32;
if (this.headerOffset < 0) {
throw new Error('File too small to contain header.');
}
// Decode properties
const titleBytes = this.data.slice(this.headerOffset, this.headerOffset + 20);
const reservedBytes = this.data.slice(this.headerOffset + 20, this.headerOffset + 25);
const makerCodeBytes = this.data.slice(this.headerOffset + 25, this.headerOffset + 27);
const gameCodeBytes = this.data.slice(this.headerOffset + 27, this.headerOffset + 31);
const version = this.data.readUInt8(this.headerOffset + 31);
this.properties = {
'Game Title': iconv.decode(titleBytes, 'shift_jis').replace(/\0/g, '').trim(),
'Reserved': Array.from(reservedBytes).map(b => b.toString(16).padStart(2, '0')).join(' '),
'Maker Code': makerCodeBytes.toString('ascii').trim(),
'Game Code': gameCodeBytes.toString('ascii').trim(),
'Version': version
};
}
printProperties() {
if (Object.keys(this.properties).length === 0) {
throw new Error('Properties not read yet.');
}
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${value}`);
}
}
write(newProperties = {}) {
if (this.data === null) {
throw new Error('File not read yet.');
}
this.updateProperties(newProperties);
fs.writeFileSync(this.filename, this.data);
}
updateProperties(newProperties) {
if (newProperties['Game Title']) {
const title = iconv.encode(newProperties['Game Title'], 'shift_jis').slice(0, 20);
const paddedTitle = Buffer.alloc(20);
title.copy(paddedTitle);
paddedTitle.copy(this.data, this.headerOffset);
}
if (newProperties['Reserved']) {
const reserved = Buffer.from(newProperties['Reserved'].split(' ').map(h => parseInt(h, 16)));
const paddedReserved = Buffer.alloc(5);
reserved.copy(paddedReserved, 0, 0, Math.min(5, reserved.length));
paddedReserved.copy(this.data, this.headerOffset + 20);
}
if (newProperties['Maker Code']) {
const maker = Buffer.from(newProperties['Maker Code'], 'ascii').slice(0, 2);
const paddedMaker = Buffer.alloc(2);
maker.copy(paddedMaker);
paddedMaker.copy(this.data, this.headerOffset + 25);
}
if (newProperties['Game Code']) {
const gameCode = Buffer.from(newProperties['Game Code'], 'ascii').slice(0, 4);
const paddedGameCode = Buffer.alloc(4);
gameCode.copy(paddedGameCode);
paddedGameCode.copy(this.data, this.headerOffset + 27);
}
if (newProperties['Version']) {
this.data.writeUInt8(newProperties['Version'] & 0xFF, this.headerOffset + 31);
}
}
}
// Example usage:
// const handler = new VBFileHandler('example.vb');
// handler.read();
// handler.printProperties();
// handler.write({ Version: 2 });
C class that can open any file of format .VB and decode, read, write, and print to console all the properties from the above list (using C++):
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>
#include <locale>
#include <codecvt> // For Shift-JIS conversion
class VBFileHandler {
private:
std::string filename;
std::vector<unsigned char> data;
size_t headerOffset;
std::map<std::string, std::string> properties;
public:
VBFileHandler(const std::string& fn) : filename(fn), headerOffset(0) {}
void read() {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
throw std::runtime_error("Unable to open file.");
}
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
data.resize(size);
file.read(reinterpret_cast<char*>(data.data()), size);
headerOffset = size - 32;
if (headerOffset >= size) {
throw std::runtime_error("File too small to contain header.");
}
// Decode properties
std::string title(reinterpret_cast<char*>(&data[headerOffset]), 20);
// Convert Shift-JIS to UTF-8
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::wstring wtitle = converter.from_bytes(title); // Assuming Shift-JIS input needs proper decoder; simplify for example
title = converter.to_bytes(wtitle); // Placeholder; use proper Shift-JIS library if needed
title.erase(std::remove(title.begin(), title.end(), '\0'), title.end());
title = title.substr(0, title.find_last_not_of(' ') + 1);
std::stringstream reservedSs;
for (size_t i = 0; i < 5; ++i) {
reservedSs << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(data[headerOffset + 20 + i]) << " ";
}
std::string reserved = reservedSs.str();
reserved = reserved.substr(0, reserved.find_last_not_of(' '));
std::string makerCode(reinterpret_cast<char*>(&data[headerOffset + 25]), 2);
makerCode = makerCode.substr(0, makerCode.find_last_not_of('\0') + 1);
std::string gameCode(reinterpret_cast<char*>(&data[headerOffset + 27]), 4);
gameCode = gameCode.substr(0, gameCode.find_last_not_of('\0') + 1);
unsigned int version = data[headerOffset + 31];
properties["Game Title"] = title;
properties["Reserved"] = reserved;
properties["Maker Code"] = makerCode;
properties["Game Code"] = gameCode;
properties["Version"] = std::to_string(version);
}
void printProperties() const {
if (properties.empty()) {
throw std::runtime_error("Properties not read yet.");
}
for (const auto& prop : properties) {
std::cout << prop.first << ": " << prop.second << std::endl;
}
}
void write(const std::map<std::string, std::string>& newProperties = {}) {
if (data.empty()) {
throw std::runtime_error("File not read yet.");
}
updateProperties(newProperties);
std::ofstream file(filename, std::ios::binary);
if (!file) {
throw std::runtime_error("Unable to write file.");
}
file.write(reinterpret_cast<const char*>(data.data()), data.size());
}
private:
void updateProperties(const std::map<std::string, std::string>& newProperties) {
if (newProperties.find("Game Title") != newProperties.end()) {
std::string title = newProperties.at("Game Title");
// Assume simple ASCII for simplicity; use encoder for Shift-JIS
title.resize(20, '\0');
std::copy(title.begin(), title.end(), data.begin() + headerOffset);
}
if (newProperties.find("Reserved") != newProperties.end()) {
std::string reservedStr = newProperties.at("Reserved");
std::stringstream ss(reservedStr);
unsigned int byte;
for (size_t i = 0; i < 5 && ss >> std::hex >> byte; ++i) {
data[headerOffset + 20 + i] = static_cast<unsigned char>(byte);
}
}
if (newProperties.find("Maker Code") != newProperties.end()) {
std::string maker = newProperties.at("Maker Code");
maker.resize(2, '\0');
std::copy(maker.begin(), maker.end(), data.begin() + headerOffset + 25);
}
if (newProperties.find("Game Code") != newProperties.end()) {
std::string gameCode = newProperties.at("Game Code");
gameCode.resize(4, '\0');
std::copy(gameCode.begin(), gameCode.end(), data.begin() + headerOffset + 27);
}
if (newProperties.find("Version") != newProperties.end()) {
unsigned int version = std::stoi(newProperties.at("Version"));
data[headerOffset + 31] = static_cast<unsigned char>(version);
}
}
};
// Example usage:
// int main() {
// try {
// VBFileHandler handler("example.vb");
// handler.read();
// handler.printProperties();
// std::map<std::string, std::string> updates;
// updates["Version"] = "2";
// handler.write(updates);
// } catch (const std::exception& e) {
// std::cerr << e.what() << std::endl;
// }
// return 0;
// }