Jump to content
IGNORED

The OS7 Journey, let's use it!


tschak909

Recommended Posts

Hey guys, as some of you have noticed, I have the Coleco Adam back on the bench, so I've been circling back around to not only doing FujiNet firmware updates, but also trying to work on particular projects I didn't get to, the last time around.

 

One of these has been the analysis and binding of OS7 functions for use in the excellent Z88DK C and Assembler suite. 

https://github.com/tschak909/os7lib

 

This has been facilitated by the fact that internal Coleco documentation for OS7 has been found, little by little, such as the ColecoVision Programmers Guide Rev 5, and a complete bound copy of both the OS7 and EOS absolute program  listings: https://archive.org/details/coleco-adam-technical-manual-eos6-os7

 

This is to be done in two parts:

 

The first part is the easy part, writing the bindings for each OS7 function, ignoring the Pascal entry points.

 

This is done by going through each individual function, and wrapping it so that parameters are passed and returned from the ROM routines, in exactly the way needed, e.g. for PUT_VRAM:

 

#include <arch/z80.h>
#include "os7.h"

/** 
 * PUT_VRAM - writes to VDP ram starting at TABLE_CODE, start index count, and count number of bytes 
 *
 * @param table_code - Table code (see VDPTable enum type)
 * @param start_index - The starting entry in the given table
 * @param data - The tarput buffer in CRAM
 * @param count - The number of items
 */
void put_vram(VDPTable table, unsigned short start_index, void *data, unsigned short count)
{
  Z80_registers r;

  r.Bytes.A   = table;
  r.UWords.DE = start_index;
  r.UWords.HL = data;
  r.UWords.IY  = count;

  AsmCall(PUT_VRAM,&r,REGS_ALL,REGS_ALL);
}

 

And it's already starting to work, given a small test program:

#include <stdbool.h>
#include <os7.h>
#include "globals.h"
#include "init.h"

void opts(void)
{
  mode_1();
  load_ascii();
  game_opt();
  while(1);
}

void main(void)
{
  opts();
}

 

We indeed get the options screen:

 

image.thumb.png.a4781341d805fd9260ab0f5527d1f0e2.png

 

and actually if we modify the rom.asm for lib/target/coleco/classic to swap the first two cartridge bytes, and filling in the game name area, we can get the coleco splash screen with our game:

 

image.thumb.png.6a71e27ca30ae8d5e809088831007389.png

image.thumb.png.83b1d663fd47e8c4385741b0a0cf0da1.png

image.thumb.png.b32e7c5e880bdde804f3b3293388b37f.png

My immediate task is to finish the bindings, then to test them, I will write a version of Targ that uses them in C. While this won't serve as an example of using Pascal to write a game (as I don't have the requisite HP 64800 Pascal compiler package, nor the HP 64000 development system itself), it will be very close, and imho better, as C maps system level constructs much more efficiently than Pascal, due to the fact that internal data structures can be cleanly abstracted by structs and union pointers, without doing crazy function/procedure marshalling gymnastics needed in Pascal.

 

I went ahead and scribbled together a mode 1 screen for Targ, there are plenty of character set bits available so that almost no sprites will be needed (basically just the Wummel and the bullet), I did have to truncate the playfield vertically to 6 rows instead of 9, due to the horizontally oriented screen:

 

image.png

 

Thoughts?

 

-Thom

 

Edited by tschak909
  • Like 3
Link to comment
Share on other sites

As part of my OS7LIB work, I've put together a file containing pointers from all of the released #ColecoVision cartridges, so I can find correlations and figure out the effective amount of memory available as a result. If anyone else has any insight on this, please feel free to chime in.

 

 

 

https://github.com/tschak909/os7lib/blob/main/pointer-examples.md

 

os7lib-pointers.png

Link to comment
Share on other sites

The example game, Targ has now been given a repository on GitHub:

https://github.com/tschak909/colecovision-targ

 

image.thumb.png.cc754bafdd95262f8375115f8e85c940.png

 

It needs the OS7 library to build:

https://github.com/tschak909/os7lib

 

Absolutely no standard C library functions are used, or will be used, this is the point.

 

But you can already see some nice things in play:

 

In general, a carefully compartmentalized design, deliberately keeping code small.

 

game.c currently shows how to use put_vram to easily get things onto the screen, without overrunning the VDP.

 

get_ready, does too, but also shows how the timer functions are set up and used.

 

color palette and function which sets it lives in colors.*

 

patterns for the nametable are in patterns.* and are in binary notation, so you can see the pixels.

 

scores shows how to easily set up score routines by making some basic assumptions on how to store and manipulate.

 

The magellan-files serve as a visual reference, and I copy the pixels out into code.

 

The next task involves using the Graphics software inside OS7 to plot the Wummel and the Targs, so I am putting my face entirely inside this:

Section 11-Appendix B-Graphics Users Manual.pdf

 

If anyone has any insight into this corner of OS7, please speak up.

 

-Thom

Edited by tschak909
  • Like 1
Link to comment
Share on other sites

Figuring out how the semi-mobile objects work in the #ColecoVision #OS7 graphics package, while working through my C bindings.

 

Details in Appendix B of The ColecoVision Programmers Manual, Rev 5


#retrocomputing #retrogaming #gamedev

 

 

#include <stdlib.h>
#include <os7.h>
#include "patterns.h"


struct _targGraphics
{
  unsigned char obj_type;
  unsigned char first_gen_name;
  unsigned char numgen;
  void *generators;
  Frame *frame[8];
};

struct _targFrame
{
  unsigned char x_extent;
  unsigned char y_extent;
  unsigned char generator_0;
  unsigned char generator_1;
};

// Extents must be at least 1, 0 causes a pre-decrement wrap to 255
const struct _targFrame targFrame0 = {2,1,0x60,0x61};
const struct _targFrame targFrame1 = {2,1,0x62,0x63};
const struct _targFrame targFrame2 = {2,1,0x64,0x65};
const struct _targFrame targFrame3 = {2,1,0x66,0x67};
const struct _targFrame targFrame4 = {2,1,0x68,0x69};
const struct _targFrame targFrame5 = {2,1,0x6A,0x6B};
const struct _targFrame targFrame6 = {2,1,0x6C,0x6D};
const struct _targFrame targFrame7 = {2,1,0x6E,0x6F};

const struct _targGraphics targGraphics={0,0x60,16,_targ_right_patterns,{targFrame0,targFrame1,targFrame2,targFrame3,targFrame4,targFrame5,targFrame6,targFrame7}};

Status targStatus;
unsigned char targOldScreen[2];
const SMO targSMO={targGraphics,targStatus,targOldScreen};

int i=0;

void targ(void)
{
  SignalNum wait;
  
  activate(&targSMO,false);
  
  for (i=0;i<256;i++)
    {
      targStatus.x++;
      targStatus.frame++;
      targStatus.frame &= 0x07;
      
      activate(&targSMO,true);
      put_obj(&targSMO);
    }
}

 

If anyone can chime in, please do so.

-Thom

 

Link to comment
Share on other sites

A very productive day today.

That is 10 SEMI-MOBILE objects (the Targ riders), which are objects that are copied directly to the VDP, with the underlying contents saved. This means that the Targ is pre-shifted 8 times across two generators (tiles).

 

What I learned:

 

First, the code for the targ objects:

