Sanyo MBC-550/555

finding the font in TimeBandit

It took me quite some time to find the font in the binary file. I was expecting a similar way of storing the pixels as for the sprites: 32x16 in steps of 4 bytes per cell with 4 cells in a row repeated 4 times. And that for 3 color channels. However, the font is only one channel and it is stored as 16 bits (the lowest 5 bits are 0) per line with 12 lines (the last line is always 0):

Finding the font in TimeBandit for Sanyo MBC-550/555

I used following Python script to convert the EXE to a textfile with just zeros and ones, 800 bytes per line to minimize linebreaks.

input_binary_file = "bandit-without-code.exe"
output_text_file = "bandit-without-code.txt"

with open(input_binary_file, 'rb') as bin_file, \
     open(output_text_file, 'w') as text_file:

  binary_string = ''.join(format(byte, '08b') for byte in bin_file.read())

  for i in range(0, len(binary_string), 800):
    print(binary_string[i:i+800], file=text_file)

Then I could use the find function in my texteditor to find parts of the letter in binary. For example 10010011001. Screenshot 2024-12-12 at 22 00 00

Changing the line length to 16 and replacing the zeros by spaces and the ones by blocks immediately shows the characters in your texteditor.

binary_string = binary_string.replace("0"," ").replace("1","█")
for i in range(0, len(binary_string), 16):
     print(binary_string[i:i+16], file=text_file)

Screenshot 2024-12-13 at 10 14 26

characters:

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ

draw_char_to_vram in Processing (on the grid)

draw_char_to_vram(chrA, 0,0);

void draw_char_to_vram(byte[] bytes, int si_offset, int di_offset) { //this only works 'on the grid' (every 8 pixels) 
  for (int b=0; b<bytes.length*8; b++) { // b=which bit 0..192 //2 bytes per line (12 lines)
    int x = b%16;
    int y = b/16;
    int si = si_offset + b/8;
    int di = di_offset + (y/4)*(4*COLS) + (y%4) + (x/8)*4;
    int dl = 128 >> (x%8);
    memory[R+di] ^= (bytes[si] & dl);  //XOR
    //println("x="+x,"y="+y,"---","di="+di, "dl="+dl);
  }
}

draw char/bitmap anywhere

draw_char_xy(chrA, 0, 0, 16, 12); //width must be multiple of 8
draw_char_xy(chrB, 12, 0, 16, 12); //width must be multiple of 8

void draw_char_xy(byte[] bytes, int ox, int oy, int w, int h) { // draw anywhere! 'off the grid'
  for (int b = bytes.length*8-1; b>=0 ; b--) { //in case of 24 bytes (16x12 pixels) b=bit index 0..192   //16 bits (2 bytes) per line (12 lines)
    //2*12 = 24 destination bytes affected when horizontally 'on the grid'
    //3*12 = 36 bytes affected when horiztonally 'off the grid'

    int x = (b % w) + ox; // x position including offset
    int y = (b / w) + oy; // y position including offset

    int src_byte_index = b/8; // source index
    int src_bit_index = b%8;
    boolean bit_value = (bytes[src_byte_index] & (128>>src_bit_index)) != 0;

    int dst_byte_index = (y / 4) * (4 * COLS) + (y % 4) + (x / 8) * 4; // destination index
    int dst_bit_index = x%8;

    if (bit_value) {
      memory[R + dst_byte_index] ^= 128>>dst_bit_index; // Set the bit
    } else {
      memory[R + dst_byte_index] &= ~(128>>dst_bit_index); // Clear the bit
    }
  }
}

animated characters from TimeBandit by Michtron

timebandit-animations

position of code and data in BANDIT.EXE

offset=0x0000, count=0x0014  //MZ header
offset=0x0297, count=0x0081  //code
offset=0x0338, count=0x08A8  //FONT
offset=0x08D1, count=0x0293  //code
offset=0x0C30, count=0x3000  //sprites: key till bandit
offset=0x3C20, count=0x002E  //strings: 0+NCN@NLNONRNUNFNI01
offset=0x3C4E, count=0x1E00  //sprites: donut till scorpion
offset=0x627A, count=0x0018  //string 1234567890123456789012345
offset=0x6CF0, count=0x062F  //strings
offset=0x7393, count=0x03BF  //strings trees, cacti, the timegates ...
offset=0x7753, count=0x03D8  //strings
offset=0x7E0A, count=0x0678  //strings
offset=0x84A1, count=0x0477  //code
offset=0x8BBB, count=0x048F  //strings
offset=0x90DB, count=0x003F  //code
offset=0x914A, count=0x1E4C  //code
offset=0xAF96, count=0x0012  //string?

