--------------------------------------------
First, let's clear up some confusion:
Code: Select all
Flags: (...) // 0x01 this has a seperate alpha map attached after the image data
Although the Level Editor places this data correctly (*) when importing alpha maps, the Riot Engine is incapable of actually using it due to some unknown bug (probably somewhere in the rendering code).
(*): Importing alpha maps works correctly, now that I've fixed it. Previously, the alpha channel data was also getting placed into the correct location, but was getting partially mangled in the process due to a programming error in the byte-order-flipping routine; this caused subtle distortion of the imported alpha maps.
So the purpose of this functionality:
...is thoroughly moot, unless someone first fixes the underlying bug in the Engine.if flags & 1:
// Unknown interpretation.
// This data might need to be appended to the above, or maybe not.
// Or maybe this data is corrupt and unusable.
uint8[uint32] separate_alphamap_data;
However... the Level Editor uses this exact data when the "Edit Convert" option is selected on a non-integrated alpha map.
This is the very data that is added when "Add Alpha map" is selected; the Editor always adds it as a separate alpha map, thus effectively requiring an additional conversion step to be performed by the user.
So for practical purposes, best current practice for any future clones of the Riot Engine (and/or clones of the Level Editor) would be to throw a warning message whenever a TXD is loaded that has any entries with a non-integrated alpha map, prompting the user to convert these to the integrated format (since otherwise they will remain non-functional in the original Engine).
This is because currently the only way to have working alpha channels in textures is by integrating the alpha map with the surface pixels:
Code: Select all
Flags: (...) // 0x02 an alpha channel is integrated into the image data
--------------------------------------------
NOTE: The critical parameter that determines whether a texture's alpha map works (or not) is the FLAGS parameter in the TXD data.
For 32-bit textures, changing FLAGS.1 to False disables the (integrated) alpha channel with no in-game side effects; however it also causes strange behavior in the Editor - which now thinks that the integrated alpha map is actually a completely separate sub-texture (it isn't!); this allows some inappropriate operations to be performed on it (don't!), likely causing the Editor to crash and/or corrupt unrelated data in the process (untested!).
With all the other texture types, changing FLAGS.1 to False will cause colorspace corruption, due to the different meaning of the bitfields in the pixel data after alpha channel integration.
--------------------------------------------
For 32-bit textures, the byte order for the pixels' component colors in the .TXD files is as follows: BGRA (8:8:8+8).
Despite the Editor's vehement assertions to the contrary, the Riot Engine can handle 32-bit textures perfectly fine - including the alpha channel!
As proof, I've attached a database file containing a single 32x32 32-bit RGBA texture, which has been hexed in manually as a proof of concept.
Regardless of the pixel format, the texture pixel data is stored in the exact same pixel layout as in the BMP files - that is, flipped vertically. So the image data in memory starts at the bottom left corner, and ends at the upper right.
--------------------------------------------
The .BMP import codepath:
(Note: Function arguments are listed by the reverse order from that in which they are pushed onto the stack - that is, the same way that ollydbg counts them; "Called from" applies only to this particular codepath!)
When the "Add texture" option in the Databases window menu is clicked, the first thing that happens is that the TextureImporter function is called, from what is presumably the main loop for the Databases window:
loop TextureManager (Level_Ed.00426860)
Active at all times when the Level Editor's window is in focus,
Appears to oversee stuff related to displaying and manipulating textures in the Editor,
Notes:
- clicking on "Add texture(s)" case 111 of switch 004268A7 Level_Ed.00426D8C,
- bails out of the loop if no color palette is loaded (spawning the error message window first),
- otherwise branches to 00426D8C and continues from there,
- calls some other code which does a lot of (irrelevant?) things, including a heap allocation call; it seems that this is where the pointer to texture descriptors originates from,
- calls function TextureImporter,
- checks if the width of the loaded texture is one of the permitted values from a list of (32, 64, 128, 256, 512, 1024, 2048),
- checks the number of bits per pixel of the loaded bitmap,
- if it's 24 or 32-bit, calls BitmapBitnessConverter,
(...)
function TextureImporter (Level_Ed.004231D0)
Called from: Level_Ed.00426860 +600h
Arg_ECX: Pointer to a location where the texture metadata (width, height, BPP, etc.) should be written to,
Arg1: Unknown (seems to be important, whatever it is - but it sure ain't a valid pointer!),
Arg2: Pointer to the full file path for the import, stored on the stack (always 0 when called fromLevel_Ed.00426860 +600h),
Arg3: Pointer to memory block containing database metadata; specifically, it points to the beginning of the currently loaded palette data (taken verbatim from the .PAL file, minus the header),
Arg4: Unknown (always 0FFFFFFFFh when called from Level_Ed.00426E60),
Arg5: Unknown (always 0 when called from Level_Ed.00426E60),
Notes:
- sometimes gets called a second time after (if) the user presses the Cancel button in the file open dialog (can't replicate reliably!),
- reserves a lot of space on the stack (over 256 bytes) - this is the buffer for the file path returned by FileOpenDlgHandler,
- pushes a whole bunch of other crap onto the stack,
- if no pointer to the file path was specified, calls function FileOpenDlgHandler; otherwise it uses the path provided,
- checks for .BMP extension (again!),
- if all is in order, calls function BMP_Reader then returns.
function FileOpenDlgHandler (Level_Ed.0046A2A0)
Called from: function TextureImporter (Level_Ed.004231D0) +0A1h
Arguments: Unknown
(Appears to take no arguments? The last thing pushed onto the stack before it's called is another pointer to some empty memory space...?)
Notes:
- gets the current working directory from somewhere (the stack?)
- spawns the "Open file..." dialog, opened in the current working directory,
- checks if the selected file has .BMP extension,
- if successful, returns the full path to the selected file on the stack, as well as a return code of 0 in EAX.
function BMP_Reader (Level_Ed.004233F0)
Called from: function TextureImporter (Level_Ed.004231D0) +1FFh
Arg_ECX: Pointer to where the new texture metadata struct needs to be written to,
Arg1: Unknown (seems to be important, whatever it is - but it sure ain't a valid pointer!),
Arg2: Pointer to the location of the full ASCII file path on the stack,
Arg3: Pointer to memory block containing database metadata; specifically, it points to the beginning of the currently loaded palette data (taken verbatim from the .PAL file, minus the header),
Arg4: Unknown (appears to always be 0FFFFFFFFh),
Arg5: Unknown (appears to always be 0 if importing a base texture; 1 if importing an alpha map),
Notes:
- Arg1, 3, 4 & 5 appear to be passed verbatim by the calling function,
- also checks if the file actually exists, and bails out if it doesn't,
- that seems redundant, because the calling function also performs its own checks to that effect,
- then tries to open the file (Kernel32.OpenFile),
- if successful, calls function BMP_HeaderParser,
- if successful, writes some data to a struct in process memory (at the location pointed to by the 3rd pointer on the stack after returning from function BMP_HeaderParser; see description below),
- calculates the size of the pixel data (width in bytes * height in pixels) and allocates this much memory (Kernel32.GlobalAlloc),
- puts the handle of the allocated memory onto the stack,
- locks the memory with Kernel32.GlobalLock,
- copies the entire pixel data from the .BMP file verbatim into the newly allocated memory,
- if a 24-bit BMP, flips the byte order of the individual pixels from BGR to RGB,
- unlocks the allocated memory with Kernel32.GlobalUnlock,
- puts the handle to said memory into the same struct as the other data,
- closes the .BMP file with Kernel32._lclose,
- calls function TextureDefLoader,
- returns 0 in EAX if successful (or 1 if not?),
NOTE: the struct listed below is the texture metadata used by the Editor for all of its operations; hexing it causes the relevant values to change in real time (such as the presence or absence of alpha channel).
Data written to the struct in memory (all DWORDs):
- horizontal width of the texture data, in pixels,
- vertical height of the texture data, in pixels,
- horizontal width of the texture data, in bytes,
- # of bits per pixel of the base texture,
- # of bits per pixel of the alpha channel data,
- unknown DWORD value (always 0FFFFFFFFh if importing a .BMP...?),
- unknown DWORD value (always 0 if importing a .BMP...?),
- unknown DWORD value (always 0 if importing a .BMP...?),
- unknown DWORD value,
- handle to the allocated memory buffer containing the flipped pixel byte data (effectively, a pointer to the pointer),
- handle to the allocated memory buffer containing the separate alpha channel pixel byte data (valid if FLAGS.0 = 1),
- unknown DWORD value (always 0 if importing a .BMP...?),
- unknown BYTE value (usually 0Ah, sometimes just 0),
- BYTE value containing the texture flags,
- unknown WORD value (always 0?),
- unknown DWORD value (always 0 if importing a .BMP...?),
- unknown DWORD value (maybe a pointer?),
- pointer to the "fake palette data" (if it exists).
function BMP_HeaderParser (Level_Ed.00423830)
Called from: function BMP_Reader (Level_Ed.004233F0) +0C6h
Arg1: File handle to the BMP file opened by the calling function,
Arg2: Pointer to a lot of empty space on the stack, reserved by the calling function (roughly 2.7kB; the exact value is constant and very specific),
Notes:
- seeks to the beginning of the BMP file (Kernel32._lseek),
- reads the first 14 bytes (the BMP descriptor; Kernel32._lread),
- checks if that's actually a real, valid BMP file,
- then loads the next 40 bytes of the BMP header and checks if read was successful,
- compares the length of the header (read from the BMP file) with 0Ch (Why? Is it something related to .PCX import?),
- checks the # of bits per pixels of the .BMP,
- calculates some values which it then places in a struct on the stack,
- reads the offset to the start of .BMP pixel data and ._lseek's to that point before returning,
Return values on the stack:
- return address + the 2 arguments (cleaned up by RETN 8 ),
- pointer to ASCII string "bmp"
- pointer to ASCII string "pcc"
- pointer to some empty memory space in the block containing the .DEF data for the currently loaded databases (!NEEDS FURTHER INVESTIGATION!),
- (unknown DWORD value, but not any valid pointer; also needs further investigation),
- 0 at return time (later the handle of the allocated memory for the BMP pixel data gets placed here),
- file handle to opened BMP file (yes, it's a duplicate of the handle that BMP_HeaderParser had been called with),
- 0 at return time (later the pointer to the allocated memory for the BMP pixel data gets placed here),
- horizontal width of the texture data, in pixels,
- vertical height of the texture data, in pixels,
- horizontal width of the texture data, in bytes,
- # of bits per pixel,
- unknown DWORD value (always 0 if importing a .BMP),
- unknown DWORD value (always 0FFFFFFFFh if importing a .BMP),
- A LOT of zeros (the rest of the stack space reserved by the calling function was not used at all)!
function TextureDefLoader (Level_Ed.00469ED0)
Called from: function BMP_Reader (Level_Ed.004233F0) +3DFh
Arg1: Pointer to the location of the full ASCII file path on the stack,
Arg2: Pointer to where the data should be written to (immediately following the previous texture metadata struct offset 40h),
Arg3: Unknown (always 100h when called from Level_Ed.004233F0),
Notes:
- does not seem to do anything of real importance beyond just writing the full pathname to the location specified,
- there's some extra code for handling cases such as files being on network drives, but in any case this data is only used for the .DEF file texture descriptors.
function BitmapBitnessConverter (Level_Ed.00421BB0)
Called from: Level_Ed.00426860 +677h
Arg_ECX: Pointer to a location where the texture metadata (width, height, BPP, etc.) should be read from (NOTE: this pertains to the source bitmap!),
Arg1: Pointer to memory block containing database metadata; specifically, it points to the beginning of the currently loaded palette data (taken verbatim from the .PAL file, minus the header) - note that this appears to always be 0 when exporting 16-bit bitmaps!,
Arg2: Number of bits per pixel of the target bitmap,
Notes:
- compares the BPP of the source and target bitmaps,
- if they match, returns 0 and does nothing else,
- otherwise
(...)
- if the bitness of the source bitmap is lower than the target's, it allocates the right amount of memory, and deallocates the previous buffer after completing the conversion,
- updates the struct on the stack with the handle to the new buffer, if applicable,
- the value it writes at Arg_EAX+24h is a pointer to the memory buffer (since it's GMEM_FIXED)!
(...)
--------------------------------------------
The texture export codepath:
function TextureExporter (Level_Ed.00422DF0)
Called from: Level_Ed.004274A5 (if exporting texture manually from the menu)
Arg_ECX: Pointer to a location where the texture metadata (width, height, BPP, etc.) should be read from,
Arg1: Handle to a window (but which - the calling one, presumably?),
Arg2: Pointer to the path+filename for the texture being exported (always 0 when exporting manually from the menu),
Notes:
- checks if Arg2 is nonzero,
- if it's 0, brings up the dialog prompting the user to select a location that the exported texture should be saved to,
- if Arg2 was nonzero, loads the path from the pointer provided and checks if the extension is .bmp,
- again checks if the extension is .bmp,
- if yes, calls function TextureExportHandler; otherwise it bails out (showing an error message if the extension was .pcx instead).
function TextureExportHandler (Level_Ed.00422F90)
Called from: Level_Ed.00422DF0 +185h
Arg_ECX: Pointer to a location where the texture metadata (width, height, BPP, etc.) should be read from,
Arg1: Handle to a window (but which - the calling one, presumably?),
Arg2: Pointer to the path+filename for the texture being exported,
Notes:
- checks if the texture has an integrated alpha channel (FLAGS.1 = True),
- bails out with an error message if that's the case,
- otherwise, calls function TextureExportMemAllocator (which creates a temporary copy of the source texture),
- then calls function BitmapBitnessConverter (which allocates more memory if required for the conversion result to fit),
- bails out with an error message if the texture bitness conversion failed for any reason,
- otherwise, creates a new file using Kernel32.CreateFileA,
- bails out with an error message if the file creation attempt failed,
- calculates the values that need to be written to the .BMP header,
- writes the .BMP file header in 2 separate operations, using Kernel32.WriteFile,
- locks the export pixel data buffer using Kernel32.GlobalLock,
- copies the data from the source to destination buffer, flipping the order of the color bytes,
- writes the contents of the destination buffer to the .BMP file, using Kernel32.WriteFile,
- unlocks the export pixel data buffer using Kernel32.GlobalUnlock,
- closes the opened file with Kernel32.CloseHandle,
- calls function TextureExportMemDeallocator,
- returns 0 if the file was exported successfully.
function TextureExportMemAllocator (Level_Ed.004214F0)
Arg_ECX: Pointer to some space on the stack where the struct for the new temporary (converted for export) texture should be written to (NOTE: this struct is 240h bytes in size!)
Arg1: Pointer to a location where the texture metadata (for the texture being exported!) should be read from (NOTE: this is the pointer to the source texture!),
Notes:
- the newly allocated buffer is the same size as the source texture,
- the pixel data is then copied verbatim,
- the value it writes at Arg_EAX+24h is a handle to the memory buffer (not a pointer)!
function TextureExportMemDeallocator (Level_Ed.004214F0)
Arg_ECX: Pointer to a location where the texture metadata should be read from (NOTE: when exporting a texture, this is the pointer to the temporary texture that got converted to the output .BMP pixel format!)
Notes:
- reads the texture metadata and deallocates the buffers used for the primary (and alpha, if it exists) texture pixel data,
- if this is a texture that is being deleted from a database, additionally calls some DirectDraw function to deal with that.