/**
 * @brief Targ example using os7lib
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see LICENSE for details
 * @verbose Targ rider object code/data
 */

#include <stdlib.h>
#include <os7.h>
#include "patterns.h"


/* GRAPHICS data object to map patterns in patterns.c */
struct _targGraphics
{
  unsigned char obj_type;        // Object type, 0=SEMI_MOBILE
  unsigned char first_gen_name;  // First generator this graphics set uses
  unsigned char numgen;          // number of total generators (counting extents) this object uses
  void *generators;              // Pointer to the generator patterns in patterns.c
  Frame *frame[8];               // pointers to frame objects
};

struct _oldScreen                // Frame objects that keep characters underneath before draw
{
  unsigned char x_pat_pos;       // saved X position
  unsigned char y_pat_pos;       // saved Y position
  unsigned char x_extent;        // X extent (how many steps in X direction)
  unsigned char y_extent;        // Y extent (how many steps in the Y direction)
  unsigned char generator_0;     // Pointer to the first generator generator to save in pattern table
  unsigned char generator_1;     // pointer to the second generator to save in the pattern table
};

struct _targFrame
{
  unsigned char x_extent;        // X extent (how many steps in X direction)
  unsigned char y_extent;        // Y extent (how many steps in Y direction)
  unsigned char generator_0;     // Pointer to first generator to plot in pattern table
  unsigned char generator_1;     // Pointer to second generator to plot in pattern table
};

// Extents must be at least 1, 0 causes a pre-decrement wrap to 255
// The frame objects.
const struct _targFrame targFrame0 = {1,2,0x60,0x61};
const struct _targFrame targFrame1 = {1,2,0x62,0x63};
const struct _targFrame targFrame2 = {1,2,0x64,0x65};
const struct _targFrame targFrame3 = {1,2,0x66,0x67};
const struct _targFrame targFrame4 = {1,2,0x68,0x69};
const struct _targFrame targFrame5 = {1,2,0x6a,0x6b};
const struct _targFrame targFrame6 = {1,2,0x6c,0x6d};
const struct _targFrame targFrame7 = {1,2,0x6e,0x6f};

// The Targ graphic object
const struct _targGraphics targGraphics=
  {
    0,
    0x60,
    16,
    _targ_down_patterns,
    {targFrame0,targFrame1,targFrame2,targFrame3,targFrame4,targFrame5,targFrame6,targFrame7}
  };

// The 10 targStatus objects (x, y, frame, etc.)
Status targStatus[10];

// The 10 targOldScreen objects (see above)
struct _oldScreen targOldScreen[10];

// The 10 top level SEMI-MOBILE objects
SMO targSMO[10];

/**
 * @brief since SMO is in RAM, we have to initialize it dynamically
 */
void targ_init(void)
{
  for (int i=0;i<10;i++)
    {
      targSMO[i].graphics_addr=&targGraphics;
      targSMO[i].status_addr=&targStatus[i];
      targSMO[i].old_screen_addr=&targOldScreen[i];
    }
}

/**
 * @brief plot 10 targs, and go down the screen
 */
void targ(void)
{
  targ_init();

  // Set initial positions
  for (int i=0;i<10;i++)
    {
      activate(&targSMO[i],false);
      targStatus[i].x=(i*24)+16;
      targStatus[i].y=40;
      targStatus[i].frame=0;
    }

  // The main loop here, update position
  while(true)
    {
      for (int i=0;i<10;i++)
        {
          if (targStatus[i].frame==0x07)
            {
              targStatus[i].frame=0;
              targStatus[i].y+=8;
              if (targStatus[i].y>160)
                return;
            }
          else
            targStatus[i].frame++;

          put_obj(&targSMO[i]);
        }
    }
}

 

Currently, in C, the data structures have to be adapted for the number of individual data items that will be contained. I am looking at macros to try and simplify this.

 

For a SEMI-MOBILE object, a top level SEMI-MOBILE object is created containing three pointers to child objects that specify the graphics, the status (where the object is/frame/etc), and a backing store structure called OLD_SCREEN to hold the old screen data when the object is being plotted. You have one of these for each semi-mobile object you want to plot. Ironically, because animation can only occur on tile boundaries, this was intended to be used to quickly plot background graphics, but you can use it for smooth animation if you pre-shift the tiles.

 

/**
 * @brief A Semi-Mobile Object top level definition
 */
typedef struct _smo
{
  void *graphics_addr;        // pointer in ROM to graphics object
  void *status_addr;          // pointer in RAM to status 
  void *old_screen_addr;      // pointer in RAM to old screen data (backing store), bit 15 set = disable
} SMO;

 

The Graphics data object for the targs encompasses 8 frame objects, so the typedef for the targGraphics object looks like:

/* GRAPHICS data object to map patterns in patterns.c */
struct _targGraphics
{
  unsigned char obj_type;        // Object type, 0=SEMI_MOBILE
  unsigned char first_gen_name;  // First generator this graphics set uses
  unsigned char numgen;          // number of total generators (counting extents) this object uses
  void *generators;              // Pointer to the generator patterns in patterns.c
  Frame *frame[8];               // pointers to frame objects
};

Since we need 8 frame objects to hold the 8 possible animations, an array of 8 pointers to frame objects is used.

 

The Graphics object is filled like this: 

// The Targ graphic object
const struct _targGraphics targGraphics=
  {
    0,
    0x60,
    16,
    _targ_down_patterns,
    {targFrame0,targFrame1,targFrame2,targFrame3,targFrame4,targFrame5,targFrame6,targFrame7}
  };

 

The 0 specifies that this graphic is for SEMI MOBILE object use.

0x60 specifies that the graphic should start at PATTERN TABLE index 0x60 (out of 0xFF)

Because there are 8 frames of 2 generators each, we need a total of 16 generators

And the generators need to come from _targ_down_patterns in patterns.c like so:

const unsigned char _targ_down_patterns[]=
  {
    // frame 0
    0b11000011,
    0b11100111,
    0b01111110,
    0b01111110,
    0b00111100,
    0b00111100,
    0b00011000,
    0b00011000,

    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,

    // frame 1
    0b00000000,
    0b11000011,
    0b11100111,
    0b01111110,
    0b01111110,
    0b00111100,
    0b00111100,
    0b00011000,

    0b00011000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,

    // frame 2
    0b00000000,
    0b00000000,
    0b11000011,
    0b11100111,
    0b01111110,
    0b01111110,
    0b00111100,
    0b00111100,

    0b00011000,
    0b00011000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,

    // frame 3
    0b00000000,
    0b00000000,
    0b00000000,
    0b11000011,
    0b11100111,
    0b01111110,
    0b01111110,
    0b00111100,

    0b00111100,
    0b00011000,
    0b00011000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,

    // frame 4
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b11000011,
    0b11100111,
    0b01111110,
    0b01111110,

    0b00111100,
    0b00111100,
    0b00011000,
    0b00011000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,

    // frame 5
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b11000011,
    0b11100111,
    0b01111110,

    0b01111110,
    0b00111100,
    0b00111100,
    0b00011000,
    0b00011000,
    0b00000000,
    0b00000000,
    0b00000000,

    // frame 6
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b11000011,
    0b11100111,

    0b01111110,
    0b01111110,
    0b00111100,
    0b00111100,
    0b00011000,
    0b00011000,
    0b00000000,
    0b00000000,

    // frame 7
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b11000011,

    0b11100111,
    0b01111110,
    0b01111110,
    0b00111100,
    0b00111100,
    0b00011000,
    0b00011000,
    0b00000000,
    
    // color/attribute data
    0x64,
    0x64,
    0x64,
    0x64,
    0x64,
    0x64,
    0x64,
    0x64,
  };

 