keyboard test

cpu 8086
org 0

GREEN equ 0x1c00

setup:
  mov al,0xFF
  out 0x3a,al           ; keyboard
  mov al,0x30
  out 0x3a,al           ; keyboard

  mov al, 5
  out 10h, al               ; select address 0x1c000 as green video page

  mov ax,GREEN      
  mov es,ax
  xor di,di

draw:
  in al,0x38  ;get data byte
  stosb

  cmp di,14400
  jb draw
  xor di,di
  jmp draw

times (180*1024)-($-$$) db 0

Expression parser

Work in progress expression parser: https://companje.nl/parse

xy-loop with one label

draw4x12:               ; bx should be zero when called
  push bx
  call calc_di_from_bx
  mov bh,4              ; width in cols (1 col = 8px)
  mov bl,4              ; height in rows (1 row = 4px)
  call draw_pic
  pop bx
  add bl,4
  cmp bl,4*8
  jl draw4x12
  mov bl,0
  add bh,4
  cmp bh,4*12
  jl draw4x12
  ret  

set cursor / calc DI

calc_di:          ; input bl,bh [0,0,71,49]
  mov ax,144      ; 2*72 cols
  mul bh          ; bh*=144 resultaat in AX
  shl ax,1        ; verdubbel AX
  mov di,ax       ; di=ax (=bh*288)
  shl bl,1        ; bl*=2
  shl bl,1        ; bl*=2
  mov bh,0
  add di,bx       ; di+=bl
  ret

mame debugger

dump memory.dmp,0,fffff   # hex
save memory.bin,0,fffff   # bin

visual dump of the current memory in MAME

Screenshot 2024-12-01 at 07 57 19

getImageFromBytes

PImage getImageFromBytes(byte[] bytes, int w, int h) { //w,h in pixels - 3 channel 3 bit image
  PImage img = createImage(w, h, RGB);
  img.loadPixels();
  for (int y=0, bit=128, j=0; y<h; y++) {
    for (int x=0; x<w; x++, bit=128>>(x%8), j++) {
      int i = int(y/4)*(w/2)+(y%4)+int(x/8)*4;
      int r = (bytes[i+0*w/8*h] & bit)>0 ? 255 : 0;
      int g = (bytes[i+1*w/8*h] & bit)>0 ? 255 : 0;
      int b = (bytes[i+2*w/8*h] & bit)>0 ? 255 : 0;
      img.pixels[j] = color(r, g, b);
    }
  }
  img.updatePixels();
  return img;
}

draw picture

The image format is optimized for the Sanyo's videomemory. color planes are separated. In the case of a 32x16 picture: 64 bytes red, 64 bytes green, 64 bytes blue. 16px vertical means 4 rows (1 row is 4 lines). 32px horizontal means 4 cols (4*8).

beker-resized

push cs
pop ds      ; ds=cs

mov si, img
mov bh,4 ; cols 
mov bl,4 ; rows
call draw_pic
hlt

draw_pic:
  mov ax, RED
  call draw_channel
  mov ax, GREEN
  call draw_channel
  mov ax, BLUE
  call draw_channel
  ret
draw_channel:
  mov es,ax
  xor di,di
  xor cx,cx
  mov cl,bl        ; rows (bl)
rows_loop:
  push cx
  xor cx,cx
  mov cl,bh        ; cols (bh)
cols_loop:
  movsw
  movsw
  loop cols_loop
  add di,COLS*4 - 4*4
  pop cx
  loop rows_loop
  ret

