- Group:
- Member: Members
- Active Posts:
- 81 (0.06 per day)
- Most Active In:
- Engineering & Reverse Engineering (41 posts)
- Joined:
- 08-February 12
- Profile Views:
- 6732
- Last Active:
Yesterday, 11:55 PM- Currently:
- Offline
My Information
- Age:
- 17 years old
- Birthday:
- October 14, 1997
- Gender:
-
Female
- Location:
- 0x00
- Interests:
- Hacking
Contact Information
- E-mail:
- Private
Previous Fields
- Project:
- SoniPlane
- National Flag:
- fi
- SA2 Emblems:
- 1
Latest Visitors
-
Josh 
23 Jul 2015 - 03:57 -
NeoFusionBox 
22 Jul 2015 - 16:01 -
JorgeBunny 
10 Jul 2015 - 15:09 -
Bakayote 
29 Jun 2015 - 15:27 -
VAdaPEGA 
26 Jun 2015 - 04:20 -
495557939 
21 Jun 2015 - 02:23 -
Felik 
11 May 2015 - 06:07 -
chacanger 
05 May 2015 - 13:13 -
HackGame 
26 Apr 2015 - 16:59 -
Ezequiel.M 
24 Apr 2015 - 21:34
Topics I've Started
-
[S1] Considerably speeding up level loading
10 November 2014 - 11:02 AM
So, the other day as I was browsing through my old projects, I found a project that I had been working on but never completed, and since I came up with ways to considerably speed up level loading, I thought I could share with you. Why you'd want to use this? Well, I don't... Gotta go fast, right??!!
Bear in mind I will use Sonic 1 HiveBrain disassembly, so for SVN/Hg/Git, reference only. I am unsure whether or not will it work with Sonic 2 or Sonic 3/Knuckles/3 and Knuckles. Most of these are compatible with each other and can be applied fairly easily, however if its not the case, and is known, will be stated in the description
Method 1: Title card art optimization
SpoilerExtra ROM usage: 2546 (0x09F2) bytes
Extra RAM usage: 0
Optimization level: Great
So, one very long process the game does each time you load up level, is load title card art. This is nemesis compressed tiles, and are compressed without interruptions. If you were playing music before this, it would freeze, which is clearly notable in Selbi's Sonic Erazor hack. As you can hear, it takes actually pretty long time, and for not very huge save of ROM space. If we uncompress the tiles, it will be able to load in only few frames, meaning the music will not be interrupted much at all. So, it obviously is quite a good speed up considering the space usage wont be much bigger, it's good tradeoff for the speed. You will need uncompressed tile-loading code from the misc section.
You want to go to each instance of this code:
move.l #$70000002,($C00004).l lea (Nem_TitleCard).l,a0 ; load title card patterns bsr.w NemDec
And, you want to replace it with:
move.l #$70000002,($C00004) ; set mode "VRAM Write to $B000" lea Nem_TitleCard,a0 ; load title card patterns move.l #((Nem_TitleCard_End-Nem_TitleCard)/32)-1,d0; the title card art lenght, in tiles jsr LoadUncArt ; load uncompressed art
You can find there ins lables loc_37B6:, loc_47D4:, and Cont_ClrObjRam:. Next, decompress artnem/ttlcards.bin. You can change the filepath of this file, rename, etc., it's up to you. However in this example I am going to keep it as is. Now, go to lable Nem_TitleCard:, and you should see something like this:
; --------------------------------------------------------------------------- ; Compressed graphics - various ; --------------------------------------------------------------------------- Nem_TitleCard: incbin artnem\ttlcards.bin ; title cards even
You want to change it to this:
; --------------------------------------------------------------------------- ; Compressed graphics - various ; --------------------------------------------------------------------------- Nem_TitleCard: incbin artnem\ttlcards.bin ; title cards Nem_TitleCard_End: even
Next, lets fix the result screens, which would cause crash. At GotThroughAct:, replace:
moveq #$10,d0 jsr (LoadPLC2).l ; load title card patterns
with:
move.l a0,-(sp) ; save object address to stack move.l #$70000002,($C00004) ; set mode "VRAM Write to $B000" lea Nem_TitleCard,a0 ; load title card patterns move.l #((Nem_TitleCard_End-Nem_TitleCard)/32)-1,d0; the title card art lenght, in tiles jsr LoadUncArt ; load uncompressed art move.l (sp)+,a0 ; get object address from stack
And there you have it! Enjoy the speed!
Method 2: Comper compressed level graphics
SpoilerExtra ROM usage: ~ 10800 bytes
Extra RAM usage: 0
Optimization level: Good
Another long process is to load level graphics. While the processor does other things while that, it must wait for it before fading level in because of the PLC queue, and the fact that the graphics would look terrible (glitched graphics, blank space, etc.). However, because of how fast comper is, we can highly optimize the loading even if we reserve the processor completely just to load level graphics. However Comper isn't as compact as Nemesis compression, so extra space usage in inevitable. It'd be almost impossible to calculate exact space usage, so I threw an aproximation. The space usage may vary. It is good to note that currently no level editor supports Comper, so you must recompress if you wish to edit this system. You will need comper compressed tile-loading code from the misc section.
So, what you want to do first, is recompress these files from Nemesis to Comper:
artnem/8x8ghz1.bin
artnem/8x8ghz2.bin (if you have combined these files, then obviously recompress the combined file)
artnem/8x8lz.bin
artnem/8x8mz.bin
artnem/8x8sbz.bin
artnem/8x8slz.bin
artnem/8x8syz.bin
Next, go to MainLoadBlockLoad:, and replace this
moveq #0,d0 move.b ($FFFFFE10).w,d0 lsl.w #4,d0 lea (MainLoadBlocks).l,a2
with
moveq #0,d0 ; quickly clear d0 move.b ($FFFFFE10).w,d0 ; get level ID bsr.s LoadLevelArt ; load level tiles lsl.w #4,d0 ; shift level ID left by 4 bits lea (MainLoadBlocks).l,a2
Somewhere near MainLoadBlockLoad:, insert thise code (For not combined GHZ art files):
; --------------------------------------------------------------------------- ; Subroutine to load level art patterns ; --------------------------------------------------------------------------- ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| LoadLevelArt: move.w d0,-(sp) ; store level ID to stack lsl.w #2,d0 ; shift 2 bits left move.l LLA_ArtList(pc,d0.w),a0 ; get correct entry from art file list move.l #$40000000,d4 ; set "VRAM Write to $0000" bsr.w LoadCompArt ; load comper compressed art ; workaround for GHZ's secondary art cmpi.b #0,ZoneID.w ; is GHZ? bne.s LLA_End ; if not, don't load art lea Nem_GHZ_2nd,a0 ; get GHZ 2nd patterns move.l #$79A00000,d4 ; set "VRAM Write to $39A0" bsr.w LoadCompArt ; load comper compressed art LLA_End: move.w (sp)+,d0 ; get old level ID from stack again rts ; return to subroutine ; list of art patterns used in levels LLA_ArtList: dc.l Nem_GHZ_1st, Nem_LZ, Nem_MZ, Nem_SLZ, Nem_SYZ, Nem_SBZ
(for combined GHZ art files)
; --------------------------------------------------------------------------- ; Subroutine to load level art patterns ; --------------------------------------------------------------------------- ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| LoadLevelArt: move.w d0,-(sp) ; store level ID to stack lsl.w #2,d0 ; shift 2 bits left move.l LLA_ArtList(pc,d0.w),a0 ; get correct entry from art file list move.l #$40000000,d4 ; set "VRAM Write to $0000" bsr.w LoadCompArt ; load comper compressed art move.w (sp)+,d0 ; get old level ID from stack again rts ; return to subroutine ; list of art patterns used in levels LLA_ArtList: dc.l Nem_GHZ, Nem_LZ, Nem_MZ, Nem_SLZ, Nem_SYZ, Nem_SBZ
Next, we need to remove pointers for level art from _inc/Pattern load cues.asm. The pointers exist for all levels, and here is example how to do it for LZ. Originally you see this:
; --------------------------------------------------------------------------- ; Pattern load cues - Labyrinth ; --------------------------------------------------------------------------- PLC_LZ: dc.w $B dc.l Nem_LZ ; LZ main patterns dc.w 0 dc.l Nem_LzBlock1 ; block dc.w $3C00 dc.l Nem_LzBlock2 ; blocks dc.w $3E00 ...
You want to change it to:
; --------------------------------------------------------------------------- ; Pattern load cues - Labyrinth ; --------------------------------------------------------------------------- PLC_LZ: dc.w $A dc.l Nem_LzBlock1 ; block dc.w $3C00 dc.l Nem_LzBlock2 ; blocks dc.w $3E00 ...
Note how I reduced the value in the first dc.w as well? This is the pointer for the amount of PLC's to load, and since we removed the main art file, that is one less. You want to repeat this for all of the levels.
Method 3: Not waiting for PLC's being loaded
SpoilerExtra ROM usage: less than 100 bytes
Extra RAM usage: 48 (0x30) bytes
Optimization level: Great
Note: You are required to have implented Method 2: Comper compressed level graphics in order to make this work correctly.
In the original game, the level loading hangs for few seconds while it loads level graphics, such as badniks and actual level tiles. This is necessary to not make the level look broken. However as we implented level graphics being decompressed with Comper, therefore it is not an issue, and you can load other graphics much faster, for example while the title card sequence is running. This means, we don't have to wait any graphics to load before we can let the player move already, and they will never notice. However, in order to store more PLC's in the queue, you need to allocate more RAM. We will extend the PLC queue from $FFFFF680-$FFFFF6FF to $FFFFF650-$FFFFF6FF. So first of all, we need to move SBZ and LZ palette cycle pointers from $FFFFF650-$FFFFF661 to somewhere else. You need to find $11 bytes of free RAM somewhere, for this example, I will use $FFFFFECA-$FFFFFEDB.
Go to loc_19F0:, loc_1A0A:, and loc_1ADA:, and replace each instance of $FFFFF650 with your desired RAM address. In my case, $FFFFFECA.
Before StartOfROM:, place these equates:
PLCQueueAdr: = $FFFFF650 ; beginning of RAM allocated for PLC PLCQueue: = PLCQueueAdr+4 ; start of PLC queue PLCQueueEnd: = $FFFFF700-$20 ; end of PLC queue, start of equates for PLC, for example last state of Nemesis decompression
Got to ClearPLC, and replace:
moveq #$1F,d0
with:
moveq #(((PLCQueueEnd+$20)-PLCQueueAdr)/4)-1,d0 ; lenght of the PLC RAM
Next, in loc_16DC:, replace:
moveq #$15,d0
with:
moveq #((PLCQueueEnd-4-PLCQueue)/4)-1,d0 ; lenght of the PLC queue RAM
Above Level_ClrVars: and End_ClrRam: replace
move.w #$15,d1
with:
move.w #((PLCQueueAdr-$FFFFF628)/4)-1,d1
These make sure the lenght of the transfers are correct, and so PLC works as it should. Next up, we should fix the PLC addresses. Replace each instance of ($FFFFF680).w with PLCQueueAdr.w, and each instance of ($FFFFF684).w with PLCQueue.w. Now we have successfully extended PLC queue! Next, we need to make use of this extra space. So, go to Level_TtlCard:, and you should see something like this:
move.b #$34,($FFFFD080).w ; load title card object Level_TtlCard: move.b #$C,($FFFFF62A).w bsr.w DelayProgram jsr ObjectsLoad jsr BuildSprites bsr.w RunPLC_RAM move.w ($FFFFD108).w,d0 cmp.w ($FFFFD130).w,d0 ; has title card sequence finished? bne.s Level_TtlCard ; if not, branch tst.l ($FFFFF680).w ; are there any items in the pattern load cue? bne.s Level_TtlCard ; if yes, branch jsr Hud_Base
Replace it with this:
move.b #$34,($FFFFD080).w ; load title card object move.w #3,$FFFFFE04.w ; set the timer (Fixes Title card bug) Level_TtlCard: move.b #$C,($FFFFF62A).w ; set VBlank routine to $C (loads more tiles per VBlank thank 8 which is normally used) bsr.w DelayProgram ; wait for VBlank jsr ObjectsLoad ; run object code jsr BuildSprites ; display sprites bsr.w RunPLC_RAM ; put PLC data to RAM move.w ($FFFFD100+8).w,d0 cmp.w ($FFFFD100+$30).w,d0 ; has title card sequence finished? bne.s Level_TtlCard ; if not, branch move.w ($FFFFD0C0+8).w,d0 ; fix for FZ crash and title card issue cmp.w ($FFFFD0C0+$30).w,d0 ; has title card sequence finished? bne.s Level_TtlCard ; if not, branch subi.w #1,$FFFFFE04.w ; substract 1 from timer bne.s Level_TtlCard ; if timer is not 0, branch jsr Hud_Base
If some of the levels crash, you can adjust the value 3 in the second line to bigger value, until the levels don't crash. However this works completely in vanilla Sonic 1. EHowever, there is still a slight possibility that FZ can cause some issues, so lets quickly fix that. Go to Resize_FZmain:, and change:
bsr.w LoadPLC ; load FZ boss patterns
to
bsr.w LoadPLC2 ; load FZ boss patterns
Never mind the above, doing the change will make the explosion graphics break, and you can not cause any crashes in the origianl game anyway, so there is no good reason to do that chance
And there you have it!
Misc
Spoilercomper compressed tile-loading
SpoilerThis is the piece of code needed for parts of this tutorial; You will be informed whenever this is necessary.
Right above LoadPLC:, put this piece of code:
; =============================================================== ; --------------------------------------------------------------- ; COMPER compressed art to VRAM loader ; --------------------------------------------------------------- ; INPUT: ; a0 - Source Offset ; d4 - VDP mode ; --------------------------------------------------------------- LoadCompArt: lea $FF0000.l,a1 ; get address of compdec buffer bsr.s CompDec ; decompress art lea $FF0000.l,a3 ; get address of compdec buffer again lea $C00000.l,a6 ; get VDP data port move.l a1,d0 ; move end address to d0 sub.l a3,d0 ; substract the compdec buffer address from d0 lsr.l #2,d0 ; shift 2 bits to right (as we transfer longword per loop) subq.l #1,d0 ; substract 1 from d0 because of dbf move #$2700,sr ; disable interrupts move.l d4,4(a6) ; set VDP transfer mode @loop move.l (a3)+,(a6) ; transfer next longword dbf d0,@loop ; loop until d0 = 0 move #$2300,sr ; enable interrupts rts ; =============================================================== ; --------------------------------------------------------------- ; COMPER Decompressor ; --------------------------------------------------------------- ; INPUT: ; a0 - Source Offset ; a1 - Destination Offset ; ; Full credits of this to Vladikcomper ; --------------------------------------------------------------- CompDec: @newblock move.w (a0)+,d0 ; fetch description field moveq #15,d3 ; set bits counter to 16 @mainloop add.w d0,d0 ; roll description field bcs.s @flag ; if a flag issued, branch move.w (a0)+,(a1)+ ; otherwise, do uncompressed data dbf d3,@mainloop ; if bits counter remains, parse the next word bra.s @newblock ; start a new block ; --------------------------------------------------------------- @flag moveq #-1,d1 ; init displacement move.b (a0)+,d1 ; load displacement add.w d1,d1 moveq #0,d2 ; init copy count move.b (a0)+,d2 ; load copy length beq.s @end ; if zero, branch lea (a1,d1),a3 ; load start copy address @loop move.w (a3)+,(a1)+ ; copy given sequence dbf d2,@loop ; repeat dbf d3,@mainloop ; if bits counter remains, parse the next word bra.s @newblock ; start a new block @end rts
If you already had CompDec routine, you can remove the old one (or the new one, they are the same anyway).
uncompressed tile-loading
SpoilerThis is the piece of code needed for parts of this tutorial; You will be informed whenever this is necessary.
Right above LoadPLC:, put this piece of code:
; =============================================================== ; --------------------------------------------------------------- ; uncompressed art to VRAM loader ; --------------------------------------------------------------- ; INPUT: ; a0 - Source Offset ; d0 - length in tiles ; --------------------------------------------------------------- LoadUncArt: move #$2700,sr ; disable interrupts lea $C00000.l,a6 ; get VDP data port LoadArt_Loop: move.l (a0)+,(a6) ; transfer 4 bytes move.l (a0)+,(a6) ; transfer 4 more bytes move.l (a0)+,(a6) ; and so on and so forth move.l (a0)+,(a6) ; move.l (a0)+,(a6) ; move.l (a0)+,(a6) ; move.l (a0)+,(a6) ; in total transfer 32 bytes move.l (a0)+,(a6) ; which is 1 full tile dbf d0, LoadArt_Loop; loop until d0 = 0 move #$2300,sr ; enable interrupts rts
If you find any bugs, or have other suggestions, post them in the comments! -
SoniPlane
13 October 2014 - 05:00 PM
As you may know by now, while PlanED is useful tool, but it has many flaws, and is unreliable. So, I present you; SoniPlane! This is a plane editor, which is made to be safe to use and user-friendly. This tool is still in early beta and may have fatal bugs. It is recommended to backup any work prior to editing! Note: This post is still incomplete and new information may be added at any given time. It is recommended to check from time to time for updates.
Help wanted
SpoilerWhile working on SoniPlane, and making it more extensive and more compatible for extending it, I ran into some huge bugs, issues, inconsistencies and limitations. Overall, I ran into a point where I figured I should rather than try to work hard and make hacky solutions to hopefully make it work, I decided to rewrite it instead.
The restart will be on a lot improved engine, OpenGL (to draw everything I want properly and efficiently, avoiding slowdown and annoying bugs like in the earlier versions), using LibGDX, and I will finally fix a lot of the annoying bugs, make the code readable, up to the Java standards, compatible, efficient, and make a mod/extension api, to make users able to add their own features they'd like! But, that is an awful lot of work and something I could definitely not make, especially considering I am way more busy than when I started SoniPlane originally, so to try do that all AND make sure it WORKS 100%, would take even up to one YEAR!
I don't know about you, but that's awfully long and that's exactly the reason why I ask YOU to help make this a thing faster! In other words, I am inviting you to join a core development team to work on SoniPlane and possibly specify in certain areas, such as UI, Graphics, API, core, additional features. Anyone with some expertise with Java is eligible to join the team (except for the graphics people, who need to be able to draw something half-decent), and will have to have something to show they are capable of working on SoniPlane in any form. I will keep the team size at first small, so I have less people to deal with and can coordinate more.
So, what are my plans? well, I mentioned some stuff earlier, but here is more complete list:
- Launcher tool that can check everything is correct, see if there is updates, open up a downloader tool if updates are needed, and load SoniPlane itself along with needed mods and extensions and whatnot.
- API to allow modification of almost anything in SoniPlane, and include most functions the user would need (to minimize need for Java supplied functions, you see why later)
- Mod Database, so the users can submit their modifications for others to use. Also would need checking to reject mods which use SoniPlane directly or most Java functions (some would need to be allowed, such as String's)
- Automatic error reporting (with option to turn off) and troubleshooting guide for common issues.
- Proper Javadocs, and easily readable online source code(also hopefully an easy way to see what changed since last version)(Github is fine but its clunky and ugly to some extent)
- Completely rewritten engine and better way of handling [input everything here].
- Java versions of compressors (gets rid of the need for WINE on Linux and OSX)
- Cross-platform compatibility.
- Easy-to-use UI and window handling.
- Graphics that don't look horrible and/or bland.
I don't expect to do a release anytime soon, nor would I expect constant work towards SoniPlane, but I aim towards releasing the tool as soon as possible with still keeping quality at the best. If you want to ask me further questions, I am occasionally at IRC; irc.badnik.net #ssrg - or just message me here!
Features & Releases
SpoilerReleases
- Downloader - Download
ChangeLog
beta 1.1
Spoilerbeta 1.0.4+ Windows have borders
+ Windows can be resized and moved
+ Windows can be minimized and maximized
+ cursor images can be customized
+ proper hotkeys for menu functions
+ when project is loaded the taskbar will blink until the app is focused.
+ save states added
* commandline would not work properly
* all tiles were not rendered properly
* now all menu items are grayed out instead of hidden when not used
* program reset is not CTRL+ESC
Spoilerbeta 1.0.3+ select multiple tiles
+ added new information to debug screen
+ moving parts of plane with mouse
+ new advanced manager for program parts
+ optimizations
* not closing when opening a project too quickly
* made sure SoniPlane not trying to launch when using incorrect directory
* system information output now outputs Bytes, KiloBytes, MegaBytes and Terabytes depending on the size of each drive/memory unit
- Menu entries for shifting plane position
Spoilerbeta 1.0.2+ SoniPlane now runs under Java 7 instead of Java 8 like before (Convenience for the user)
* Improved textfields and fixed bunch of bugs
* Improved file chooser
Spoilerbeta 1.0.1+ Redesigned file selection button.
+ Icons for on/off switches.
+ Output of data in SoniPlane folder
+ Program icon
* Not updating update checker status with very fast internet connection.
* Hopefully fixed launch path detection
- Removed "No backups" screen
Spoilerbeta 1.0+ Added ability to edit or delete projects from the project selection screen
* update box appearing when not checking for updates
* debug screens crashing
* issues caused by editing certain files
Spoilerbeta 0.1.3+ now updates are handled via SoniPlane downloader.
+ the engine is completely rewritten.
+ window is redrawn only when something is changed.
+ automatic update checking.
+ instead of using KensSharp, now SoniPlane uses FW_KENC.
+ Comper support.
+ ugly in-program menus changed to window menus.
+ most things use proper version controllers.
+ compression system overhaul.
* optimizations, tweaks, etc.
* a lot of bugs, also some new may be introduced.
- ability to change framerate - deemed useless due to engine rewrite.
And whatever else I forgot :I
Spoilerbeta 0.1.2+ name field of compression format and mapping, tile, and palette modules are shown instead of filename.
+ implemented feature to remove autosaves after certain amount of space is used.
+ settings menu to easily manage preferences added in beta 0.1.2
* crash issue on saving art (modules/tile/MD.jar).
* inability to type space ( ) on textfields.
* you could only open 1 instance of SoniPlane with run.bat (edited script)
Spoilerbeta 0.1.1+ added preferences! It allows you to edit certain aspects of the program.
+ button to reset the program (autosaves, preferences, etc.)
+ ability to use different commandlines per compression format.
+ ability to modify text field styles.
+ changed mappings, art and palette modules to .txt files to describe them and not have multiple files do that.
* "Exception in thread "LWJGL Application" java.lang.ArithmeticException: / by zero".
* opening main menu on project load when using commandline.
* fixed positioning of errors in project creator
* fixed program title without version number
* changed loading texts
Spoiler+ ability to choose palette line offset
+ commandline support
+ You can now select tile from the plane using O instead of only Middle Click
* using Numpad on text fields would type "Numpad #" instead of "#"
* pressing Numpad 9 or Num 9 would not work
* you cant use colon or semicolon to write said characters
* if you have less palettes than what plane uses, it will crash (Warning: now it just reads from existing lines!)
* you are able to edit multiple textfields at once
* compression format "Uncompressed" fails to automatically exit its command prompt and must be exited manually.
* The editor itself may not be in a filepath that contains any whitespace characters ( ) due to a bug with commandline. However, edited files can be in any kind of filepath.
Features
- Support for "Uncompressed", "Nemesis", "Kosinski", "Enigma", "Comper" and custom compression formats.
- Supports any MegaDrive games mappings, art, and palette, and can be extended for other systems similar.
- Lot of options for editing plane mappings easily and efficiently.
- Palette and tile editor.
- Project file creator.
- Autosave and save backup features.
- High and low plane, tile flip, and palette line support.
- Any number of palette lines, tiles, and mapping size supported.
- Ability to customize the program
- State save/load support
To Do
- add/remove/move tiles
- import/export tiles from image
- import/export plane from image
- import/export palette from image
- add support for multiple palette files
How to Update SoniPlane
SpoilerFirst, if you don't have it already, download the downloader from this post, and move it to the directory you want to install SoniPlane in. Next, execute it with Java 7 or later. The updater will do everything needed from there. If the application does not start, make sure you have Java 7 installed, Downloader.jar is not renamed and you have working internet connection.
Media
Spoiler
System requirements & Source code & misc notes
SpoilerSource code:
SoniPlane
Downloader
Graphics library
System minimum requirements:
Operating system: Windows XP, Windows 7, Linux (Ubuntu 14 tested) (Windows 8, and OS X untested)
CPU: ? (Recommended dual core 1GHz or better)
System memory: 20MB or more
Hard Drive: 5MB (15MB recommended)
Java version: Java 7 or better
Wine: Needed in Linux and OS X (compressors are not cross-platform)
Miscellaneous notes:
If the application does not start normally or crashes, see run.txt for the errors. If the error is not listed as known issue or isnt found in troubleshooting section, nor has anyone reported in this threads replies, you can reply to this topic with everything you know about this issue, as well as short description about your computer (such as Operating System and Java version). You can also contact me via BadnikNet IRC, with the nickname "Natsumi", or write a private message on the forums.
Tutorials
SpoilerControls
SpoilerText fields
SpoilerText fields have been updated to being controlled by Java default text fields. They should work correctly in any computer with any keyboard style. Only remember all other textfields and controls are blocked while you are editing one. You can press Enter to exit a textfield.
Plane editor
SpoilerF1 - reset window positions
F3 - change debug information mode
CTRL+ESC - reset preferences and remove autosaves
CTRL+S - save project
CTRL+R - reload project
CTRL+E - edit project configuration
CTRL+O - go back to main menu
CTRL+Q - disable/enable draw high plane
CTRL+W - disable/enable draw low plane
CTRL+6 - Increase plane width
CTRL+7 - Decrease plane width
CTRL+8 - Increase plane height
CTRL+9 - Decrease plane height
CTRL+Y - Deselect
CTRL+L - Clear tiles from selection
CTRL+I - Remove tiles from selection
CTRL+K - Insert tiles to selection
CTRL+O - Fill selection with tile
CTRL+U - Set selection to high/low plane
CTRL+G - Flip tiles in selection horizontally
CTRL+B - Flip tiles in selection vertically
CTRL+H - Increase palette line in selection
CTRL+N - Decrease palette line in selection
CTRL+J - Increase tile index in selection
CTRL+M - Decrease tile index in selection
Mouse controls
SpoilerTile Editor:
Left hold: Draw pixels to tile.
Right/Middle hold: Select palette offset from pixel.
Palette Editor: Left/Right hold: Change value
Palette selector: Left click: Select palette line and offset.
Tile selector: Left click: Select tile.
Plane editor:
Left hold: Place tiles to plane
Middle click/Key O: Select tile
Right hold: Select an area
Control/Shift + right hold: Copy/Move selection (when you let go of Control/Shift, the selection is Copied/Moved)
Text tutorials
SpoilerCreating a new project
SpoilerWhile opening "Create new" from main menu, you can create new projects through there. Whilst it should be pretty self-explanatory, here is the list of data you must fill, and brief explanations (NOTE: This will be changed in near future and this will be obsolete):
Project file: the file where project is saved to. The file should be on directory "projects" (relative to filepath of SoniPlane).
Project name: the name given to the project. This will be displayed when opening projects.
Palette file: direct filepath for the palette file
Palette type: dropdown menu for available palette decoding modules
Palette line offset: Offset of palette lines. Can be negative.
Transparent line: line number for transparent color
Transparent entry: offset (in current line) for transparent color
Art file: direct filepath for the art file
Art type: dropdown menu for available art decoding modules
Art compression: dropdown menu for available art decompression types
Mapping file: direct filepath for the mapping file
Mapping type: dropdown menu for available mapping decoding modules
Mapping compression: dropdown menu for available mapping decompression types
Plane width: width of plane mappings (in tiles)
Plane height: height of plane mappings (in tiles)
Map offset: addition to tile offset values in decoded mappings (For example, Sonic 1 title screen would have -512)
Autosave delay (Minutes): How many minutes between autosaving your current progress (0 will disable this feature)
Project files must be saved to "projects" folder in programs filepath in order to work!
Project file format:
(NOTE: This will be changed in near future and this will be obsolete)
SoniPlaneProject: 1.x: Used to determine this is valid project.
name: Project display name.
palette type: Palette module filename (No extension)
palette file: Direct path to palette file.
line offset: Offset of palette lines.
trans line: Line number for transparent color.
trans off: Offset (in current line) for transparent color
art file: Direct path to art file.
art type: Art module filename (No extension)
art compression: Art compression script filename (No extension)
map file: Direct path to mappings file.
map type: Mappings module filename (No extension)
map compression: Mappings compression script filename (No extension)
map offset: Offset for tile drawing in plane.
map width: Plane width in tiles.
map height: Plane height in tiles.
autosave: Delay between autosaving in milliseconds.
If you are still confused with the project format, here is an example project.
Adding new compression formats
SpoilerDue to the design of compression, you can easily add more compression formats, as long as you have application that supports commandline. You may not use batch, however.
Existing compression formats are stored in application_path/modules/comp/ and new ones must be stored here as well. You can look at other files for example, but here is a short tutorial to it. All compression files must be .txt files!
version: Version of this compression format caller. Usually your best bet is to have it be same as with any other of the files.
name: String to be displayed on project creator.
compress: Commandline to be executed when attempting to compress existing file. Only called when when cmd field contains %cmd%
decompress: Commandline to be executed when attempting to decompress existing file. Only called when when cmd field contains %cmd%
cmd: called when _OS_SPECIFIC.asm has %cmd% in command field.
There is few rules and special commands to commandline:- %CD% can be used to get direct filepath to comp folder (also include extra slash (/))
- %%I is used to describe where direct path to input file goes. Recommended to enclose in double quotation marks in case of spaces in filepath.
- %%O is used to describe where direct path to output file goes. Recommended to enclose in double quotation marks in case of spaces in filepath.
- %copyfile% is used for uncompressed file formats, to simply copy input file to output file location. Cannot be used with any other arguments.
Launching using commandline
SpoilerSince beta 0.1.1, this application has had ability to directly launch parts of the program from commandline arguments. Here is guide how to use them.
First of all, in order to launch the program to begin with, you must use line similar to this
Java -jar SoniPlane.jar
Here is all the commandline arguments available:
"-new" - Opens project creator
"-edit <file>" - Opens project editor with specified file
"-open <file>" - Opens the plane editor with specifier file
Fail to use commandline properly and the app will boot normally. Here is example with commandline arguments:
Java -jar SoniPlane.jar -open <path>/projects/S1Title.SPP
more coming soon!
Video tutorials
Spoilercoming soon!
Known issues
SpoilerWhen you use right click -> open with -> Java, the program wont open normally
Troubleshooting
Spoiler"Unsupported major.minor version"
SpoilerThis is because you are trying to run with older Java version. Make you sure install Java 7 or higher, and if problem persist, you can use run.bat, with replacing "Java" at beginning with this (Replace "jreX" with jre7 or jre8 depending on which version of Java you have installed):
64-bit Java on 64-bit OS: "C:\Program Files\Java\jreX\bin\java.exe"
32-bit Java on 32-bit OS: "C:\Program Files\Java\jreX\bin\java.exe"
32-bit Java on 64-bit OS: "C:\Program Files (x86)\Java\jreX\bin\java.exe"
"My issue is not listed as known issue, nor exists in troubleshooting section and no reply does note about this" - You can reply to this topic with everything you know about this issue, as well as short description about your computer (such as Operating System and Java version). You can also contact me via BadnikNet IRC, with the nickname "Natsumi", or write a private message on the forums.
Copyright
SpoilerThis software and all of it's components are lisenced under Open Software License v. 3.0 (OSL-3.0)
Copyright 2014 Green Snake. Some Rights Reserved. -
[S1] How to add extra characters
02 October 2014 - 02:40 PM
As we don't have a working multi-character guide anywhere (Trust me, this one doesn't work), so I thought I'd make a full guide on this. the good part about this guide is that: 1: It uses techniques to load the art/mappings/DPLC as fast as possible, leaving more time for whatever else code the game is running; 2: You have multiple options on how to do things, so you can select the most fitting way; 3: You can have up to 64 characters using this guide, so you should not run out of them in a while. The character IDs are always in increments of 4, to save some processor cycles; 4: It is easy to add more characters, you have to do barely anything! Just note that this is based on the HiveBrain 2005 disassembly, and you may need to do some extra work for SVN/HG/Git disassemblies. Also note that this will NOT work on Sonic 2 or Sonic 3! I am also going to use Tails as an example, so replace each reference to Tails with your characters name!
Step 1: Preparations.Spoiler
File includes
That said, lets begin! So, the first thing you want to do, is get the proper art, mappings, DPLC, Animation, and palette files for your new character. If you miss one of these, this tutorial wont work. The files should be located at same places where normal Sonic's files of same type are at. As an example, you would put the new character DPLC file at the _inc folder (in case of HiveBrain 2005). As we are on the subject of the files, lets include them to the ROM:
First, go to label "Art_Sonic", and you'll see something like this:
; --------------------------------------------------------------------------- ; Uncompressed graphics - Sonic ; --------------------------------------------------------------------------- Art_Sonic: incbin artunc\sonic.bin ; Sonic even
What you want to do this, is copy this, and replace each Sonic with Tails. Also make sure You use the correct file name (If Tails.bin does not exist, make sure to change either the incbin reference or the original file name to match). So, in my case, this is what I would have:
; --------------------------------------------------------------------------- ; Uncompressed graphics - Tails ; --------------------------------------------------------------------------- Art_Tails: incbin artunc\tails.bin ; Tails' art even
Note that this should be located either before or after the Sonic's sequence, as so:
; --------------------------------------------------------------------------- ; Uncompressed graphics - Sonic ; --------------------------------------------------------------------------- Art_Sonic: incbin artunc\sonic.bin ; Sonic even ; --------------------------------------------------------------------------- ; Uncompressed graphics - Tails ; --------------------------------------------------------------------------- Art_Tails: incbin artunc\tails.bin ; Tails' art even
Ok, next up mappings and DPLC. Go to "Map_Sonic", and you'll see something along the lines of this:
; --------------------------------------------------------------------------- ; Sprite mappings - Sonic ; --------------------------------------------------------------------------- Map_Sonic: include "_maps\Sonic.asm" ; --------------------------------------------------------------------------- ; Uncompressed graphics loading array for Sonic ; --------------------------------------------------------------------------- SonicDynPLC: include "_inc\Sonic dynamic pattern load cues.asm"
Do something similar to it too, than with the art. So, this is what I'd have:
; --------------------------------------------------------------------------- ; Sprite mappings - Sonic ; --------------------------------------------------------------------------- Map_Sonic: include "_maps\Sonic.asm" ; --------------------------------------------------------------------------- ; Uncompressed graphics loading array for Sonic ; --------------------------------------------------------------------------- SonicDynPLC: include "_inc\Sonic dynamic pattern load cues.asm" ; --------------------------------------------------------------------------- ; Sprite mappings - Tails ; --------------------------------------------------------------------------- Map_Tails: include "_maps\Tails.asm" ; --------------------------------------------------------------------------- ; Uncompressed graphics loading array for Tails ; --------------------------------------------------------------------------- TailsDynPLC: include "_inc\Tails dynamic pattern load cues.asm"
Next, let's do the animations file. Go to "SonicAniData", and you'll see this:
SonicAniData: include "_anim\Sonic.asm"
In my case, I would change it to:
SonicAniData: include "_anim\Sonic.asm" TailsAniData: include "_anim\Tails.asm"
All we need to include now is the palette. Go to "Pal_Sonic", and you'll see something like this:
Pal_Sonic: incbin pallet\sonic.bin ... Pal_LZSonWater: incbin pallet\son_lzuw.bin ; Sonic (underwater in LZ) pallet Pal_SBZ3SonWat: incbin pallet\son_sbzu.bin ; Sonic (underwater in SBZ act 3) pallet
Still, do the same thing than with the art. I'd do this:
Pal_Sonic: incbin pallet\sonic.bin ; Sonic's normal palette Pal_Tails: incbin pallet\Tails.bin ; Tails' normal palette ... Pal_LZSonWater: incbin pallet\son_lzuw.bin ; Sonic (underwater in LZ) palette Pal_SBZ3SonWat: incbin pallet\son_sbzu.bin ; Sonic (underwater in SBZ act 3) palette Pal_TailsLZ: incbin pallet\tails_lz.bin ; Tails (underwater in LZ) palette Pal_TailsSBZ3: incbin pallet\tails_sbz3.bin ; Tails (underwater in SBZ act 3) palette
Sonic 2's DPLC
if you have followed either Add Spin Dash to Sonic 1 (Part 3), or How to optimize Shield Art loading in Sonic 1 (Step 1), you have already it on your hack. If not, however, you must add it. I suggest following the latter link.
Different versions
Good! Now we should have everything we need for your new character to work! Well, except all the code behind it of course. But before we start on it, build your ROM, and make sure it wont complain about any "undefined errors" or "this and that not found". If some of them comes up, fix them up first. If not, lets continue. Now. you should decide which version you want to use, and find free RAM space.
Version 1: Everything is stored via RAM. Requires 32 bytes of free RAM. Fastest.
Version 2: Animation offsets are referenced via tables. Requires 8 bytes of free RAM. Slower.
version 3: Everything is referenced via tables. Requires 0 bytes of free RAM. Slowest.
So, after you have decided the technique you want to use, you want to go to beginning of your ROM, and just before
align macro cnop 0,\1 endm
You want to add this equate:
Current_Character equ ; whatever the character you are using is
Now, you must find 1 free byte of RAM. I suggest you to use "$FFFFFFF9", as it is 1-byte free address. In any case, put the address after the "equ" (You need at least 1 space between them). If you want to know more about unused addresses, go here. What this flag is used for, is to store what character you are using. Note that referencing "Current_Character" and not the RAM address you chose is much better, as you can easily mass-change the RAM itself, and it is easier to identify what it is used for.
You also need some other RAM equates, but they depend on the version you use. Here is what you need for version 1:
SpoilerPlayer_ArtLoc equ ; 4 bytes ; pointer for players art location (can also be used for special art if needed to ;)/> ) Player_DPLCLoc equ ; 4 bytes ; pointer for players DPLC location (can be used for special DPLCs) Player_AniDat equ ; 4 bytes ; pointer for players animation data location (can be used for special animations) PAni_Run equ ; 4 bytes ; pointer for players Run animation (can be used for special animation) PAni_Walk equ ; 4 bytes ; pointer for players Walk animation (can be used for special animation) PAni_Roll equ ; 4 bytes ; pointer for players Roll animation (can be used for special animation) PAni_Roll2 equ ; 4 bytes ; pointer for players Roll animation (can be used for special animation) PAni_Push equ ; 4 bytes ; pointer for players Push animation (can be used for special animation)
Version 2:SpoilerPlayer_ArtLoc equ ; 4 bytes ; pointer for players art location (can also be used for special art if needed to ;)/> ) Player_DPLCLoc equ ; 4 bytes ; pointer for players DPLC location (can be used for special DPLCs) Player_AniDat equ ; 4 bytes ; pointer for players animation data location (can be used for special animations)
Version 3: none.
Step 2: The actual code.Spoiler
Palette
So, lets get to work! First of all, we want to do the simplest part, which will probably be palette. Head to "Level_LoadPal". You will see the following code:
moveq #3,d0 bsr.w PalLoad2 ; load Sonic's pallet line cmpi.b #1,($FFFFFE10).w ; is level LZ? bne.s Level_GetBgm ; if not, branch moveq #$F,d0 ; pallet number $0F (LZ) cmpi.b #3,($FFFFFE11).w ; is act number 3? bne.s Level_WaterPal ; if not, branch moveq #$10,d0 ; pallet number $10 (SBZ3) Level_WaterPal: bsr.w PalLoad3_Water ; load underwater pallet (see d0) tst.b ($FFFFFE30).w beq.s Level_GetBgm move.b ($FFFFFE53).w,($FFFFF64E).w
We don't want this. Replace with:
lea $FFFFFB00,a2 ; normal palette lea $FFFFFA80,a3 ; underwater palette bsr LoadPlayerPalettes ; load palette for current character cmpi.b #1,$FFFFFE10.w ; is level LZ? bne.s Level_GetBgm ; if not, branch tst.b $FFFFFE30.w ; has lamppost been hit? beq.s Level_GetBgm ; if not, branch move.b $FFFFFE53.w,$FFFFF64E.w ; copy water direction to lamppost RAM
There are, however, few locations where Sonic's palette is loaded. It is optional to fix any of them, but for completions sake, here is how: In Title_ClrPallet, loc_3946, End_LoadData, and Cred_ClrPallet, replace following piece of code: (Note: Title screen uses it's own palette for Sonic, you need to find your own way to replace that.)
moveq #3,d0 bsr.w PalLoad1 ; load Sonic's pallet
With this:
lea $FFFFFB80,a2 ; normal target palette lea $FFFFFA00,a3 ; underwater target palette bsr LoadPlayerPalettes ; load palette for current character
Now we need the actual code to load the palette. Somewhere near "Pal_Sonic", insert this code:
; =========================================================================== ; --------------------------------------------------------------------------- ; Subroutine to load correct player pallets ; --------------------------------------------------------------------------- LoadPlayerPalettes: moveq #0,d1 move.b Current_Character.w,d1 ; get character ID move.l CharPalList(pc,d1.w),a1 ; get normal palette to a1 moveq #7,d0 ; 16 palette entries bsr.s Loc_Pal ; load palettes to RAM cmpi.b #1,$FFFFFE10.w ; is LZ? bne.s LPP_rts ; if not, branch move.l CharPalListLZ(pc,d1.w),a1 ; get underwater palette to a1 cmpi.b #3,$FFFFFE11.w ; is act number 3? bne.s LPP_UWPal ; if not, branch move.l CharPalListSBZ3(pc,d1.w),a1 ; get SBZ3 underwater palette to a1 LPP_UWPal: moveq #7,d0 ; 16 palette entries lea (a3),a2 ; put water palette to a2 bsr.s Loc_Pal ; load to RAM LPP_rts: rts ; return ; =========================================================================== ; --------------------------------------------------------------------------- ; copy d0 + 1 longwords from a1 to a2 ; --------------------------------------------------------------------------- loc_Pal: move.l (a1)+,(a2)+ ; process next 2 palette entries dbf d0,loc_Pal ; keep looping until d0 is 0 rts ; return
Next, we need to create the array of pointers for our desired player palettes. This isn't exactly complicated, so let me show example of how I would do it:
CharPalList: dc.l Pal_Sonic, Pal_Tails CharPalListLZ: dc.l Pal_LZSonWater, Pal_TailsLZ CharPalListSBZ3: dc.l Pal_SBZ3SonWat, Pal_TailsSBZ3
It makes most sense to place this either under or above the LoadPlayerPalettes code I did show you above.
Art and dynamic pattern load cues
First off, go to LoadSonicDynPLC, and replace the entire routine with this:
version 1 and version 2:Spoiler
LoadSonicDynPLC: movea.l Player_DPLCLoc,a2 ; get DPLC location move.w #$F000,d4 ; offset in VRAM to store art move.l Player_ArtLoc,d6 ; get art location moveq #0,d0 move.b $1A(a0),d0 ; load frame number cmp.b $FFFFF766.w,d0 ; check if equal with last queued frame beq.s DPLC_End ; if is, don't load new DPLC move.b d0,$FFFFF766.w ; remember queued frame ; End of function LoadSonicDynPLC ; --------------------------------------------------------------------------- ; Subroutine to queue any pattern load cue ; Input: a2 - DPLC file, d4 - VRAM address, d6 - Art file, d0 - frame number ; --------------------------------------------------------------------------- Load_DPLC: add.w d0,d0 ; multiply by 2 adda.w (a2,d0.w),a2 ; get the right DPLC location moveq #0,d5 ; quckly clear d5 move.b (a2)+,d5 ; then move the amount of requests to d5 subq.w #1,d5 ; subtract 1 bmi.s DPLC_End ; if negative, branch away DPLC_ReadEntry: moveq #0,d1 move.b (a2)+,d1 ; get first byte to d1, and increment pointer lsl.w #8,d1 ; shift 8 bits left move.b (a2)+,d1 ; move second byte to d1 move.w d1,d3 ; move d1 to d3 lsr.w #8,d3 ; shift 8 bits right andi.w #$F0,d3 ; leave only bits 7, 6, 5, and 4 addi.w #$10,d3 ; add $10 to d3 andi.w #$FFF,d1 ; filter out bits 15, 14, 13 and 12 lsl.l #5,d1 ; shift 5 bits left add.l d6,d1 ; add the art address to d1 move.w d4,d2 ; move VRAM location to d2 add.w d3,d4 ; add d3 to VRAM address add.w d3,d4 ; add d3 to VRAM address jsr QueueDMATransfer; Save it to the DMA queue dbf d5,DPLC_ReadEntry; repeat for number of requests DPLC_End: rts ; return
version 3:Spoiler
LoadSonicDynPLC: moveq #0,d0 ; quickly clear d0 move.b Current_Character.w,d0 ; get character ID movea.l Player_DPLCLoc(pc,d0.w),a2 ; put players DPLC location to a2 move.l Player_ArtLoc(pc,d0.w),d6 ; put players art location to a2 move.w #$F000,d4 ; offset in VRAM to store art moveq #0,d0 move.b $1A(a0),d0 ; load frame number cmp.b $FFFFF766.w,d0 ; check if equal with last queued frame beq.s DPLC_End ; if is, don't load new DPLC move.b d0,$FFFFF766.w ; remember queued frame ; End of function LoadSonicDynPLC ; --------------------------------------------------------------------------- ; Subroutine to queue any pattern load cue ; Input: a2 - DPLC file, d4 - VRAM address, d6 - Art file, d0 - frame number ; --------------------------------------------------------------------------- Load_DPLC: add.w d0,d0 ; multiply by 2 adda.w (a2,d0.w),a2 ; get the right DPLC location moveq #0,d5 ; quckly clear d5 move.b (a2)+,d5 ; then move the amount of requests to d5 subq.w #1,d5 ; subtract 1 bmi.s DPLC_End ; if negative, branch away DPLC_ReadEntry: moveq #0,d1 move.b (a2)+,d1 ; get first byte to d1, and increment pointer lsl.w #8,d1 ; shift 8 bits left move.b (a2)+,d1 ; move second byte to d1 move.w d1,d3 ; move d1 to d3 lsr.w #8,d3 ; shift 8 bits right andi.w #$F0,d3 ; leave only bits 7, 6, 5, and 4 addi.w #$10,d3 ; add $10 to d3 andi.w #$FFF,d1 ; filter out bits 15, 14, 13 and 12 lsl.l #5,d1 ; shift 5 bits left add.l d6,d1 ; add the art address to d1 move.w d4,d2 ; move VRAM location to d2 add.w d3,d4 ; add d3 to VRAM address add.w d3,d4 ; add d3 to VRAM address jsr QueueDMATransfer; Save it to the DMA queue dbf d5,DPLC_ReadEntry; repeat for number of requests DPLC_End: rts ; return
Next we need to create an array to store direct locations to art and DPLC data. It should be very simple, so I'll just show an example:
Player_DPLCLoc: dc.l SonicDynPLC, TailsDynPLC Player_ArtLoc: dc.l Art_Sonic, Art_Tails
Put it right above LoadSonicDynPLC.
Great! Now your character loads its own art and DPLC.
Mappings
version 1:Spoiler
Above Obj01, put this code:
; --------------------------------------------------------------------------- ; Subroutine to set Player-specific variables ; --------------------------------------------------------------------------- SetPlayerDisplay: moveq #0,d0 ; quickly clear d0 move.b Current_Character,d0 ; get character ID move.w d0,d1 ; store to d1 as well lsl.w #3,d0 ; shift left 4 bits add.w d1,d0 ; add d1 to d0 lea PlayerDataList(pc,d0.w),a1; get the table to a1 move.l (a1)+,Player_ArtLoc ; player art location move.l (a1)+,4(a0) ; get mappings move.l (a1)+,Player_DPLCLoc ; FPLC location move.l (a1)+,Player_AniDat ; Animation data location move.l (a1)+,PAni_Walk ; walk animation move.l (a1)+,PAni_Run ; run animation move.l (a1)+,PAni_Roll ; roll animation move.l (a1)+,PAni_Roll2 ; roll animation move.l (a1)+,PAni_Push ; push animation rts ; return
Next, we are going to create an array containing all this data. It's format is described in the comment.
; Art file, Map file, DPLC file, Ani file, Walk ani ptr, Run ani ptr, Roll ani ptr, Roll2 ani ptr, Push ani ptr PlayerDataList: dc.l Art_Sonic, Map_Sonic, SonicDynPLC, SonicAniData, SonAni_Walk, SonAni_Run, SonAni_Roll, SonAni_Roll2, SonAni_Push ; Sonic dc.l Art_Tails, Map_Tails, TailsDynPLC, TailsAniData, TailsAni_Walk, TailsAni_Run, TailsAni_Roll, TailsAni_Roll2, TailsAni_Push ; Tails
Put it below SetPlayerDisplay. Next, at Obj01_Main and Obj09_Main replace:
move.l #Map_Sonic,4(a0)
with:
bsr.w SetPlayerDisplay
version 2:Spoiler
Above Obj01, put this code:
; --------------------------------------------------------------------------- ; Subroutine to set Player-specific variables ; --------------------------------------------------------------------------- SetPlayerDisplay: moveq #0,d0 ; quickly clear d0 move.b Current_Character,d0 ; get character ID lsl.w #2,d0 ; shift left 2 bits lea PlayerDataList(pc,d0.w),a1; get the table to a1 move.l (a1)+,Player_ArtLoc ; player art location move.l (a1)+,4(a0) ; get mappings move.l (a1)+,Player_DPLCLoc ; FPLC location move.l (a1),Player_AniDat ; Animation data location rts ; return
Next, we are going to create an array containing all this data. It's format is described in the comment.
; Art file, Map file, DPLC file, Ani file PlayerDataList: dc.l Art_Sonic, Map_Sonic, SonicDynPLC, SonicAniData ; Sonic dc.l Art_Tails, Map_Tails, TailsDynPLC, TailsAniData ; Tails
Put it below SetPlayerDisplay. Next, at Obj01_Main and Obj09_Main replace:
move.l #Map_Sonic,4(a0)
with:
bsr.w SetPlayerDisplay
version 3:Spoiler
At Obj01_Main and Obj09_Main replace:
move.l #Map_Sonic,4(a0)
with:
moveq #0,d0 ; quickly clear d0 move.b Current_Character.w,d0 ; get character ID move.l #Player_MapLoc,a1 ; get players mapping location array add.l d0,a1 ; get correct mapping for player move.l (a1),4(a0) ; put it to Sonic's mappings
Now we need table for mappings. This, again, isnt all that complicated, so have an example:
Player_MapLoc: dc.l Map_Sonic, Map_Tails
Place it above Obj01.
Animations
version 1:Spoiler
First, go to Sonic_Animate, and replace:
lea (SonicAniData).l,a1
With this:
move.l Player_AniDat.w,a1
Now, at loc_13A9C, replace:
lea (SonAni_Run).l,a1 ; use running animation cmpi.w #$600,d2 ; is Sonic at running speed? bcc.s loc_13AB4 ; if yes, branch lea (SonAni_Walk).l,a1 ; use walking animation
With:
move.l PAni_Run.w,a1 ; use running animation cmpi.w #$600,d2 ; is Sonic at running speed? bcc.s loc_13AB4 ; if yes, branch move.l PAni_Walk.w,a1 ; use walking animation
Go to loc_13ADE, and replace:
lea (SonAni_Roll2).l,a1 ; use fast animation cmpi.w #$600,d2 ; is Sonic moving fast? bcc.s loc_13AF0 ; if yes, branch lea (SonAni_Roll).l,a1 ; use slower animation
With:
move.l PAni_Roll2.w,a1 ; use fast animation cmpi.w #$600,d2 ; is Sonic moving fast? bcc.s loc_13AF0 ; if yes, branch move.l PAni_Roll.w,a1 ; use slower animation
Go to loc_13B26, and replace:
lea (SonAni_Push).l,a1
With:
move.l PAni_Push.w,a1 ; use pushing animation
version 2:Spoiler
First, go to Sonic_Animate, and replace:
lea (SonicAniData).l,a1
With this:
move.l Player_AniDat.w,a1
Now, at loc_13A9C, replace:
lea (SonAni_Run).l,a1 ; use running animation cmpi.w #$600,d2 ; is Sonic at running speed? bcc.s loc_13AB4 ; if yes, branch lea (SonAni_Walk).l,a1 ; use walking animation
With:
moveq #0,d4 ; quickly clear d0 move.b Current_Character.w,d4 ; get character ID movea.l PAni_Run(pc,d4.w),a1 ; put players running animation to a1 cmpi.w #$600,d2 ; is Sonic at running speed? bcc.s loc_13AB4 ; if yes, branch movea.l PAni_Walk(pc,d4.w),a1 ; put players walking animation to a1
Go to loc_13ADE, and replace:
lea (SonAni_Roll2).l,a1 ; use fast animation cmpi.w #$600,d2 ; is Sonic moving fast? bcc.s loc_13AF0 ; if yes, branch lea (SonAni_Roll).l,a1 ; use slower animation
With:
moveq #0,d4 ; quickly clear d0 move.b Current_Character.w,d4 ; get character ID movea.l PAni_Roll2(pc,d4.w),a1 ; put players fast rolling animation to a1 cmpi.w #$600,d2 ; is Sonic moving fast? bcc.s loc_13AF0 ; if yes, branch movea.l PAni_Roll(pc,d4.w),a1 ; put players slow rolling animation to a1
Go to loc_13B26, and replace:
lea (SonAni_Push).l,a1
With:
moveq #0,d4 ; quickly clear d0 move.b Current_Character.w,d4 ; get character ID movea.l PAni_Push(pc,d4.w),a1 ; put players fast rolling animation to a1
Just above SAnim_RollJump, we make few arrays. They are pretty simple so I'll show an example:
PAni_Run: dc.l SonAni_Run, TailsAni_Run PAni_Walk: dc.l SonAni_Walk, TailsAni_Walk PAni_Roll2: dc.l SonAni_Roll2, TailsAni_Roll2 PAni_Roll: dc.l SonAni_Roll, TailsAni_Roll PAni_Push: dc.l SonAni_Push, TailsAni_Push
version 3:Spoiler
First, go to Sonic_Animate, and replace:
lea (SonicAniData).l,a1
With this:
moveq #0,d0 ; quickly clear d0 move.b Current_Character.w,d0 ; get character ID movea.l Player_AniDat(pc,d0.w),a1 ; put players animation data to a1
Above Sonic_Animate, put array. This is pretty simple so here is example:
AniDat: dc.l SonicAniData, TailsAniData
Now, at loc_13A9C, replace:
lea (SonAni_Run).l,a1 ; use running animation cmpi.w #$600,d2 ; is Sonic at running speed? bcc.s loc_13AB4 ; if yes, branch lea (SonAni_Walk).l,a1 ; use walking animation
With:
moveq #0,d4 ; quickly clear d0 move.b Current_Character.w,d4 ; get character ID movea.l PAni_Run(pc,d4.w),a1 ; put players running animation to a1 cmpi.w #$600,d2 ; is Sonic at running speed? bcc.s loc_13AB4 ; if yes, branch movea.l PAni_Walk(pc,d4.w),a1 ; put players walking animation to a1
Go to loc_13ADE, and replace:
lea (SonAni_Roll2).l,a1 ; use fast animation cmpi.w #$600,d2 ; is Sonic moving fast? bcc.s loc_13AF0 ; if yes, branch lea (SonAni_Roll).l,a1 ; use slower animation
With:
moveq #0,d4 ; quickly clear d0 move.b Current_Character.w,d4 ; get character ID movea.l PAni_Roll2(pc,d4.w),a1 ; put players fast rolling animation to a1 cmpi.w #$600,d2 ; is Sonic moving fast? bcc.s loc_13AF0 ; if yes, branch movea.l PAni_Roll(pc,d4.w),a1 ; put players slow rolling animation to a1
Go to loc_13B26, and replace:
lea (SonAni_Push).l,a1
With:
moveq #0,d4 ; quickly clear d0 move.b Current_Character.w,d4 ; get character ID movea.l PAni_Push(pc,d4.w),a1 ; put players fast rolling animation to a1
Just above SAnim_RollJump, we make few arrays. They are pretty simple so I'll show an example:
PAni_Run: dc.l SonAni_Run, TailsAni_Run PAni_Walk: dc.l SonAni_Walk, TailsAni_Walk PAni_Roll2: dc.l SonAni_Roll2, TailsAni_Roll2 PAni_Roll: dc.l SonAni_Roll, TailsAni_Roll PAni_Push: dc.l SonAni_Push, TailsAni_Push
Congratulations! Now you should have extra character in your hack! If you have any trouble, please look into troubleshooting section prior to asking any questions.
TroubleshootingSpoiler
Garbled graphics/parts of graphicsSpoiler
This is due to your characters art being inbetween 2 128KB ROM "banks". VDP can only DMA from one bank per transfer, and because of this the art can only lie on one bank. You can put align $20000 just before the art file causing issue. Note: This will put 00's until next address dividable by 131072 (0x20000). Here is an example:
; --------------------------------------------------------------------------- ; Uncompressed graphics - Sonic ; --------------------------------------------------------------------------- Art_Sonic: incbin artunc\sonic.bin ; Sonic even ; --------------------------------------------------------------------------- ; Uncompressed graphics - Tails ; --------------------------------------------------------------------------- align $20000 ; align to next bank Art_Tails: incbin artunc\tails.bin ; Tails' art even
Sometimes graphics don't updateSpoiler
I suggest following this guide, and trying again.

Find My Content
Yesterday, 11:55 PM
Female