As can be see here, each pattern is defined sequentially, with the color attributes (in VDP order, hi-nibble foreground, lo-nibble background) specified for each generator afterwards.

 

But what's a frame object? It specifies which generators in the PATTERN_TABLE to use. Activate() will copy the data from the generators pointer above in ROM, to the VDP area in those generator values specified in the Frame objects.

 

struct _targFrame
{
  unsigned char x_extent;        // X extent (how many steps in X direction)
  unsigned char y_extent;        // Y extent (how many steps in Y direction)
  unsigned char generator_0;     // Pointer to first generator to plot in pattern table
  unsigned char generator_1;     // Pointer to second generator to plot in pattern table
};

Each frame needs two generators (tiles), so the data structure has to take this into account. Each frame object is filled in thus:

// Extents must be at least 1, 0 causes a pre-decrement wrap to 255
// The frame objects.
const struct _targFrame targFrame0 = {1,2,0x60,0x61};
const struct _targFrame targFrame1 = {1,2,0x62,0x63};
const struct _targFrame targFrame2 = {1,2,0x64,0x65};
const struct _targFrame targFrame3 = {1,2,0x66,0x67};
const struct _targFrame targFrame4 = {1,2,0x68,0x69};
const struct _targFrame targFrame5 = {1,2,0x6a,0x6b};
const struct _targFrame targFrame6 = {1,2,0x6c,0x6d};
const struct _targFrame targFrame7 = {1,2,0x6e,0x6f};

 

Finally, we need a Status object for each SEMI-MOBILE object. This contains the following information:

/**
 * @brief a STATUS object
 */
typedef struct _status
{
  unsigned char frame;
  int x;
  int y;
  unsigned char next_gen;
} Status;

(it is worth noting that the x and y coordinates for objects are 16-bits, referring to a "meta-plane" allowing objects to slide smoothly on and off the display, taking care of e.g. setting early clock bit for sprites)

 

and create 10 instances of status and oldscreen objects as an array:

// The 10 targStatus objects (x, y, frame, etc.)
Status targStatus[10];

// The 10 targOldScreen objects (see above)
struct _oldScreen targOldScreen[10];

// The 10 top level SEMI-MOBILE objects
SMO targSMO[10];

 

We have to do a little bit of initialization because the SMO objects are in RAM. WIll try to move everything to ROM.

/**
 * @brief since SMO is in RAM, we have to initialize it dynamically
 */
void targ_init(void)
{
  for (int i=0;i<10;i++)
    {
      targSMO[i].graphics_addr=&targGraphics;
      targSMO[i].status_addr=&targStatus[i];
      targSMO[i].old_screen_addr=&targOldScreen[i];
    }
}

 

but once initialized, the objects can be activated just before their use, which copies them from the ROM into the VDP, we go ahead and set the positions in the status register while we're at it:

  // Set initial positions
  for (int i=0;i<10;i++)
    {
      activate(&targSMO[i],false);
      targStatus[i].x=(i*24)+16;
      targStatus[i].y=40;
      targStatus[i].frame=0;
    }

 

and to make them move, all we have to do is call put_obj(), changing X and the frame counter to move it down the screen. Any calls to putobj happen on the next frame, automatically.

  // The main loop here, update position
  while(true)
    {
      for (int i=0;i<10;i++)
        {
          if (targStatus[i].frame==0x07)
            {
              targStatus[i].frame=0;
              targStatus[i].y+=8;
              if (targStatus[i].y>160)
                return;
            }
          else
            targStatus[i].frame++;

          put_obj(&targSMO[i]);
        }
    }

 

To make this work, I had to add a call to initialize the WRITER queue, which is used to keep track of deferred writes that need to happen on the next VDP interrupt. I made the queue 16 entries long (each entry is 3 bytes). There is also a call to the TIME_MGR() which will decrement every active timer by 1 at the end of every frame.

 

TimerTable tt[2];
TimerData td[2];
unsigned char queue[48];

static void vdp_nmi(void)
{
  M_PRESERVE_ALL;
  writer();
  time_mgr();
  M_RESTORE_ALL;
}

void init(void)
{
  init_timer(&tt,&td);
  init_writer(queue,16);
  add_raster_int(vdp_nmi);
}

 

The timer subsystem handles timers up to 65535 ticks (frames) in length, and track whether they need to repeat, or not, using REQUEST_SIGNAL to request a new timer, and TEST_SIGNAL to see if a timer has lapsed.

 

All in all, I'm finding this set of routines (built into each and every ColecoVision) to be very complete and well thought out. Yes, there are bugs, but they are documented in the manual with work-arounds. I can see exactly why COLECO was able to pump so many titles out, so quickly, and I am proving here, that you can use the asm vectors from C. :)

 

More to come, but this post is long enough.

 

oh yeah, the ROM is here:

test-230913.rom

 

-Thom

Edited by tschak909
Link to comment
Share on other sites

Another day, grinding through, this time implementing the data structures for "Mobile", or MOB objects.

 

These are objects that, like the "Semi-mobile" objects, use the background pattern plane, except they are exactly four pattern generators arranged in a 2x2 pattern, creating a 16x16 object. Unlike semi-mobile objects, which must always exist on pattern boundaries (e.g. animate in 8 pixel increments), the mobile objects can be positioned on any pixel boundary, and they can bleed on and off the pattern plane automatically. You can literally think of them as "soft-sprites."

 

It accomplishes this by creating a temporary area of 9 generators in RAM, onto which the object is shifted and ORed into place.

 

So you need a set of objects to represent this, a MOBILE GRAPHICS object:

/**
 * @brief a MOB GRAPHICS object defining n of frames
 */
#define MOBGraphics(n)                          \
  typedef struct _mob_graphics_n                \
  {                                             \
    unsigned char obj_type;                     \
    unsigned char numgen;                       \
    void *newgen;                               \
    void *generators;                           \
    MOBFrame *frame[n];                         \
  }

For the sake of dynamism, this is wrapped into a macro, which generates the appropriate number of FRAME object pointers.

The obj_type for this object, is 1, while SEMI_MOBILE objects use 0. This is so the various object routines in memory can properly detect the object type, especially when used with compound objects.

 

You also need to specify a numgen, or number of total generators that are a part of this object. For mobile objects, recall that they are made up of 4 generators, so e.g. for 3 seperate frames, you'll need to specify 12 seperate generators for numgen

 

What is new here, is the pointer to a newgen, that is a place in RAM where the image transformation of shifting and compositing will take place, before being copied into the VDP ram. This is always 9 generators long, needing 72 bytes in total. This is to cover all the positional possibilities while doing the transformation. 

 

Like the SEMI_MOBILE object, generators is a pointer to the graphic data. The graphic data is specified as:

  • 8 bytes for the top-left quadrant
  • 8 bytes for the bottom-left quadrant
  • 8 bytes for the top-right quadrant
  • 8 bytes for the bottom-right quadrant

multiplied by the number of frames that this graphic data needs to contain.

 