img: 
  db 0,112,127,112,255,255,255,255,245,250,253,250,0,14,254,14
  db 56,28,7,0,255,127,255,255,253,250,245,255,28,56,224,0
  db 0,0,0,0,15,3,3,3,240,192,192,128,0,0,0,0
  db 0,0,0,0,3,7,63,127,192,176,212,234,0,0,0,0
  db 0,96,117,96,255,255,255,255,160,208,232,208,0,4,234,4
  db 48,24,7,0,255,127,127,191,232,208,224,215,8,16,224,0
  db 0,0,0,0,15,3,3,3,160,192,128,128,0,0,0,0
  db 0,0,0,0,3,7,63,127,128,144,196,226,0,0,0,0
  db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

clear green channel

clear_green_channel:
  mov ax,GREEN
  mov es,ax
  xor di,di
  mov cx,0x2000
  xor ax,ax
  rep stosw
  ret

mame in debug mode

  • start with -debug
  • F11 step into
  • F4 run to cursor
  • -debugscript autostart.txt containing g for 'go'

Ports (hex)

  • 00: PIC
  • 02: PIC
  • 08: disk command/status
  • 0A: disk track
  • 0C: disk sector
  • 0E: disk data
  • 10: video page
  • 18: joystick
  • 1a: printer data
  • 1C: parallel/drive control
  • 1e: PPI control
  • 20: timer channel 0
  • 22: timer channel 1
  • 24: timer channel 2
  • 26: timer control
  • 28: (serial header) serial
  • 29: (serial header)
  • 2a: (serial header) serial
  • 2b: (serial header)
  • 30: CRTC address
  • 32: CRTC data
  • 38: keyboard data
  • 3A: keyboard command/status

HxC Floppy Emulator / Floppy image file converter

See hxc

Time Bandit raw flux file

write this flux file (19,2MB) using Greaseweazle to an empty floppy to play the classic Time Bandit game on your Sanyo MBC-550/555.

gw write bandit.scp --tracks="c=0-39:step=2"

Run Time Bandit on Gotek with Flashfloppy 3.42 and HFE_v3 disk image

Place this HFE_v3 file (2,5MB) on your Gotek drive with FlashFloppy to play Time Bandit on your Sanyo MBC-550/555. First boot in MS-DOS, than switch to this diskimage and type BANDIT to start Time Bandit. Enjoy! 0001_TimeBandit_Sanyo_MBC55x.hfe The flux file by Greaseweazle was converted to HFE_v3 using HxC2001.

RAMDISK

put this in autoexec.bat to get an extra drive in RAM.

ramdisk 64
rem OPTIONAL copy diskcopy.com e:
path e: 

you need this in config.sys:

device=ramdrv.sys

and you need these files on your msdos 2.11 floppy:

RAMDISK.COM
RAMDRV.SYS
DISKCOPY.COM (optional)
CONFIG.SYS
AUTOEXEC.BAT

Mame save screenshot

  • F12 - Saves screenshot to subfolder in 'snap'. for example: ./snap/mbc55x/0000.png'
  • Shift F12 - saves movie file in same location

Mount a .DSK file on Mac

rename it to .DMG and open it. Now you can copy the files. (does not work for MS-DOS v1.25)

Sanyo MBC-555 boot code / BIOS in ROM disassembly

  • clears the screen, inits CRT, keyboard, loads bootsector from floppy and jumps to it
  • FFFF:0000 jump to code in ROM
  • FE00:1E00 start of code in ROM
  • 0038:0000 start of loaded code in floppy bootsector
  • Details: Sanyo MBC-555 boot code in ROM disassembled

Sanyo MBC-555 floppy bootsector disassembly

Repair story by Mike @ Leaded Solder

https://www.leadedsolder.com/2022/08/23/sanyo-mbc555-power-supply-swap-pickup.html tip: https://github.com/keirf/Greaseweazle

black & white ordered dithering

;from dark to light: 4x8 bits (4 lines, 8 bits per line). in total 8 chars.
grays: db 0,0,0,0, 136,0,34,0, 170,0,170,0, 170,17,170,68, 170,85,170,85, 85,238,85,187, 119,255,221,255, 255,255,255,255
ordered-dithering-black-white

3 bit grayscale dithering on monochrome monitor

