Introduction 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: uls_setup uls_terminate uls_dumpscreen uls_setread uls_setwrite uls_file_io uls_statesave uls_stateload uls_execute uls_setpath uls_req_rd_addr uls_update_rdsk uls_F30set200 uls_F30set240 uls_F30set200TC uls_search 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 filename D0.L Unpacked length of file, or error A0.L: Load address D1.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 filename A0.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. max_filesize This must be set to the size of the biggest file you will want to either read or write via ULS adv_debug 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) jsr DMA_FILELOADER movem.l (a7)+,a0-a1 jsr DEPACK_ROUTINE rts 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 jsr DEPACK_ROUTINE rts 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.