And finally, there are a number of pointers to MOBFrame objects, which contain the ROM data to VDP generator mappings required, as well as the desired color for the target object, placed, in the topmost nibble:

/**
 * @brief the frame data for a given MOB GRAPHICS object
 */
typedef struct _mob_frame
{
  unsigned char upper_left;
  unsigned char lower_left;
  unsigned char upper_right;
  unsigned char lower_right;
  unsigned char color;
} MOBFrame;

 

As before with the SEMI_MOBILE objects, there is an OldScreen data structure that serves the same purpose, as a backing store. The difference is that the number of generators to store is fixed to the 9 maximum to cover all transformations:

typedef struct _mob_old_screen
{
  unsigned char x_pat_pos;
  unsigned char y_pat_pos;
  unsigned char saved_generators[9];
} MOBOldScreen;

 

Like the SEMI_MOBILE objects, the MOBILE OBJECTs have a status object, that stores current position, frame, and a pointer to the object's newgen.

/**
 * @brief a MOBILE status object
 */
typedef struct _mob_status
{
  unsigned char frame;
  int x;
  int y;
  void *newgen;
} MOBStatus;

 

Pulling this all together, the top level MOB object looks like:

/**
 * @brief a Mobile Object (MOB) top level definition
 */
typedef struct _mob
{
  void *graphics_addr;        // pointer in ROM to graphics object
  void *status_addr;          // pointer in RAM to status 
  void *old_screen_addr;      // pointer in RAM to old screen data (backing store), bit 15 set = disable
  unsigned char first_gen;    // Index to first generator to use in PATTERN and COLOR tables
} MOB;

 

Which can then be used by ACTIVATE and PUT_OBJ, just like with mobile objects.

 

I have placed an example folder in the os7lib repository called mobile_object, which looks like this:

/**
 * @brief Mobile Object demo (VDP Mode 1)
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see COPYING for details
 */

#include <interrupt.h>
#include <arch/z80.h>
#include <intrinsic.h>
#include <os7.h> 
#include "generators.h" // pac-man's data is here.

/**
 * @brief The data structures for the timer table, to support one timer
 */
TimerTable tt[2];
TimerData td[2];

/**
 * @brief the size of writer queue is queue size / 3, as there are three bytes per entry
 */
#define WRITER_QUEUE_SIZE 16

/**
 * @brief The queue needed to hold 16 deferred VDP write entries
 */
unsigned char writer_queue[WRITER_QUEUE_SIZE*WRITER_QUEUE_ENTRY_SIZE];

/**
 * @brief PATTERN TABLE generators which will hold each frame, and color
 */
const MOBFrame pacmanFrame0 = {0x60,0x61,0x62,0x63,0x20};
const MOBFrame pacmanFrame1 = {0x64,0x65,0x66,0x67,0x20};
const MOBFrame pacmanFrame2 = {0x68,0x69,0x6A,0x6B,0x20};

/**
 * @brief newGen is 9 generator spaces, each holding 8 bytes for temporary transformation storage.
 */
unsigned char pacmanNewGen[NEWGEN_SIZE];

/**
 * @brief Graphics object which points to bitmaps in ROM, and mappings to frames above
 */
MOBGraphics(3) PacmanGraphics;
const PacmanGraphics pacmanGraphics = {1, 12, pacmanNewGen, pacman_right_horizontal, {pacmanFrame0, pacmanFrame1, pacmanFrame2}};

/**
 * @brief Old Screen object (backing store) for the PAC-MAN object
 */
MOBOldScreen pacmanOldScreen;

/**
 * @brief Pac-man's STATUS object which holds position, frame, etc.
 */
MOBStatus pacmanStatus;

/**
 * @brief PAC-Man's Top-level object
 */
const MOB pacmanMOB = {pacmanGraphics,pacmanStatus,pacmanOldScreen,0x60};

/**
 * @brief the routine to use during VDP interrupt, update writer and timer manager
 */
static void vdp_nmi(void)
{
  M_PRESERVE_ALL; // preserve registers, so we don't crash
  writer();       // fire off pending queue entries
  time_mgr();     // decrement all timers
  M_RESTORE_ALL;  // restore registers, so we don't crash
}

/**
 * @brief Initialize timer, writer, and VDP
 */
void init(void)
{
  init_timer(&tt,&td);                          // Set up timer queue
  init_writer(writer_queue,WRITER_QUEUE_SIZE);  // set up writer queue
  add_raster_int(vdp_nmi);                      // attach vdp_nmi() to be called on every VDP interrupt.

  mode_1();                                     // set up VDP mode 1, at this point, display is OFF (blank)
  load_ascii();                                 // put ASCII table in PATTERN GENERATORS
  fill_vram(MODE1_PATTERN_NAME_TABLE,768,' ');  // spaces in name table
  fill_vram(MODE1_PATTERN_COLOR_TABLE,32,0xF4); // Fill color table with white foreground/black background
  write_register(0x01,0xE0);                    // turn on display, mode 1, VDP interrupt.
}

/**
 * @brief Plot sign-on message to VDP using ASCII PATTERNs
 */
void signon_msg(void)
{
  const char sign_on[32]="  MODE 1 MOBILE PATTERN TEST   ";

  put_vram(PATTERN_NAME_TABLE,0x0000,sign_on,32);
}

/**
 * @brief Program starts here
 */
void main(void)
{
  init();
  signon_msg();

  activate(pacmanMOB,false);
  pacmanStatus.x = 0;
  pacmanStatus.y = 0;
  put_obj(pacmanMOB);
  while(1);
}

 

I am still debugging it, but I figured i'd post where I am, because it's the end of the day.

 

 

 

 

Anyone want to chime in and comment where I may be missing something?

 

-Thom 

Edited by tschak909
Link to comment
Share on other sites

I'm currently trying to decipher the official Coleco manual because this is available through the BIOS yet no examples are easy to follow in Assembly.
Even the PDF's that supposedly explain everything is hard on the eye's

So, whatever you can figure out.

I just disassembled Donkey Kong which I can partially figure out how the pattern generators are set up and work.
When I say everything, I mean it, everything in DKong was built with these pattern tables.


Smurf uses them heavily to scroll the backgrounds.

 

I'm now working on Frenzy again which is 99% pattern based and that is why you can only find 2 robots in the entire game.
16x16 robots patterns x 16 directions is a lot of patterns and very little VRAM for backgrounds

 

Somewhere between the two I will figure it out.


I was able to create 4 individual 8x8 patterns that would fit in VRAM, I may be able to have more if I use MODE 1 but at the end of the day it is either a lot of individual little guys running around with no room for backgrounds or a balance.

If you can look around my threads you will find some video work of the patterns running amok.
I did not go through the BIOS and table setup was easier than I thought.  First try kind of deal which blew my mind.
It was getting to the point that I was putting tables picking tables within tables.  Pretty neat when you think about it.

And it all works in real time.

 

Here are the pdf's I have, they are fairly common at this point.

 


03 Cozmos

Research.zip

Edited by Captain Cozmos
  • Like 1
Link to comment
Share on other sites

6 hours ago, Captain Cozmos said:

I'm currently trying to decipher the official Coleco manual because this is available through the BIOS yet no examples are easy to follow in Assembly.
Even the PDF's that supposedly explain everything is hard on the eye's

So, whatever you can figure out.

I just disassembled Donkey Kong which I can partially figure out how the pattern generators are set up and work.
When I say everything, I mean it, everything in DKong was built with these pattern tables.