PImage img = loadImage("input/"+filename);
img.resize(width, 200);
img.filter(GRAY);
img = applyDithering(img, 8); //8 levels of brightness
img = grayTo3bitIntensity(img);
savePIC(img, "data/output/"+filename.replace(".jpg", ".pic"));
///
void savePIC(PImage img, String filename) {
  byte bytes[] = new byte[img.width*img.height*3/8+4];
  bytes[0] = byte(img.width & 255);
  bytes[1] = byte(img.width >> 8);
  bytes[2] = byte(img.height & 255);
  bytes[3] = byte(img.height >> 8);

  for (int i=0, x=0, y=0, n=img.width*img.height/8; i<n; i++) {
    for (int j=128; j>0; j/=2, x=(x+1)%img.width, y=i/(img.width/8)) {
      color c = img.get(x, y);
      bytes[i+4+2*n] |= byte(j * red(c)/255);
      bytes[i+4+1*n] |= byte(j * green(c)/255);
      bytes[i+4+0*n] |= byte(j * blue(c)/255);
    }
  }
  saveBytes(filename, bytes);
}

PImage grayTo3bitIntensity(PImage img) {
  PImage img2 = img.get();
  color c[] = {color(0), color(0,0,255),color(0,255,0),color(0,255,255),color(255,0,0),color(255,0,255),color(255,255,0),color(255,255,255)};
  int lut[] = {0,1,4,2,5,3,6,7};
  img.loadPixels();
  for (int y = 0; y<img.height; y++) {
    for (int x = 0; x<img.width; x++) {
      int index = int(brightness(img.pixels[y*img.width+x])/32); //32=256/8 because 8 colors in 3 bit
      img2.set(x,y,c[lut[index]]);
    }
  }
  return img2;
}

Panel mounted switches for diskimage and drive selection

IMG_8359

Screenshot 2022-09-16 at 12 12 41

run Mame for Sanyo MBC-555

  • ROM in roms/mbc55x/mbc55x-v120.rom
  • Fn+DEL (to enable UI interface controls) then TAB to show menu
  • make mame for (just) Sanyo: make SUBTARGET=mbc55xtest SOURCE=src/mame/sanyo/mbc55x.cpp
  • make mame tools: make TOOLS=1 REGENIE=1
    ./mbc55xtest mbc55x -ramsize 256K -verbose -skip_gameinfo -effect scanlines -window -nomaximize -resolution0 800x600 -flop1 floppies/disk-a.img

BASIC CALL function

BASIC manual Sanyo MBC-555

decode Michtron PIC image file with Processing

size(640,400);
noStroke();

//load and check width
byte bytes[] = loadBytes("SATURN.PIC");
int w = (bytes[1]<<8) + (bytes[0] & 0xff);
int h = (bytes[3]<<8) + (bytes[2] & 0xff);

//check and fix width if needed
//int bytesPerChannel = (bytes.length-4)/3;
//if (w*h/8<bytesPerChannel) 
//  w = (int(w+8)/8)*8;

//draw
for (int i=0, x=0, y=0, n=w*h/8; i<n; i++) {
  for (int j=128; j>0; j/=2, x=(x+1)%w, y=i/(w/8)) {
    int rr=(bytes[i+2*n+4]&j)/j<<8;
    int gg=(bytes[i+1*n+4]&j)/j<<8;
    int bb=(bytes[i+0*n+4]&j)/j<<8;
    fill(rr, gg, bb);
    rect(x, y*2, 1, 1.75); //double height, with slight vertical raster line in between the lines
  }
}

get(0,0,w,int(h*1.75)).save("SATURN.PNG");

SATURN

receive data from Python

on Sanyo: type file.asm > aux

#!/usr/bin/env python3

import serial

ser = serial.Serial('/dev/tty.usbmodem1301',1200)

while True:
    x = ser.read()
    print(x.decode('ascii'), end="")

ser.close()

sanyo mbc-555 VRAM emulation in Processing/Java

(running in 72 cols mode, update to 80 = 640px if needed)

PImage getVRAM() {
  PImage img = createImage(576, 200, RGB);
  img.loadPixels();
  for (int y=0, bit=0, j=0; y<img.height; y++) {
    for (int x=0; x<img.width; x++, bit=128>>(x%8), j++) {
      int i=int(y/4)*img.width/2+(y%4)+int(x/8)*4;
      int r = (mem[RED+i] & bit)>0 ? 255 : 0;
      int g = (mem[GREEN+i] & bit)>0 ? 255 : 0;
      int b = (mem[BLUE+i] & bit)>0 ? 255 : 0;
      img.pixels[j] = color(r, g, b);
    }
  }
  img.updatePixels();
  return img;
}

