D-Bug ULSv3 (c) 2005-2009 D-Bug


1.1 What is ULS?
1.2 System Requirements

Understanding ULS

2.1 Concept
2.2 ULSv3 Functions
2.3 Assembly equates
2.4 Patching into a game

Trouble Shooting

3.1 Troubleshooting crashes & errors
3.2 Known problems
3.3 Revision List

1.1 What is ULS?

U.L.S. (Universal Loading System) is a means by which Atari ST applications can request disk access, or system function calls when TOS is not available, as is the case with most games and demos, and therefor run from any media (Including Hard Disks). Late in 2005 D-Bug released the first few ULS patched games, and then the system went into a bit of a slumber as we continued with Falcon patching. In August 2008, the ULS code was completely re-written from the ground up, adding new functionality, using less memory, running quicker, with more stability and making it much easier to implement.

It is hoped that the release of this code will kick-start the ST hard disk adaption scene, in much the same way WHDLoad has done for the Amiga. Indeed, ULS can perform many of the functions of WHDLoad, and offer serveral exciting new ones that were previously only seem in emulators!

1.2 System Requirements

The basic system requirements are:

An Atari of any type (ST/STF/STFm/STe/Mega/Mega2/MSTe/F030/TT)
An ST disk media of any type (ACSI, SCSI, IDE, Floppy....)
A minimum of 1mb of memory (some titles will require more)

2.1 Concept

The basic concept of ULS is "Let GEMDOS do it all". At the start of the stubloader a "snapshot" of the lower chunk of memory is taken along with several system variables and hardware registers. ULS is then installed which then takes care of all disk access from that point on using standard GEMDOS commands to replace the low level access in the application. Hard disk access is performed via the installed driver, making ULS work with multiple drivers without requiring repartitioning or device setup.

2.2 ULSv3 Functions

There are 16 basic function calls in ULSv3. These are accessed by passing parameters to them via registers (or, in some cases, using the filebuffer) and calling the function as an offset from the install address, much the same way as SNDH music is called.

The functions are as follows:


Function: uls_setup

Call with: Returns:
D0.L: 16 Mhz Request A0.L ULS JMP Table address
D1.L: Cache Request A1.L ULS Filebuffer address
D6.L: 4 char ASCII of initial dir for RAMdisk A2.L Address stubloader ram from
D7.L: AutoMagic Ramdisk Flag A3.L ULS Low memory copy address
A0.L: RAMTop for ULS D0.L=Machine type
A5.L: Pointer to a filespec for Ramdisk D1.L=!HD! flag
A6.L: Size of Ramdisk D2.W=VGA (1) / RGB (2)

This function initialises the ULSv3 system. It takes a "snapshot" of the current state of the enviromnet, and returns parameters required for the stubloader to call other ULS functions. The call will return with the system in 320x200x4bpp ST-Low resolution. If run on a Falcon it will detect RGB/VGA and automatically set the VIDEL accordingly, and also put the machine in STE Bus Emulation Mode. On 68030 based systems (F30/TT) the PMMU table is moved to high memory so that all low RAM can be accessed without errors.

Call A0.L: RAMtop for ULS

Unlike most applications, ULS will allocate its memory downwards from the top end of RAM. This is because nearly all games and demos that trash TOS will occupy the lower 512-1024k of memory, and we don't want any of our code or workspace to get over written. The easiest way to call uls is to load A0 with RAMtop from $42e.w, making sure your code (and the stack) are well clear of anything it will wipe over.

Call D0.L: 16Mhz Request

Set D0.L to $00000001 and ULS_init will return with the system in 16Mhz if available (MSTe/F030)
Set D0.L to $FFFFFFFF and ULS_init will return in 8Mhz

Call D1.L: Cache Request

Set D1.L to $00000001 and ULS_init will return with the system cache on if available (MSTe/F030/TT)
Set D1.L to $FFFFFFFF and ULS_init will return with the system cache disabled

Call D6.L: 4 char ASCII of RAMdisk folder

This defaults to #"DATA" but was added in order to support multiple data folders.

Call D7.L: AutoMagic Ramdisk Flag

Set D1.L to #'RAMD' and ULS_init will take the following two parameters and create and fill a ramdisk of all the files requested
Set D1.L to anything else to disable this feature.

Call A6.L: Size of Ramdisk

Set A6.L to the size (in bytes) required to cache all the files that the filespec (below) will match.
This value must be: Size in bytes of all files+(20*(number of files+1))

Call A5.L: Pointer to a filespec for Ramdisk

Set A5.L to the address of a filespec to use to build the ramdisk, eg, "*.*" for all files or "*." for all files without extenders.
You do not need to specify the "DATA\" in this filespec.