Smurf uses them heavily to scroll the backgrounds.

 

I'm now working on Frenzy again which is 99% pattern based and that is why you can only find 2 robots in the entire game.
16x16 robots patterns x 16 directions is a lot of patterns and very little VRAM for backgrounds

 

Somewhere between the two I will figure it out.


I was able to create 4 individual 8x8 patterns that would fit in VRAM, I may be able to have more if I use MODE 1 but at the end of the day it is either a lot of individual little guys running around with no room for backgrounds or a balance.

If you can look around my threads you will find some video work of the patterns running amok.
I did not go through the BIOS and table setup was easier than I thought.  First try kind of deal which blew my mind.
It was getting to the point that I was putting tables picking tables within tables.  Pretty neat when you think about it.

And it all works in real time.

 

Here are the pdf's I have, they are fairly common at this point.

 


03 Cozmos

Research.zip 9.81 MB · 1 download

Yup, those are exactly the ones I have.

 

I think what we're doing has the opportunity to converge. :)

 

Overall, I am very impressed with the OS7, and honestly bewildered why even after documentation surfaced, people ignored it.

 

@Captain Cozmos you may also want the OS7 absolute listing (which came straight off the HP 64000 development system), that I helped rescue from a speculator, by orchestrating a group buy. It's now in the Internet Archive, and can be downloaded. @Savetz did the scanning. 


https://archive.org/details/coleco-adam-technical-manual-eos6-os7/

  • Like 1
Link to comment
Share on other sites

Any potential insight here? Mobile objects are acting weird. 

 
/**
 * @brief Mobile Object demo (VDP Mode 1)
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see COPYING for details
 */

#include <interrupt.h>
#include <arch/z80.h>
#include <intrinsic.h>
#include <os7.h> 
#include "generators.h" // pac-man's data is here.

/**
 * @brief The data structures for the timer table, to support one timer
 */
TimerTable tt[2];
TimerData td[2];

/**
 * @brief the size of writer queue is queue size / 3, as there are three bytes per entry
 */
#define WRITER_QUEUE_SIZE 16

/**
 * @brief The queue needed to hold 16 deferred VDP write entries
 */
unsigned char writer_queue[WRITER_QUEUE_SIZE*WRITER_QUEUE_ENTRY_SIZE];

/**
 * @brief PATTERN TABLE generators which will hold each frame, and color
 */
const MOBFrame pacmanFrame0 = {0x00,0x01,0x02,0x03,0xB0};
const MOBFrame pacmanFrame1 = {0x04,0x05,0x06,0x07,0xB0};
const MOBFrame pacmanFrame2 = {0x08,0x09,0x0a,0x0b,0xB0};

/**
 * @brief newGen is 9 generator spaces, each holding 8 bytes for temporary transformation storage.
 */
unsigned char pacmanNewGen[NEWGEN_SIZE];

/**
 * @brief Graphics object which points to bitmaps in ROM, and mappings to frames above
 */
MOBGraphics(3) PacmanGraphics;

const PacmanGraphics pacmanGraphics = {MOBILE, 3, pacmanNewGen, pacman_right_horizontal, {pacmanFrame0, pacmanFrame1, pacmanFrame2}};

/**
 * @brief Old Screen object (backing store) for the PAC-MAN object
 */
MOBOldScreen pacmanOldScreen;

/**
 * @brief Pac-man's STATUS object which holds position, frame, etc.
 */
MOBStatus pacmanStatus;

/**
 * @brief PAC-Man's Top-level object
 */
const MOB pacmanMOB = {pacmanGraphics,pacmanStatus,pacmanOldScreen,0x80};

/**
 * @brief the routine to use during VDP interrupt, update writer and timer manager
 */
static void vdp_nmi(void)
{
  M_PRESERVE_ALL; // preserve registers, so we don't crash
  writer();       // fire off pending queue entries
  time_mgr();     // decrement all timers
  M_RESTORE_ALL;  // restore registers, so we don't crash
}

/**
 * @brief Initialize timer, writer, and VDP
 */
void init(void)
{
  init_timer(&tt,&td);                          // Set up timer queue
  init_writer(writer_queue,WRITER_QUEUE_SIZE);  // set up writer queue
  add_raster_int(vdp_nmi);                      // attach vdp_nmi() to be called on every VDP interrupt.

  mode_1();                                     // set up VDP mode 1, at this point, display is OFF (blank)
  load_ascii();                                 // put ASCII table in PATTERN GENERATORS
  fill_vram(MODE1_PATTERN_NAME_TABLE,768,' ');  // spaces in name table
  fill_vram(MODE1_PATTERN_COLOR_TABLE,32,0xF0); // Fill color table with white foreground/black background
  write_register(0x01,0xE0);                    // turn on display, mode 1, VDP interrupt.
}

/**
 * @brief Plot sign-on message to VDP using ASCII PATTERNs
 */
void signon_msg(void)
{
  const char sign_on[32]="  MODE 1 MOBILE PATTERN TEST   ";

  put_vram(PATTERN_NAME_TABLE,0x0000,sign_on,32);
}

/**
 * @brief Program starts here
 */
void main(void)
{
  init();
  signon_msg();
  pacmanStatus.y=128;
  activate(pacmanMOB,false);
  
  while(1)
    {
      SignalNum wait = request_signal(1,false);
      pacmanStatus.x++;
      pacmanStatus.frame++;
      if (pacmanStatus.x>256)
        pacmanStatus.x=0;
      if (pacmanStatus.frame>2)
        pacmanStatus.frame=0;

      put_obj(pacmanMOB,0);
      while (!test_signal(wait));
    }
}

 

Link to comment
Share on other sites

Using the timers and put_vram facilities in OS7 to implement the Targ bonus screen.

 

 
/**
 * @brief Targ example using os7lib
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see LICENSE for details
 * @verbose Bonus Screen
 */

#include <os7.h>
#include <ctype.h> // for toascii()
#include "colors.h"
#include "bonus.h"
#include "patterns.h"

static const char *_extra_digit[10]=
  {
    // 0
    {
      "  // "
      " /  /"
      " /  /"
      " /  /"
      " /  /"
      "  // "
    },
    
    // 1
    {
      " //  "
      "  /  "
      "  /  "
      "  /  "
      "  /  "
      " /// "
    },

    // 2
    {
      " /// "
      " / / "
      "   / "
      " /// "
      " /   "
      " /// "
    },
    
    // 3
    {
      " /// "
      "   / "
      "  // "
      "   / "
      "   / "
      " /// "
    },
    
    // 4
    {
      "   / "
      " / / "
      " / / "
      " ////"
      "   / "
      "  ///"
    },

    // 5
    {
      " /// "
      " /   "
      " /// "
      "    /"
      "    /"
      " ////"
    },

    // 6
    {
      " /// "
      " /   "
      " /   "
      " ////"
      " /  /"
      " ////"
    },

    // 7
    {
      " ////"
      "    /"
      "   //"
      "  // "
      "  /  "
      "  /  "
    },

    // 8
    {
      "  ///"
      "  / /"
      " ////"
      " /  /"
      " /  /"
      " ////"
    },

    // 9
    {
      " ////"
      " /  /"
      " ////"
      "    /"
      "    /"
      "  ///"
    }    
  };

#define BONUS_FRAME_COUNT 180

