World CDEV
Volume Number: | | 5
|
Issue Number: | | 9
|
Column Tag: | | System Sleuthing
|
Related Info: Script Manager
World CDEV Investigated
By Martin Minow, Mike Carleton, Arlington, MA
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
Sleuthing the Map Cdev
[Martin Minow is a Principal Engineer at Digital Equipment Corporation. Originally a speech major (later a linguist), he decided he might need a real job someday, so he took the last programming course given for the Illiac I computer in 1962. When his graduate study grant ran out, he joined Digital Equipment Corporation in 1972, where he has held positions in software support and speech research and development, eventually working on the DECtalk speech synthesizer. He is currently a Principal Engineer in the mid-range systems advanced development group. In his spare time, he runs marathons (slowly), and orienteers (even more slowly).
Mike Carleton received his BSEE in 1981. He first worked on special purpose compiler systems used to test the black boxes in fighter aircraft. After joining Digital Equipment Corporation in 1984 he supported the TOPS-10/20 COBOL compiler. Later he joined up as the system engineer for the VAX Supercomputer Gateway product and worked hand in hand with Cray Research engineers to make their two systems talk to each other. Currently, he is a Senior Software Engineer at Digital Equipment Corporation.]
System 6.0 includes an unexpected gimmick--a control panel program that displays a world map and allows you to select your location and time zone. The system is distributed with about 80 large cities already defined, and it is very easy to add your own location. However, there doesnt appear to be any published way to extract the information for your own applications.
With a little poking around, we were able to decipher the internal information and think that other MacTutor readers might find it useful. By using the code in the following program, your phase of the moon program (or program to determine sunrise and sunset times) need not ask the user to specify the systems location.
The program has only been tested on Mac SEs and Mac IIs under System 6.0. At its heart is a subroutine to read data from the clock parameter ram.
The data is returned in three longwords with the following format (the actual numbers locate a suburb of Boston) found in figure 1.
Figure 1.
Latitude and longitude are stored as a fraction of the circle. You can think of latitude and longitude either as signed quantities ranging from -.5 to +.5, or as unsigned quantities ranging from 0 to 1. The right method depends on your programs needs. For example, since people dont think of Boston (71°05' West) as 288°55' East, a program that displays the location would probably treat the numbers as signed, as shown in the example program.
Time is stored as the number of seconds offset from UTC (GMT) in the low three bytes of the third longword. The high byte is zero. When the 24-bit value is sign-extended, it will be positive for offsets East of UTC, and negative for Western offsets.
Sleuthing the Parameter RAM
The parameter RAM is a block of memory in the clock chip that is kept alive by a battery when the computer is turned off. It was included in the Macintosh to store a small amount of environmental information that should not change when the system is booted from a different floppy. The MAP CDEV data is stored here because Apple wanted your Mac to know that it is located in Boston even if it was booted from a disk created in Cupertino.
The classic Macintosh has 20 bytes of parameter memory. The contents are copied to low memory when the system is booted and you should access it by referencing the SysParam block as discussed in Inside Macintosh, volume II, chapter 13. When Apple introduced the Mac SE and Mac II, they used a clock chip with 256 bytes of memory. Since only the 20 bytes of the classic Mac are copied to system memory, we had to find a way to access the rest of the information.
The read_param_ram() function below is the C-language interface to the undocumented _ReadXPRam trap that we found tucked away in the 256 KByte ROM. This routine can read any part (or all) of the 256 byte parameter RAM. It takes three parameters: a pointer to a buffer, an offset from the start of the parameter ram, and a count of bytes to copy to your programs buffer. It returns an OSErr code which read_param_ram() then returns to your program.
Finally, we would remind you that you shouldnt assume that Apple will keep this information in the same place or format in future system releases.
In a recently-published document, Apple described ScriptManager 2.0 which contains support routines to allow reading and writing the Map information. As we show in the article, the information is stored in three longwords (Fract format) with the following format:
/* 1 */
struct MachineLocation {
Fract latitude;
Fract longitude;
union {
char dlsDelta;
/* Signed byte: daylight savings time delta */
long gmtDelta;
} gmtFlags;
};
The ScriptManager defines two functions to read and write the Map data:
pascal void ReadLocation(MachineLocation *loc);
pascal void WriteLocation(const MachineLocation *loc);
(const is a keyword that was added to the Draft Ansi C Standard: it indicates that the parameter will not be changed by the function. You can omit it without harm.)
Latitude and longitude are stored as fractions of a great circle: see our code for details. The low-order three bytes of the third word (gmtFlags.gmtDelta & 0x00FFFFFF) contain the number of seconds the timezone is East of the prime meridian. Note that the high byte must be masked off and the high-bit propogated to get the timezone offset: this is shown in our program. The high byte is marked reserved but seems to be a repository for a Daylight Savings Time offset.
I dont know when the ReadLocation (and WriteLocation) functions will be available: they are not present in the current release of Think C. Until that time, our sample program should be usable, but the cautious program will switch to an approved interface as soon as possible.
/*
* This is a simple program to demonstrate
* extracting information from the Macintosh
* parameter RAM and displaying the current
* latitude, longitude, and time-zone offset.
* Note that the format and manner of storage
* of this data have not been published by
* Apple.
* This program is copyright © 1988, 1989
* by MacTutor magazine. You may incorpor-
* ate portions of this program in your
* applications without restriction.
* Compile using Think C, V3.0.
*
* Written by Martin Minow and Mike Carleton
*/
#include<stdio.h>
/*
* Define the Read parameter ram trap and
* the offset in the parameter ram where the
* map is stored. This information doesnt
* seem to be written down anywhere.
*/
#define _ReadXPRam 0xA051
#define Map_Offset 0xE4 /* Ram offset*/
/*
* This structure defines the location
* currently set by the Map Cdev.
* Latitude and longitude are long integers
* specifying the fraction of the circle:
* 1 degree East == 0x000308B9
* 1 degree West == 0xFFFCF747
* All values are possible for longitude,
* while latitude is constrained to ± 90°.
* Only the low-order three bytes of timezone
* are used. The value is the number of
* seconds offset from UTC, East is positive,
* West negative.
*/
#ifdef Use_Script_Manager
#include <ScriptMgr.h>
typedef struct {
Fract latitude;
Fract longitude;
union {
char dlsDelta;
long gmtDelta;
} gmtFlags;
} MachineLocation;
MachineLocation info;
pascal void ReadLocation(MachineLocation *);
#else
typedef struct {
long latitude;
long longitude;
long timezone;
} MapDatum;
MapDatuminfo;
#endif
void main(void);
void dms(long, char *, char *);
void hms(long, char *);
OSErr read_param_ram(void *, int, int);
void printf(char *, ...);
intfgetc(FILE *);
void
main()
{
OSErr status;
/*
* This is a test for divide rounding.
* It should yield 180°0000" W.
*/
dms(0x80000000L, Test, EW);
/*
* Loop through here until the user types q -- this lets you run the
Map Cdev at the same time to see how location and timezone changes affect
the data.
*/
do {
#ifdef Use_Script_Manager
ReadLocation(&info);
dms(info.latitude, Latitude, NS);
dms(info.longitude,Longitude, EW);
hms(info.gmtFlags.gmtDelta, Zone);
#else
status = read_param_ram(&info,
Map_Offset, sizeof (MapDatum));
if (status != noErr)
printf(Error %d\n, status);
dms(info.latitude, Latitude, NS);
dms(info.longitude,Longitude, EW);
hms(info.timezone, Zone);
#endif
} while (getchar() != q);
}
/*
* Convert to degrees/minutes/seconds.
*/
void
dms(raw_value, what, zone)
long raw_value;
char *what;
char *zone;
{
register long value;
register long degree, minute, second;
int west;
static longone_degree = (0x80000000L / 180L);
static longone_minute = (0x80000000L / (180L * 60L));
static longone_second = (0x80000000L / (180L * 60L * 60L));
value = raw_value;
degree = value / one_degree;
value -= (degree * one_degree);
minute = value / one_minute;
value -= (minute * one_minute);
second = value / one_second;
if ((west = (raw_value < 0))) {
degree = (-degree);
minute = (-minute);
second = (-second);
}
printf(%08lx: %3ld°%02ld%02ld\ %c %s\n,
raw_value, degree, minute, second, zone[west], what);
}
/*
* Convert time to hours:minutes:seconds.
*/
void
hms(timezone, what)
long timezone;
char *what;
{
register long value;
register long hour, minute, second;
int west;
static longone_hour = (60L * 60L);
static longone_minute = (60L);
if (timezone & 0xFF000000) {
printf(timezone high byte %08lx\n,
timezone);
timezone &= 0x00FFFFFF;
}
/*
* Propogate sign bit from bit 23 to bit 31 if West of UTC.
*/
if ((timezone & 0x00800000) != 0)
timezone |= 0xFF000000;
value = timezone;
hour = value / one_hour;
value -= (hour * one_hour);
minute = value / one_minute;
value -= (minute * one_minute);
second = value;
if ((west = (timezone < 0))) {
hour = (-hour);
minute = (-minute);
second = (-second);
}
printf(%08lx: %3ld.%02ld.%02ld %c %s\n,
timezone, hour, minute, second, EW[west], what);
}
/*
* Read data from the clock parameter ram. The start parameter specifies
the offset within the parameter ram where the read is to start. The count
parameter specifies the number of bytes to read.
*/
OSErr
read_param_ram(address, start, count)
void *address;/* Result loc*/
intstart; /* Ram start */
intcount; /* Read size */
{
/*
* Put the count into the high word of D0, the pRRAM start address into
the low word of D0, the Macintosh memory address in A0, and trap to ROM.
_ReadXPRam returns noErr on success and prInitErr on failure.
*/
asm {
move.w count,D0
swap D0
move.w start,D0
movea.laddress,A0
dc.w _ReadXPRam
return
}
}