tixyboot.asm

A tribute to Martin Kleppe's beautiful https://tixy.land as well as a tribute to the Sanyo MBC-550/555 PC (1984) which forced me to be creative with code since 1994.

https://github.com/companje/Sanyo-MBC-550-555-experiments/tree/main/tixy.boot

My own emulator and bootsector experiments

Mame

segments and offsets

"The 8086 has 20-bit addressing, but only 16-bit registers. To generate 20-bit addresses, it combines a segment with an offset. " SEGMENT*16+OFFSET

qemu

not for Sanyo but emulator for x86 in general: On Mac:

brew install qemu
qemu-system-x86_64 -fda boot-basic.img

libdsk

nog onderzoeken: http://www.seasip.info/Unix/LibDsk/

debug

-l 100 1 0 1  # load onto address 100h from 2nd drive (1) the first sector (0) and only one sector
-n A:tmp.com
-rcx
100
-w
writing 0200h bytes (512 bytes)

interrupt controller info

Sanyo MBC550 - John Elliott 27 January 2016

int 14h Serial, int 16h keyboard etc

ms-dos int services

debug search

debug basic.exe
s 1c67:ffff ffff e4 18   # searches for the bytes e4 and 18

Time Bandit BANDIT.EXE crack

this is a first step. Skipping disk access at start.

debug BANDIT.EXE
-g 8ee8
-g =8ef3

samdisk

https://simonowen.com/samdisk/ (samdisk-388-osx works on Macbook Air M1)

./samdisk IMAGE.td0  /Volumes/FLASHFLOPPY/IMAGE.dsk

GOTEK

Sanyo-MBC-555-Rick-Companje

Goed nieuws! Gotek met FlashFloppy firmware werkt super op de Sanyo MBC-555. https://gotek.nl/

# in FF.CFG 
interface = shugart
host = pc-dos
pin02 = auto
pin34 = auto
nav-mode = indexed   # voor 0001-msdos211.img etc
indexed-prefix = ""