#define EXTRA_POS_X 5
#define EXTRA_POS_Y 2
#define EXTRA_NUM_DIGITS 4
#define EXTRA_DIGIT_WIDTH 5
#define EXTRA_DIGIT_HEIGHT 6

#define EXTRA_POINTS_POS 362
#define BONUS_POS 525

#define POINTS_POS 650
#define POINTS_DIGIT_POS POINTS_POS + 2

#define SCREEN_WIDTH 32

static const char *_extra_points = "EXTRA POINTS";
static const char *_bonus = "BONUS";
static const char *_points[12]= "<  0 POINTS";

/**
 * @brief Show BONUS/EXTRA POINTS screen, with requested point values
 * @param extra_digit, large digit to show (0-9)
 * @param bonus_digit, bonus digit to show (0-9)
 */
void bonus(unsigned char extra_digit, unsigned char bonus_digit)
{
  SignalNum wait;
  unsigned char block_pattern[8] =
    {
      0b11111111,
      0b11111111,
      0b11111111,
      0b11111111,
      0b11111111,
      0b11111111,
      0b11111111,
      0b11111111
    };
  
  // clear vram
  fill_vram(0x0000,0x4000,0x00);

  // set up for graphics 1
  mode_1();

  // Load ASCII table into VRAM
  load_ascii();

  // Replace / with block char
  put_vram(PATTERN_GENERATOR_TABLE,'/',block_pattern,1);

  // Replace < with up-facing Targ rider
  put_vram(PATTERN_GENERATOR_TABLE,'<',_targ_up_patterns,1);
  
  // Set color palette
  set_colors();

  // Plot the extra digits i = digit, j = digit row
  for (int i=0;i<EXTRA_NUM_DIGITS;i++)
    {
      unsigned char *p = i==0 ? _extra_digit[extra_digit] : _extra_digit[0];
      
      for (int j=0;j<EXTRA_DIGIT_HEIGHT;j++)
        {
          int pos = ((j+EXTRA_POS_Y)*SCREEN_WIDTH) + (i*EXTRA_DIGIT_WIDTH) + EXTRA_POS_X;
          
          put_vram(PATTERN_NAME_TABLE,pos,p,6);
          p += EXTRA_DIGIT_WIDTH;
        }
    }

  // plot EXTRA POINTS
  put_vram(PATTERN_NAME_TABLE,EXTRA_POINTS_POS,_extra_points,12);

  // plot bonus
  put_vram(PATTERN_NAME_TABLE,BONUS_POS,_bonus,5);
  
  // plot bonus points
  put_vram(PATTERN_NAME_TABLE,POINTS_POS,_points,12);

  // plot bonus digit
  bonus_digit += '0'; // convert to ascii character
  put_vram(PATTERN_NAME_TABLE,POINTS_DIGIT_POS,&bonus_digit,1);
  
  // Turn on display
  write_register(0x01,0xE0);
  write_register(0x07,0x04);

  // RED
  wait = request_signal(30,false);
  
  while (!test_signal(wait))
    {
      unsigned char color = 0x64;

      put_vram(PATTERN_COLOR_TABLE,0x05,&color,1);
    }

  // RED/YELLOW FLASHING
  for (unsigned i=0;i<8;i++)
    {
      unsigned char color = i % 2 ? 0x64 : 0xB4;

      put_vram(PATTERN_COLOR_TABLE,0x05,&color,1);
      wait = request_signal(4,false);

      while (!test_signal(wait));
    }

  // Yellow static
  wait = request_signal(120,false);

  while (!test_signal(wait))
    {
      unsigned char color = 0xB4;
      
      for (unsigned char i=0;i<8;i++)
        block_pattern[i]=rand();

      put_vram(PATTERN_GENERATOR_TABLE,'/',block_pattern,1);
      put_vram(PATTERN_COLOR_TABLE,0x05,&color,1);
    }
}

 

-Thom

Link to comment
Share on other sites

Simplest possible OS7 sprite example. Uses a timer to wait for end of frame. None of the multiplexing features are used here, but even with the most basic implementation, the EARLY CLOCK bit is taken care of for you, so you can simply position where you wish (even off screen), and it will set the EARLY CLOCK bit so that the sprite can move in from the left.

 

Code is here on github:
https://github.com/tschak909/os7lib/tree/main/examples/sprite

 

/**
 * @brief Sprite Object demo (VDP Mode 1)
 * @author Thomas Cherryhomes
 * @email thom dot cherryhomes at gmail dot com
 * @license gpl v. 3, see COPYING for details
 */

#include <interrupt.h>
#include <arch/z80.h>
#include <intrinsic.h>
#include <os7.h>

#define OFF_THE_LEFT -32

/**
 * @brief mode 1 color palette
 * @verbose defined as static const so it stays in ROM, and is only referenced in this file.
 */
static const unsigned char colors[32]=
  {
    0xB4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0x64, // 5 for bonus
    0x64,
    0x64,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4,
    0xF4
  };

/**
 * @brief The data structures for the timer table, to support one timer
 */
TimerTable tt[2];
TimerData td[2];

/**
 * @brief sprite pattern to use
 */
const unsigned char generator[8] =
  {
    0b11101110,
    0b01000100,
    0b11111111,
    0b11111101,
    0b11111101,
    0b11111111,
    0b01000100,
    0b11101110
  };

/**
 * @brief Status object for the sprite
 */
SprStatus status;

/**
 * @brief frame to generator and color mapping for sprite
 */
const SprFrame frame={LIGHT_GREEN,0x00};

/**
 * @brief graphics object mapping generator in rom to pattern table in VRAM
 */
const SprGraphics graphics = {SPRITE,0x00,generator,1,frame};

/**
  * @brief top level object for sprite
  */
const SprObj obj = {graphics,status,0};

/**
 * @brief the routine to use during VDP interrupt, update writer and timer manager
 */
static void vdp_nmi(void)
{
  M_PRESERVE_ALL; // preserve registers, so we don't crash
  time_mgr();     // decrement all timers
  M_RESTORE_ALL;  // restore registers, so we don't crash
}

/**
 * @brief Plot sign-on message to VDP using ASCII PATTERNs
 */
void signon_msg(void)
{
  const char sign_on[32]="  MODE 1 SPRITE PATTERN TEST   ";

  put_vram(PATTERN_NAME_TABLE,0x0000,sign_on,32);
}

void main(void)
{
  init_timer(&tt,&td);                          // Set up timer queue
  add_raster_int(vdp_nmi);                      // attach vdp_nmi() to be called on every VDP interrupt.
  mode_1();                                     // set up VDP mode 1, at this point, display is OFF (blank)
  load_ascii();                                 // put ASCII table in PATTERN GENERATORS
  put_vram(PATTERN_COLOR_TABLE,0,colors,sizeof(colors)); // Place the color palette.
  write_register(0x07,0x04);                    // set border to dark blue.
  signon_msg();                                 // Go ahead and plot sign-on message
  blank(true);
  
  activate(obj,false);

  status.x = OFF_THE_LEFT;
  
  while(1)
    {
      SignalNum wait_frame = request_signal(1,false); // timer that waits for one frame.
      
      status.x+=2;

      if (status.x>288)
        status.x=OFF_THE_LEFT;
      
      put_obj(obj,0);
      
      while (!test_signal(wait_frame)); // wait until end of frame
    }
}

 

-Thom

 