Returns A0.L: Pointer to ULS JMP Table

This is the base address for all the ULS function calls. They are accessed by loading this into an address register and then calling the function via an offset, eg

move.l uls_base(pc),a6
jsr uls_setread(a6)

Returns A1.L: Pointer to ULS Filebuffer

This is the address that uls_file_io will load all data through, before sending it either to disk (write) or back to the application (read). It is also where you must leave a register dump before calling uls_statesave.

Returns A2.L: Load address of stubloader / A3.L ULS low memory copy address

These values are returned so that you can "work around" any segmented memory issues while code called via uls_execute is running (more info in that function description)

Returns D0.L: Machine Type

The value is one of: 0 (ST), 1 (STE), 2 (MSTE), 3 (F30) or 4 (TT)

Returns D1.L: Hard Disk flag

Returns D1=#"!HD!" if the code is not being executed from A:\ or B:\

Returns D2.W: VGA Detect Flag (Falcon)

Returns (1) if VGA or (2) if RGB on a Falcon

Function: uls_setread

Call this function to put the uls_file_io function into READ mode

Function: uls_setwrite

Call this function to put the uls_file_io function into WRITE mode

Function: uls_file_io

This is the main function call that will handle all your disk IO, it accepts and returns parameters as follows:

Call with:Returns:
A0.L: Pointer to filenameD0.L Unpacked length of file, or error
A0.L: Load addressD1.L Actual length of file
D0.L: Bytes to IO  
D1.L: Seek offset into file (read only)  
D7.L: IO Data Transfer Mode  

Note: If the file requested is in the Ramdisk then ULS will not be called at all, and the file will be byte copied to the destination address. The ramdisk routines will work with the offset and bytes to IO values as per the normal call. You cannot write to the ramdisk. If the file being written was stored in the ramdisk, then it will be devalidated from the ramdisk index and any further read request for that file will result in it being read from disk.

Call A0.L: Pointer to filename