Diversen

  • wellicht deze bestellen: http://www.deviceside.com/fc5025.html (nee niet, mailcontact gehad. Werkt alleen voor 1.2MB diskdrives, en dan ook nog eens readonly.)

  • http://www.seasip.info/VintagePC/sanyo.html

  • Capacitor C9 on the board may need to be dealt with if disk access is slow or erratic (it was installed backwards at the factory)."

  • http://www.vintage-computer.com/vcforum/showthread.php?24281-Teac-FD-54A

  • "The PC floppy cable (assuming that you don't have any drives with a READY/ line on pin 34) can be a bit problematical to fabricate. On the other hand, if you can find a Teac FD235HF with the appropriate jumpers or a FD235F (which does have a READY/ line), you're in business, sort of." [[http://www.vintage-computer.com/vcforum/archive/index.php/t-23641.html?s=382f5103d732b9ff22b19f0dcba42069|source]]

  • see [[disk]]

  • index sensor measures optically the hole in the floppy disk. It marks the start of the current track. Read 'index sensor adjustment' in Sams Computer Facts about the Sanyo. I measure 208ms (milliseconds) for one turn of the disk. Around 5 turns a second and 300 turns per minute. Which is right according to 'spindle speed adjustment' part.

  • Weird thing: when I remove the index sensor this has no effect on the readings on TP9 and TP10. There's 10us between the pulses.

  • 'Precompensation adjustment': Connect input of a scope to TP1 on System Board. Set scope sweep to 1uSec, voltage to 2V and trigger to positive slope. Adjust Precompensation Control (RV1) for 2uSec from the rising edge of the first pulse to the rising edge of the second pulse. RESULT: 2uSec 500kHz.

DISKCOPY

Als een diskette bad sectors heeft kun je geen DISKCOPY gebruiken. Je kunt in veel gevallen wel met COPY de bestanden overzetten. Eventueel kun je met DEBUG de sectors 1 voor laden en wegschrijven (l 0 0 5 1 -> w 0 0 0 5 1).

Info about segments:offsets

Test pins on mainboard

  • TP1: Precompensation adjustment test. Should measure 2 uSec / 500 kHz. Adjust RV1 to fix.
  • TP2: ??

Drive Track Program

The following Basic program can be used to select Driva A or B, select side 0 or 1 and step the Drive Head to a specific track. To stop the program, press the BREAK key.

10 INPUT "ENTER DRIVE (A OR B)"; D$
20 INPUT "ENTER SIDE (0 OR 1)"; S
30 IF S=0 THEN Y=0 ELSE Y=4
40 IF D$="A" THEN Z=0 ELSE Z=1
50 TS=0: OUT 28, Y+Z: OUT 8,8
60 FOR D=1 TO 500: NEXT D
70 INPUT "ENTER TRACK NUMBER"; T
80 IF T>40 THEN 70
90 IF T>TS THEN TR=T-TS ELSE TR=TS-T
100 IF T>TS THEN C=72 ELSE C=104
110 FOR X=1 TO TR
120 OUT 8,C
130 FOR D=1 TO 5: NEXT D
140 NEXT X: TS=T
150 PRINT "PRESS ANY KEY TO STOP"
160 A$=INKEY$: OUT 8,228: IF A$="" THEN 160 ELSE 70

EDLIN

https://www.computerhope.com/edlin.htm

1l  # list from line one
1   # edit line 1
5i  # insert line(s) before line 5
q   # exit without saving
e   # save and exit
edlin autoexec.bat
i
  line of text
  ^Z
e

wordstar manual

http://www.textfiles.com/bitsavers/pdf/microPro/Wordstar_3.3/Wordstar_3.3_Reference_Manual_1983.pdf

debug.com

  • fill memory with 0's: e 0 ffff 0
  • rcx sets cx register. This register is used in debug.com to store the amount of bytes to write to the loaded (or newly created file).
  • l 0 0 5 1 load sector 5 of drive 0 at currentSeg:0000

create a program with debug.com (ms-dos 1.25 without assemble command)

A> debug test.com
- e 100
  B8 {space} 00 {space} 4C {space} CD {space} 21 {enter}
-u
0AAC:0100 B8004C    MOV    AX,4C00
0AAC:0103 CD21      INT    21
-w
-q
-rcx
6
-n filename
-w

more info about Debug: https://thestarman.pcministry.com/asm/debug/debug.htm

asm.com

technical info

Game I/O

  • http://www.built-to-spec.com/blog/2009/09/10/using-a-pc-joystick-with-the-arduino/
  • https://www.epanorama.net/documents/joystick/pc_joystick.html
  • https://github.com/phillipmacon/m.a.m.e/blob/master/src/devices/bus/a2gameio/gameio.cpp
    Apple II Game I/O Connector
    This 16-pin DIP socket is described in the Apple II Reference
    Manual (January 1978) as "a means of connecting paddle controls,
    lights and switches to the APPLE II for use in controlling video
    games, etc." The connector provides for four analog "paddle"
    input signals (0-150KΩ resistance) which are converted to
    digital pulses by a NE558 quad timer on the main board. The
    connector also provides several digital switch inputs and
    "annunciator" outputs, all LS/TTL compatible. Apple joysticks
    provide active high switches (though at least one third-party
    product treats them as active low) and Apple main boards have no
    pullups on these inputs, which thus read 0 if disconnected.
    While pins 9 and 16 are unconnected on the Apple II, they provide
    additional digital output and input pins respectively on the Sanyo
    MBC-550/555 (which uses 74LS123 monostables instead of a NE558).
    The Apple IIgs also recognizes a switch input 3, though this is
    placed on pin 9 of the internal connector rather than 16.
    ...
                                ____________
                   +5V   1 |*           | 16  (SW3)
                   SW0   2 |            | 15  AN0
                   SW1   3 |            | 14  AN1
                   SW2   4 |            | 13  AN2
                  /STB   5 |  GAME I/O  | 12  AN3
                  PDL0   6 |            | 11  PDL3
                  PDL2   7 |            | 10  PDL1
                   GND   8 |            |  9  (AN4/SW3)
                            ------------