Edited by tschak909
Link to comment
Share on other sites

OS7 contains a call to set up the VDP for Mode 1, setting all the table registers using init table, and then setting VDP register 0 and 1 appropriately.

 

This is ALMOST everything you need for Mode 2.

 

All that is needed is to call write_register to set Register 0 to 2, to flip on the M2 bit. 

 

The call to mode1 using OS7LIB is implemented as:

#include <arch/z80.h>
#include "os7.h"

/**
 * MODE_1 - Set VDP to Graphics I
 * 
 * VDP tables are set to:
 * 
 * Sprite Generator Table  - 0x3800
 * Pattern Color Table     - 0x2000
 * Sprite Attribute Table  - 0x1B00
 * Pattern Name Table      - 0x1800
 * Pattern Generator Table - 0x0000
 */

void mode_1(void)
{
  Z80_registers r;

  AsmCall(MODE_1,&r,REGS_ALL,REGS_ALL); // MODE1 defined as 0x1F85
}

 

So for mode 2, all we need is:

#include "os7.h"

/**
 * MODE_2 - Set VDP to Graphics II
 * 
 * VDP tables are set to:
 * 
 * Sprite Generator Table  - 0x3800
 * Pattern Color Table     - 0x2000
 * Sprite Attribute Table  - 0x1B00
 * Pattern Name Table      - 0x1800
 * Pattern Generator Table - 0x0000
 */

void mode_2(void)
{
  // First call mode_1 to get 99% of it set up.
  mode_1();

  // Then set the OS7 graphics mode byte.
  // This also silently sets the VDP_MODE_WORD byte in CRAM (see OS7PRIME:pOS PAGE 130)
  // So the OS7 table and object routines can adjust calculations, per mode.
  write_register(0x00,2);
}

 

Finally, a note from the OS7_PRIME:pOS absolute listing has this to say about write_register for register values of 0 and 1:

image.thumb.png.7be1d9a5ec8408000e4a00f9f65382d4.png

-Thom

Link to comment
Share on other sites

1 hour ago, tschak909 said:

@Captain Cozmos have you seen any games that actually use the MOBILE object type? I'm trying to find one, and have been coming up empty.

 

-Thom

All the robots in Frenzy move in 8 directions with Animation.  They are all made up of patterns.
 

I am revisiting all my old disassembles right now to identify pattern tables and the like.
Each time I fiddle with these things I get a little better at what I do.

Edited by Captain Cozmos
Link to comment
Share on other sites

@Captain Cozmosfrom what I saw in Frenzy with gearcoleco's VDP debugger, all the patterns are pre-shifted.

 

This is in stark contrast to e.g. the mobile object demo I initially put together, where the mobile object consists of nine contiguous names, and the data is temporarily stored in the newgen area, where it is shifted, and then spit back out to VRAM in the same contiguous 9 tiles.

 

Does anyone else have any insight on this?

 

-Thom

Link to comment
Share on other sites

2 hours ago, Captain Cozmos said:

All the robots in Frenzy move in 8 directions with Animation.  They are all made up of patterns.
 

I am revisiting all my old disassembles right now to identify pattern tables and the like.
Each time I fiddle with these things I get a little better at what I do.

Put what you have on GitHub, so we can start adding to it.

-Thom

Link to comment
Share on other sites

My next post will be the newest Smurf Disassembly.
I now "KNOW"  Not guessing.

The book revealed all, at least the parts I could recover.

Basically, You build a table (frame), that points to smaller tables (frames)

 

 

Smurf works kind of like this.
Here is a screen with objects in it.  Each object is a frame.

So you tell within the screen (frame) where to place those objects.
The objects are mini frames that describe the object.  These are all pointers to the pattern in vram.

All of it is basically tables pointing to tables.

As far as animated frames go (mobile objects)
Each move points to another table which shows the in between pattern.
The neat thing, which I have yet to get to, is that these objects are supposed to be able to or/and if it runs into a background.


OLD_SCREEN is the buffering system that returns that pattern after the mobile object leaves that area.


I did something similar in a game I set aside.  It is basically Doge 'em with missiles.
I wanted two color cars for both players so I made pattern missiles.
The Missiles fly along the track in all 4 directions regardless of dots still being on the track because no one has picked them up yet.

I had to be able to not only restore the dot after a fly over but return a 9 pattern explosion after it hit an object.
Meaning the track and anything present had to be returned after the animation.

The game is done except for opposing car CPU AI.  Two players can play all day.

I'll tell you that it was a bear to figure out and now that I look at the BIOS routines, they could have solved so many issues early on.
I think my routines may be faster but in reliance to T-States it does not matter because the Z80 can handle it.

People have said that they don't like the BIOS but I think it's because they never used them or encountered bugs.
There is a bug, I think, Put_VRAM in Colecovision OS 7 that you can only move 256k to VRAM at a time and was fixed in ADAM EOS.
Clearly a BC issue but that can easily be fixed with your own routine in Cartridge.
 

Edited by Captain Cozmos
Link to comment
Share on other sites

@Captain Cozmos The tables translate pretty well to "C"

/**
 * @brief A Semi-Mobile Object (SMO) top level definition
 */
typedef struct _smo
{
  void *graphics_addr;        // pointer in ROM to graphics object
  void *status_addr;          // pointer in RAM to status 
  void *old_screen_addr;      // pointer in RAM to old screen data (backing store), bit 15 set = disable
} SMO;

/**
 * @brief a SMO GRAPHICS object defining n of frames
 */
#define SMOGraphics(n,FrameObj)                 \
  typedef struct _smo_graphics_n                \
  {                                             \
    unsigned char obj_type;                     \
    unsigned char first_gen_name;               \
    unsigned char numgen;                       \
    void *generators;                           \
    FrameObj *frame[n];                         \
  }

/**
 * @brief the frame data for a given SMO GRAPHICS object
 */
#define SMOFrame(n)                             \
  typedef struct _smo_frame_n                   \
  {                                             \
    unsigned char x_extent;                     \
    unsigned char y_extent;                     \
    unsigned char generator[n];                 \
  }                                             \

  /**
 * @brief the old screen data for a given SMO GRAPHICS object
 */
#define SMOOldScreen(n)                         \
  typedef struct _smo_old_screen_n              \
  {                                             \
    unsigned char x_pat_pos;                    \
    unsigned char y_pat_pos;                    \
    unsigned char x_extent;                     \
    unsigned char y_extent;                     \
    unsigned char generator[n];                 \
  }                                             \

  /**
 * @brief A Semi-Mobile Object (SMO) top level definition
 */
typedef struct _smo
{
  void *graphics_addr;        // pointer in ROM to graphics object
  void *status_addr;          // pointer in RAM to status 
  void *old_screen_addr;      // pointer in RAM to old screen data (backing store), bit 15 set = disable
} SMO;

/**
 * @brief a SEMI-MOBILE STATUS object
 */
typedef struct _smo_status
{
  unsigned char frame;
  int x;
  int y;
  unsigned char next_gen;
} SMOStatus;

 

and in practice, defining the structures:

SMOFrame(2) TargFrame;
SMOOldScreen(2) TargOldScreen;
SMOGraphics(8,TargFrame) TargGraphics;

