""" Some info. Size = 0 = 1BDh 1 = 3BDh Tilemap screen1 base = 800h Tilemap screen2 base = 1000h Tileset base = 2000h Sprite base = 1800h 0000:7000 store the screen orientation 0000:7001 and onward can be used by the boot splash for variables Splash flags, bit 7 > 0 = 1BPP tiles, 1 = 2BPP tiles bit 4-0 store amount of palettes to copy. For 1bpp tiles, each palettes store 2 colours. For 2bpp tiles, each palettes store 4 colours. Splash data is copied at 0600:0000 Avail color for console name: 0600:06e6 [0] 0h, F00h, F70h, FF0h 0600:06ee [4] 7F0h, F0h, F7h, FFh 0600:06f6 [8] 7Fh, Fh, 70Fh, F0Fh 0600:06fe [12] F07h, FFFh, 777h On the VBlank code, CS = 0600h DS = 0600h ES = ?? SS = 0000h struct bootsplash_t { uint8_t padding[3]; uint8_t consoleFlags; uint8_t consoleNameColor; uint8_t padding2; uint8_t size; uint8_t startFrame; uint8_t endFrame; uint8_t spriteCount; uint8_t paletteFlags; uint8_t tilesCount; uint16_t paletteOffset; uint16_t tilesetOffset; uint16_t tilemapOffset; uint16_t horizontalTilemapDestOffset; uint16_t verticalTilemapDestOffset; uint8_t tilemapWidth; uint8_t tilemapHeight; uint32_t splashCodePointer; uint16_t consoleNameHorizontalPos; uint16_t consoleNameVerticalPos; uint8_t padding3[2]; uint16_t soundSampleOffset; uint16_t soundChannelDataOffset[3] }; Sounds seems to be uint32_t duration (8 bit) (in vblank) Volume (8bit) Pitch (16bit) if pitch bit 7 is set then the channel is stopped. """ import os import json import struct import argparse from . import __version__ class Coordinates(object): def __init__(self, config): self.x = config["top"] self.y = config["left"] class Animation(object): def __init__(self, config): self.start = config["startTime"] self.end = config["endTime"] class ConsoleName(object): def __init__(self, config): self.vertical = Coordinates(config["vertical"]) self.horizontal = Coordinates(config["horizontal"]) self.color = config["color"] class Tiles(object): def __init__(self, config): self.bpp = config["bpp"] self.count = config["count"] binfile = os.path.abspath(config["binfile"]) self.data = open(binfile, "rb").read() def get_size(self): return len(self.data) def write(self, f): f.write(self.data) class Tilemap(object): def __init__(self, config): self.vertical = Coordinates(config["vertical"]) self.horizontal = Coordinates(config["horizontal"]) self.height = config["height"] self.width = config["width"] binfile = os.path.abspath(config["binfile"]) self.data = open(binfile, "rb").read() def get_size(self): return len(self.data) def get_horz_offset(self): return 2 * (self.horizontal.x * 32 + self.horizontal.y) + 0x800 def get_vert_offset(self): return 2 * (self.vertical.x * 32 + self.vertical.y) + 0x800 def write(self, f): f.write(self.data) class VBlankCode(object): def __init__(self, config): if "asm" in config: asmfile = os.path.abspath(config["asm"]) binfile = os.path.splitext(asmfile)[0] + ".bin" runcmd = "nasm -f bin -o {output} {input}".format(input=asmfile, output=binfile) os.system(runcmd) self.data = open(binfile, "rb").read() elif "binfile" in config: binfile = os.path.abspath(config["binfile"]) self.data = open(binfile, "rb").read() else: self.data = b"cb" def get_size(self): return len(self.data) def write(self, f): f.write(self.data) class Sound(object): def __init__(self, config): binfile = os.path.abspath(config["waves"]) self.waves = open(binfile, "rb").read() channeldata = config["channelbin"] self.chdata = {} self.channel_count = 0 for i in range(4): self.chdata[i] = None tag = "ch{i}".format(i=i) if channeldata[tag] is not "": self.channel_count += 1 binfile = os.path.abspath(channeldata[tag]) self.chdata[i] = open(binfile, "rb").read() if self.channel_count == 0: raise Exception("You need at least one channel") def get_size(self): return len(self.waves) def get_ch_size(self): current_len = 0 for i in range(4): if self.chdata[i]: current_len += len(self.chdata[i]) return current_len def get_list_size(self): current_len = 2 # We always have a tag to show the end of list for i in range(4): if self.chdata[i]: current_len += 2 return current_len def write(self, f): f.write(self.waves) def write_list(self, f, offset): for i in range(4): if self.chdata[i]: f.write(struct.pack(" 16: raise Exception("You have too many palettes set (max 16)") self.flags = int(self.flags) & 0x1F if self.bpp == 2: self.flags = self.flags | 0x80 else: self.flags = self.flags & ~0x80 def get_size(self): return len(self.palettes) * 2 def write(self, f): if self.palettes: for p in self.palettes: f.write(struct.pack("BB", (p[1] << 4) | p[2], p[0])) else: f.write(self.data) class BootSplash(object): def __init__(self, config_json): self._spriteCount = config_json["sprite"]["count"] self._animation = Animation(config_json["animation"]) self._consoleName = ConsoleName(config_json["consoleName"]) self._palettes = Palette(config_json["palette"]) self._tiles = Tiles(config_json["tiles"]) self._tilemap = Tilemap(config_json["tilemap"]) self._vblankcode = VBlankCode(config_json["vblankCode"]) self._sound = Sound(config_json["sound"]) # Set bootsplash, and volume to 2 self._consoleFlags = 0x82 def write(self, filename): # This is the size of the start structure, used to calculate offset for # all other data offset = 36 + self._sound.get_list_size() with open(filename, "wb") as f: f.write(struct.pack("xxx")) f.write(struct.pack("B", self._consoleFlags)) f.write(struct.pack("B", self._consoleName.color)) f.write(struct.pack("x")) f.write(struct.pack("B", 1)) f.write(struct.pack("BB", self._animation.start, self._animation.end)) f.write(struct.pack("B", self._spriteCount)) f.write(struct.pack("B", self._palettes.flags)) # Splash flags f.write(struct.pack("B", self._tiles.count)) f.write(struct.pack(" 0x3DB: raise Exception("This boot splash is bigger than 987 bytes and will not work.") def main(): print("WonderSwan SplashBuilder - {ver}".format(ver=__version__)) parser = argparse.ArgumentParser() parser.add_argument("-c", "--config", required=True, type=str, help="Configuration file") parser.add_argument("-o", "--output", type=str, help="Output file", default="output.bin") args = parser.parse_args() try: with open(args.config, "rt") as f: config = json.load(f) except IOError: print("Error: config file '{f}' not found".format(f=args.config)) return -1 try: bs = BootSplash(config) bs.write(args.output) except FileNotFoundError as e: print("Error: The file '{f}' cannot be found," " please check your config file".format(f=e.filename)) except Exception as e: print("Error: {e}".format(e=e))