The filename can be in the format "filename.ext" or "filenameext" with or without the "." - if the "." is omitted, then the filename must be space padded (ie, in directory format) Filenames must always be null terminated (ie, #0 at the end)

Call A1.L: Load address

Quite simply, the address you want the data loaded to, or written from.

Call D0.L: bytes to IO

The ammount of bytes you want to read or write. Setting D0=$FFFFFFFF will load the entire file in READ MODE, and set it to write the number of bytes in the already existing file if in WRITE MODE

Call D1.L: Seek offset into file (Must be 0 if using file overwrite mode)

READ MODE: Set to 0 to start reading from the beginning of the file, or the offset into the file to start reading from.
WRITE MODE: If using file overwrite this must be set to 0, otherwise set to 0 to write bytes from the start of the file, or set to the offset into the file to start writing from.

Call D7.L: IO Data Transfer Mode

READ MODE: Set to 0 to load the file as normal, or to -1 to load it only to the ULS filebuffer.
WRITE MODE: Set to -1 to create a new file (or replace existing file), or set to anthing else to overwrite file (if size=$ffffffff then bytes written will be same as file size)

Returns D0.L: Packed File Length (or error)

If the file loaded is packed using a known packer, then D0 will return the length of the UNPACKED file. If it is unpacked, or an unknown packer, it will return the actual file length. If there was a file error (file not found) then D0 will return $ffffffff.

Returns D0.L: Actual File Length

This will return the actual length of the file loaded.

Function: uls_terminate

This function will return the system back to the state it was in before the stubloader was called. All memory, hardware registers and system vectors will be restored.

Call with:
D0.L: Set to #'RTS!' to not exit back to OS.
A0.L: Set to the address to JMP to on system restore.

Note: If D0.L<>#'RTS!' the call will restore the system and terminate back to the desktop. If the return address is specified (A0.L) and the flag is set (D0.L) then a JUMP will be performed. The system will be in the same state as the entry point of uls_init - It is your responsibility to make sure the stack and anything else is set up correctly. ULS will most likely still be sitting in high memory, but if you wish to call any of its functions again you should execute another uls_init call first.

Function: uls_dumpscreen

This function will save the current screenbuffer as a PI1 (Degas Elite Uncompressed) file. The file will be written to the same directory the stubloader was launched from with the name ULS_0000.PI1 - the number will increment (in hex) each time the function is called. If the application was launched from drive A or drive B (ie, floppy) then the function returns without doing anything.

Function: uls_execute

Call with:  
A0.L: Address of code to call under TOS  
This will let you execute your own subroutine (which can access any system trap, etc as normal) whilst the application is running. The code has to be RTS terminated, and you must be aware of the memory segmentation that the system will be in. During ULS execution, the lower part of memory is "mirrored" into high memory, the top address of which is returned from the uls_init function as A2.L (see above). Memory from $8 up to this address will be located at the address specified by the A3.L address returned by uls_init.

Function: uls_statesave

Note: The following is still true, but you will be taken to the State Management screen first to select a state-save slot, which will alter the filename accordingly.

Call with:  
D0.L Ramtop for memory dump  
D1.L JMP address upon resume  
ULS Filebuffer preloaded with a register dump  

Call D0.L: Ramtop for memory dump

The end of memory required to be dumped by the application. For most games this would be $80000 (512k) but in some cases it might be more.

Call D1.L: Resume JMP address

The address that uls_stateload will jmp to upon resuming the memory snapshot.

Call uls_filebuffer Register Dump

In order for statesave/load to work, the registers have to be restored along with the memory and hardware settings. These need to be dumped into the uls_filebuffer when uls_statesave is called. Please note: the stack pointer (a7) MUST be correct at register dumptime for resuming to work, and the stack MUST be within the memory range to be dumped. If the application was launched from drive A or drive B (ie, floppy) then the function returns without doing anything. The following code illustrates the uls_filebuffer register dump format required. The file "DBUGULSV.III" will be created in the stubloader directory.

move.l a0,-(a7) ; save a0
lea uls_fb(pc),a0 ; get address of uls_filebuffer
move.l (a0),a0 ; put it in a0
add.l #4,a7 ; correct the stack for reg-dump (move.l a0,-(a7) changed it)
movem.l d0-7/a1-7,(a0) ; dump all registers except a0
sub.l #4,a7 ; correct a7
lea 60(a0),a0 ; skip the registers we just dumped
move.w sr,(a0)+ ; store the sr
move.l (a7),(a0)+ ; store a0
move.l (a7)+,a0 ; restore a0

Once the state file is saved, program execution will continue as normal.

Function: uls_stateload

Note: The following is still true, but you will be taken to the State Management screen first to select a state-load slot, which will alter the filename accordingly.

Call this function to resume from the saved state. If no state is found on the disk, then normal execution continues. If the application was launched from drive A or drive B (ie, floppy) then the function returns without doing anything.

Function: uls_setpath

Sets the name of the subfolder that will be used for file access by uls_file_io.

Call with:
D0.L: 4-Char ASCII of new folder name

If you wish to have multiple folders because, for example, a file with the same name exists on a different disk, but has different content, you can use this function to get ULS to use another directory for its data files. This can also be used in conjunction with uls_update_rdsk to drastically reduce the memory footprint of a game, by allocating the ramdisk to contain only a single disk of a multi-disk game, and re-populating it on a "disk swap" request.

Function: uls_req_rd_addr

Returns the address of a file's table header in the RAMdisk.

Call with:Returns:
A0.L: Pointer to filenameA0.L Address of file's header in RAMdisk

Call A0.L: Pointer to filename

This is the same format as used for uls_file_io

Returns A0.L: Pointer to file's header in RAMdisk.

The structure of the header is as follows:

Bytes 00-11: Filename (8) + '.' + extender (3)
Bytes 12-15: File length
Bytes 16-19: Folder name the file is in (eg, #'DATA')
Bytes 20-++: File data

Function: uls_update_rdsk

Allows the ULS ramdisk to be updated (append mode) or repopulated (replace mode)

Call with:
D0.L: 4 char ASCII of initial dir for RAMdisk
D1.W: Update Mode Flag
A0.L: Pointer to a filespec for Ramdisk

Call D0.L/A0.L

D0.L and A0.L are the same as required by D6.L and A5.L respectivly for the uls_init function

Call D1.W: Update Mode Flag

Set D1=-1 (negative) and the existing RAMdisk will be completely replaced with the new data.
Set D1=+1 (positive) and the existing RAMdisk will be appended. This is requires for multi directory games if you wish to cache both directories at setup time, as only one path can be specified in uls_init for ramdisk creation.

Note: The updated RAMdisk *MUST* fit inside the parameters specified in uls_init.

Function: uls_F30set200

Sets the screen to 320x200x16 colours on a Falcon - autodetects VGA/RGB

Function: uls_F30set240

Sets the screen to 320x240x16 colours on a Falcon - autodetects VGA/RGB

Function: uls_F30set200TC

Sets the screen to 320x200x65536 colours on a Falcon - autodetects VGA/RGB

Function: uls_search

Allows memory search/replace calls to be made easily. Handy for patching.

Call with:
A0.L: Address to start searching
A1.L: Address to end searching
A2.L: Pointer to bytes to match
A3.L: Pointer to bytes to overwrite a match with
D0.L: Length of search string
D1.L: Length of replace string

No description really necessary for this one, again if you need it explained you shouldn't be reading this ;-)

2.3 Assembly equates

There are two equates that ULS requires to be in the source file for the stubloader, these are "max_filesize" and "adv_debug" and are described below.


This must be set to the size of the biggest file you will want to either read or write via ULS


Define this to enable the debugging screen in ULS, disable this for the final loader.

The debug screen looks like this:

and will be displayed each time ULS is called. The ULS Execution line at the bottom updates as ULS execution flow progresses, and is very useful for tracing hang faults in loaders. If you think you have fixed something, please reprot it to us on the ULS forum on our website!

2.4 Patching into a game

The best place to patch in the ULS loader is in the DMA-File loading routine that the original cracker put in the title. Most of them will have something like

original code:

lea filename,a0
lea load_address,a1
movem.l a0-a1,-(a7)
movem.l (a7)+,a0-a1

for ULS this should be patched to:

lea filename,a0
lea load_Addres,a1
movem.l a0-a1,-(a7)
jsr ULS_Handler
movem.l (a7)+,a0-a1

As can be seen, all you are doing is replacing the load routine with the ULS handler. As far as the title is concerned, its still loading as normal and then depacking as normal.

You will also have to patch the ULS handler so it can tell if a read/write is requested, or a header only read is requested (For example, Barbarian II's loader sets $31.b to -1 if a file is to be written, else its a read, and F1GP's loader sets $96.w to $5555 for a header only request)

You may also have to patch the exit routine with a value to confirm the load was ok. (For example, Leavin' Teramis required $3000.w to be cleared when load was completed or it assumed there was a disk error)

At the bottom of this page you will find the source code (DevPac2 format) for some of the loaders we have released.

3.1 Troubleshooting crashes & errors

The first thing to do here is define the "adv_debug" label in the source and run the loader again. This will give you an idea of what is going on when ULS is called. You can see the registers, and also get the base addresses for the ULS code, your stubloader, and the segmented RAM. You might have to disable the Ramdisk if you set it up for this, as the debug screen is not called for Ramdisk functions. Secondly, we strongly recommend you use Steem Debug's Boiler room, put a breakpoint at your ULS_Handler address, and trace through your code until you see what the issue is. Happy Hunting!

3.2 Known problems

There is an issue where the ULS_core will "hang" in "SAVING GEM MFP (TIM)" in some situations (eg, Robocop III) - we are investigating this and hope to have a fix in the future. Currently this has only occured in this one title (and then only on the Falcon).

3.3 Revision List

For v3.1

*Added State Management Menu (10 states/comments)
*Added Machine and HD flag return on uls_init
*Removed "above 1mb transfer address check" in uls_file_io (Now caller's responsibility to check, if required)
*Fixed issues with savestates not saving correctly (Made block_move 100% accurate)
*Added partial cross-host state compatibility

For v3.11cf (22/01/2009)

* Ramdisk bug fixed
* Init returns VGA/RGB status (If Falcon)
* Cache can now be on (F30/TT)
* CPU in 16Mhz+Cache mode (if possible) during ULS execution (Faster swaps)
* Fixed a non critical bug in the ramdisk handler that could cause a ramdisk-fail and invoke ULS even tho the file was there.

For v3.12 (27/01/2009)

* Fixed crashing if RAMdisk set to off on F30/TT (Hurrah!)
* Added 3 new function calls (uls_F30_set2xx)

For v3.12f (28/01/2009)

* Added multiple DATA folder support and RAMdisk updates (uls_update_rdsk)
* Added file replace mode to uls_file_io when length=$ffffffff
* Added file create (F_CREATE) / overwrite (F_OPEN) code to uls_file_io

For v3.12g (06/02/2009)

* Bugfixed MSTE cache init problem (Thanks Shw!)

For v3.12h (27/02/2009)

* Added the uls_search function

For v3.12i (16/03/2009)

* Fixed bug in copy_file_down where it was copying the unpacked length to memory instead of the packed length.

For v3.12j (17/03/2009)

* Fixed RAMdisk handler whereby odd length files were returning a rounded up even file length if read from RAMdisk.
* Added "return code" to uls_terminate.

ULSv3.12j Source Code

Microprose F1GP Loader Source (ULS v3.0 - Requires modification for current ULS build)

Alcatraz Loader Source (ULS v3.12h+)

Elite Loader Source (ULS v3.11 - Requires modification for current ULS build)

Universal Loading System v3 (c) 2005-2009 D-Bug. All Rights Reserved.
This software is released as Open Source and may be used, viewed or modified by anyone.
D-Bug accept no responsibility for any loss of data or damage to your equipment from using this code or any of our patches
(Although, of course, we don't expect there ever to be any!)