// Extents must be at least 1, 0 causes a pre-decrement wrap to 255
// The frame objects.
const TargFrame targFrame0 = {1,2,{0x60,0x61}};
const TargFrame targFrame1 = {1,2,{0x62,0x63}};
const TargFrame targFrame2 = {1,2,{0x64,0x65}};
const TargFrame targFrame3 = {1,2,{0x66,0x67}};
const TargFrame targFrame4 = {1,2,{0x68,0x69}};
const TargFrame targFrame5 = {1,2,{0x6a,0x6b}};
const TargFrame targFrame6 = {1,2,{0x6c,0x6d}};
const TargFrame targFrame7 = {1,2,{0x6e,0x6f}};

// The Targ graphic object
const TargGraphics targGraphics=
  {
    0,
    0x60,
    16,
    _targ_down_patterns,
    {targFrame0,targFrame1,targFrame2,targFrame3,targFrame4,targFrame5,targFrame6,targFrame7}
  };

// The 10 targStatus objects (x, y, frame, etc.)
SMOStatus targStatus[10];

// The 10 targOldScreen objects (see above)
TargOldScreen targOldScreen[10];

// The 10 top level SEMI-MOBILE objects
const SMO targSMO[10];

 

Once defined, they can be activated and put_obj'd

/**
 * @brief since SMO is in RAM, we have to initialize it dynamically
 */
void targ_init(void)
{
  for (int i=0;i<10;i++)
    {
      targSMO[i].graphics_addr=&targGraphics;
      targSMO[i].status_addr=&targStatus[i];
      targSMO[i].old_screen_addr=&targOldScreen[i];
    }
}

/**
 * @brief plot 10 targs, and go down the screen
 */
void targ(void)
{
  targ_init();

  // Set initial positions
  for (int i=0;i<10;i++)
    {
      activate(&targSMO[i],false);
      targStatus[i].x=(i*24)+16;
      targStatus[i].y=40;
      targStatus[i].frame=0;
    }

  // The main loop here, update position
  while(true)
    {
      for (int i=0;i<10;i++)
        {
          if (targStatus[i].frame==0x07)
            {
              targStatus[i].frame=0;
              targStatus[i].y+=8;
              if (targStatus[i].y>160)
                return;
            }
          else
            targStatus[i].frame++;

          put_obj(&targSMO[i],0);
        }

    }
}

 

-Thom

Link to comment
Share on other sites

Smurf Rescue (Crow)

 

In Assembly

 

CROW_OBJECT:

           DW CROW_GRAPHICS
           DW $72CB               ; (RAM AREA)
           DB    9                    ; (0-31 SPRITE INDEX)
CROW_GRAPHICS:

           DB    3                    ; (OBJECT TYPE) 3 = Sprites
           DB   0F8h                ; (FIRST_GEN_NAME)
           DW CROW_PATTERN ; (PATTERNS)
           DB    8                    ; (HOW MANY PATTERNS ?*8)
           DW CROW_FRAME   ; (COLOR FOR FRAME)  In this case Frame 0 gets color 1, frame 4 gets color 1.  Both are Black
CROW_FRAME:

           DB 001, 000, 001, 004
CROW_PATTERN:

            DB 000, 000, 000, 128, 192, 248, 254, 126
            DB 063, 003, 031, 060, 004, 000, 000, 000
            DB 000, 000, 001, 001, 003, 031, 127, 254
            DB 252, 248, 254, 015, 002, 000, 000, 000
            DB 000, 000, 000, 000, 000, 000, 014, 062
            DB 127, 227, 223, 188, 132, 000, 000, 000
            DB 000, 000, 000, 000, 000, 000, 000, 096
            DB 240, 252, 255, 126, 060, 028, 012, 004

Edited by Captain Cozmos
Link to comment
Share on other sites

As part of putting together OS7LIB, I am disassembling the OS7 ROM and Donkey Kong, which uses it extensively. This is so I can work inward to fully annotate the structures and put them into context.

 

To help with this, I am using the EXCELLENT Ghidra SRE toolkit. For those who haven't worked with it, it is the premier toolkit for doing in-context software reverse engineering, and in a twist of anticlimactic irony, was developed by the National Security Agency (NSA), so, it's a case of our tax dollars, at work, making something both public and useful.

 

ghidra.thumb.png.cd89fcab9d7b03f0a848668187006746.png

 

@Captain Cozmos if you haven't, I suggest using this tool, as with proper patience, it can produce REALLY concise disassemblies with tons of context.

 

-Thom

Link to comment
Share on other sites

5 hours ago, tschak909 said:

As part of putting together OS7LIB, I am disassembling the OS7 ROM and Donkey Kong, which uses it extensively. This is so I can work inward to fully annotate the structures and put them into context.

 

To help with this, I am using the EXCELLENT Ghidra SRE toolkit. For those who haven't worked with it, it is the premier toolkit for doing in-context software reverse engineering, and in a twist of anticlimactic irony, was developed by the National Security Agency (NSA), so, it's a case of our tax dollars, at work, making something both public and useful.

 

ghidra.thumb.png.cd89fcab9d7b03f0a848668187006746.png

 

@Captain Cozmos if you haven't, I suggest using this tool, as with proper patience, it can produce REALLY concise disassemblies with tons of context.

 

-Thom

I've tried using it, it is a pain in the a** compared to more popular methods.

What should be a simple operation ends up being a lot of work just for a 32k binary.

Regardless of that part.  You still have to link all the data and jump tables by hand so why complicate both sides of the equation.
Now if you have a disassembler that will do 100% everything then I am all in.

Edited by Captain Cozmos
Link to comment
Share on other sites

33 minutes ago, Captain Cozmos said:

I've tried using it, it is a pain in the a** compared to more popular methods.

What should be a simple operation ends up being a lot of work just for a 32k binary.

Regardless of that part.  You still have to link all the data and jump tables by hand so why complicate both sides of the equation.
Now if you have a disassembler that will do 100% everything then I am all in.

I have both OS7 and the requisite ROM in the same memory map in Ghidra, and have been annotating everything at a feverish pace. 

 

I've done a LOT of SRE, and nothing comes close to Ghidra, not even IDA. The patience given to it pays off in orders of magnitude in the quality of what you get back. (e.g. I used it to reverse engineer Apple's Airplay code).

 

-Thom

Link to comment
Share on other sites

My argument is that if I wanted to disassemble windows program or hack into a foreign Embassy I would use Ghidra.

It's a Colecovision and I should not have to use Java for anything.

Why people just can't use the Windows API for everything is a mystery to me.

 

I had that same argument when Magic UI came out.

The Amiga had easy to create windows and radio buttons even in assembly language.  From WB1.3 to 3.9 as long as you had the libraries.

 

I also think the NSA claim is overrated but it does help to give it publicity.



edit....
Out of ****s and giggles I went back to Ghidra to see if there was anything beneficial that my current disassembler misses.
All I will say is that I'm glad you are happy with it.
Some people swear by Vegemite while I find it nasty tasting.

For me, it is too cumbersome for the task at hand and not very intuitive.  It also does not provide any new analysis for Z80 processors that my current rig.
You still have to edit tables by hand and for Ghidra that seems to be a navigational pain.

If you have a way that it automates finding tables and deciphers JP (IX) and (HL) off the stack or some of the more advanced work then I would love to know the settings and I'll use it just to find my bearing.

 

I did not mean for the discussion to turn into a debate on software but this is how collaboration works.
You bring something to the table and so do I.

Edited by Captain Cozmos
  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...