Skip to content

Instantly share code, notes, and snippets.

@leovoel
Last active January 7, 2024 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leovoel/399911503ee8dbce7ff0c54593d3eae7 to your computer and use it in GitHub Desktop.
Save leovoel/399911503ee8dbce7ff0c54593d3eae7 to your computer and use it in GitHub Desktop.
# USAGE
# =====
# save this code into a file called rcru.bms.
#
# then, for saving sounds, execute QuickBMS like so:
#
# quickbms rcru.bms audio.rcru audio
#
# this will dump ogg files in the "audio" folder.
#
# for saving sprites, you can:
#
# - save all textures.
# see `startfunction SAVE_TEXTURE`
# - save all sprites (texture slices)
# see `startfunction SAVE_TEXTURE_SLICE`
#
# if you want exported files to be saved as png, you'll need ImageMagick
# (https://imagemagick.org/) installed and available on your PATH.
# this is only set up for windows, though.
#
# once you have all that set up, you can use it in the same way as shown above:
#
# quickbms rcru.bms textures.rcru textures
#
# ignore the comments below, they're details of the script.
# MEMORY FILE USAGE
# =================
# for texture archives:
# - MEMORY_FILE1: decompressed archive
# - MEMORY_FILE2: texture_bundle texture_bundles[BUNDLE_COUNT]
# - MEMORY_FILE3: uint32 texture_palette[PALETTE_COLOR_COUNT]
# - MEMORY_FILE4: uint32 decompressed_texture[TEXTURE_PIXEL_COUNT]
# - MEMORY_FILE5: uint32 decompressed_textures[{pixel count of all textures}]
# - MEMORY_FILE6: texture_info texture_array[TEXTURE_COUNT]
# - MEMORY_FILE7: uint32 texture_slice[SLICE_PIXEL_COUNT]
# - MEMORY_FILE8: c code for rle, slicing, etc.
#
# for audio archives:
# - MEMORY_FILE1: decompressed archive
#
# with every array intended to be filled using putvarchr, there's an
# accompanying *_POINTER variable, which is supposed to be incremented on every
# write with the size of what was just written. cumbersome, yes.
# INTERNAL STRUCTURES
# ===================
# texture_bundle
# uint32 data_offset
# uint32 name_offset
# uint32 name_length
# 12 bytes in total
#
# texture_info
# uint32 width
# uint32 height
# uint32 offset
# uint32 size
# 16 bytes in total
idstring 0 "RCRU "
get ARCHIVE_TYPE_NAME string 0
get ARCHIVE_TYPE_NUMBER byte 0
if ARCHIVE_TYPE_NAME == "Audio "
callfunction CHECK_ARCHIVE_TYPE_NUMBER 1 ARCHIVE_TYPE_NUMBER 5
get VORBIS_OFFSET long 0
savepos TEMPORARY_OFFSET 0
math VORBIS_OFFSET + TEMPORARY_OFFSET
callfunction DECOMPRESS_ARCHIVE 1
get SOUND_COUNT long MEMORY_FILE1
set SOUND_DATA_OFFSET long 0
for SOUND_INDEX = 0 < SOUND_COUNT
# get length of this sound's name
callfunction READ_7_BIT_ENCODED_INT 1
set SOUND_NAME_LENGTH long R7BEI_RET
# get sound name
getdstring ORIGINAL_SOUND_NAME SOUND_NAME_LENGTH MEMORY_FILE1
set SOUND_NAME_WITHOUT_EXTENSION string ORIGINAL_SOUND_NAME
string SOUND_NAME_WITHOUT_EXTENSION > ".wav"
string SOUND_NAME P "%SOUND_NAME_WITHOUT_EXTENSION%.ogg"
# get offset of the next sound
get NEXT_SOUND_DATA_OFFSET long MEMORY_FILE1
# calculate size of this sound
set SOUND_DATA_SIZE long NEXT_SOUND_DATA_OFFSET
math SOUND_DATA_SIZE - SOUND_DATA_OFFSET
math SOUND_DATA_OFFSET + VORBIS_OFFSET
# maybe someone wants to use this info, so here it is
goto SOUND_DATA_OFFSET 0 SEEK_SET
get SAMPLE_COUNT long 0
get LOOP_START_IN_SAMPLES long 0
get LOOP_LENGTH_IN_SAMPLES long 0
# skip the 3 longs above
math SOUND_DATA_OFFSET + 12
math SOUND_DATA_SIZE - 12
# save sound
log SOUND_NAME SOUND_DATA_OFFSET SOUND_DATA_SIZE 0
set SOUND_DATA_OFFSET long NEXT_SOUND_DATA_OFFSET
next SOUND_INDEX
elif ARCHIVE_TYPE_NAME == "Textures "
callfunction CHECK_ARCHIVE_TYPE_NUMBER 1 ARCHIVE_TYPE_NUMBER 1
callfunction DECOMPRESS_ARCHIVE 1
get BUNDLE_COUNT long MEMORY_FILE1
# build up `texture_bundle texture_bundles[BUNDLE_COUNT]`
set TEXTURE_BUNDLES_SIZE long 12
math TEXTURE_BUNDLES_SIZE * BUNDLE_COUNT
set TEXTURE_BUNDLES_POINTER long 0
# allocate and reset position
putvarchr MEMORY_FILE2 TEXTURE_BUNDLES_SIZE 0 byte
log MEMORY_FILE2 0 0
set BUNDLE_NAMES_POINTER long BUNDLE_COUNT
math BUNDLE_NAMES_POINTER + 1 # include BUNDLE_COUNT itself
math BUNDLE_NAMES_POINTER * 4
for BUNDLE_INDEX = 0 < BUNDLE_COUNT
# the first offset is implicit
if BUNDLE_INDEX == 0
set BUNDLE_DATA_OFFSET long 0
else
get BUNDLE_DATA_OFFSET long MEMORY_FILE1
endif
# mark where in the data offset list we are
savepos TEMPORARY_OFFSET MEMORY_FILE1
# jump to where the current name is and get its length
goto BUNDLE_NAMES_POINTER MEMORY_FILE1 SEEK_SET
callfunction READ_7_BIT_ENCODED_INT 1
set BUNDLE_NAME_LENGTH long R7BEI_RET
# mark where the name starts, right after its length
savepos BUNDLE_NAME_OFFSET MEMORY_FILE1
# advance to the next name
set BUNDLE_NAMES_POINTER long BUNDLE_NAME_OFFSET
math BUNDLE_NAMES_POINTER + BUNDLE_NAME_LENGTH
# jump back to the data offset list
goto TEMPORARY_OFFSET MEMORY_FILE1 SEEK_SET
# write struct fields
putvarchr MEMORY_FILE2 TEXTURE_BUNDLES_POINTER BUNDLE_DATA_OFFSET long
math TEXTURE_BUNDLES_POINTER + 4
putvarchr MEMORY_FILE2 TEXTURE_BUNDLES_POINTER BUNDLE_NAME_OFFSET long
math TEXTURE_BUNDLES_POINTER + 4
putvarchr MEMORY_FILE2 TEXTURE_BUNDLES_POINTER BUNDLE_NAME_LENGTH long
math TEXTURE_BUNDLES_POINTER + 4
next BUNDLE_INDEX
set MEMORY_FILE8 string "
/* flimsy. */
unsigned long rcru_rle(
unsigned char *input,
unsigned char *output,
unsigned long texture_pixel_count,
unsigned char *palette_input
) {
unsigned long *output_array = (unsigned long *)(output);
unsigned long *end = output_array + texture_pixel_count;
unsigned long *palette_array = (unsigned long *)(palette_input);
unsigned char *input_start = input;
unsigned long color = 0;
while (output_array < end) {
unsigned char input_byte = *input++;
if ((input_byte & 0x80) == 0) {
if ((input_byte & 0x7E) != 0x7E) {
color = *(palette_array + input_byte);
} else {
color = (unsigned long)(*input++);
color |= (*input++) << 8ul;
color |= (*input++) << 16ul;
if ((input_byte & 1) == 0) {
color |= 0xFF << 24ul;
} else {
color |= (*input++) << 24ul;
}
}
*output_array++ = color;
} else {
unsigned long repeat_count = input_byte & 0x3F;
if ((input_byte & 0x40) != 0) {
repeat_count <<= 8ul;
repeat_count |= *input++;
}
repeat_count += 2;
while (repeat_count != 0) {
repeat_count--;
*output_array++ = color;
}
}
}
return (unsigned long)(input - input_start);
}
int clamp(int x, int a, int b) {
if (x < a) {
return a;
} else if (x > b) {
return b;
} else {
return x;
}
}
void rcru_slice(
unsigned char *input,
unsigned char *output,
int texture_width,
int texture_height,
int rectangle_x,
int rectangle_y,
int rectangle_width,
int rectangle_height
) {
unsigned long *input_array = (unsigned long *)(input);
unsigned long *output_array = (unsigned long *)(output);
int x0 = clamp(rectangle_x, 0, texture_width);
int y0 = clamp(rectangle_y, 0, texture_height);
int x1 = clamp(x0 + rectangle_width, 0, texture_width);
int y1 = clamp(y0 + rectangle_height, 0, texture_height);
int i;
int j;
for (j = y0; j < y1; j++) {
for (i = x0; i < x1; i++) {
*output_array++ = *(input_array + i + (j * texture_width));
}
}
}
"
# extract all textures from each bundle
set BUNDLE_DATA_BASE_OFFSET long BUNDLE_NAMES_POINTER
for BUNDLE_INDEX = 0 < BUNDLE_COUNT
# get struct fields for this bundle
get BUNDLE_DATA_OFFSET long MEMORY_FILE2
get BUNDLE_NAME_OFFSET long MEMORY_FILE2
get BUNDLE_NAME_LENGTH long MEMORY_FILE2
set BUNDLE_DATA_ORIGINAL_OFFSET long BUNDLE_DATA_OFFSET
math BUNDLE_DATA_OFFSET + BUNDLE_DATA_BASE_OFFSET
# get bundle name
goto BUNDLE_NAME_OFFSET MEMORY_FILE1 SEEK_SET
getdstring BUNDLE_NAME BUNDLE_NAME_LENGTH MEMORY_FILE1
# parse bundle data
goto BUNDLE_DATA_OFFSET MEMORY_FILE1 SEEK_SET
get TEXTURE_COUNT byte MEMORY_FILE1
get RECTANGLE_COUNT short MEMORY_FILE1
# build up `texture_info texture_array[TEXTURE_COUNT]`
set TEXTURE_ARRAY_SIZE long 16
math TEXTURE_ARRAY_SIZE * TEXTURE_COUNT
set TEXTURE_ARRAY_POINTER long 0
# allocate and reset position
putvarchr MEMORY_FILE6 TEXTURE_ARRAY_SIZE 0 byte
log MEMORY_FILE6 0 0
set TEXTURE_OFFSET long 0
for TEXTURE_INDEX = 0 < TEXTURE_COUNT
get TEXTURE_WIDTH short MEMORY_FILE1
get TEXTURE_HEIGHT short MEMORY_FILE1
set TEXTURE_PIXEL_COUNT long TEXTURE_WIDTH
math TEXTURE_PIXEL_COUNT * TEXTURE_HEIGHT
# build up `uint32 decompressed_texture[TEXTURE_PIXEL_COUNT]`
set TEXTURE_SIZE long 4
math TEXTURE_SIZE * TEXTURE_PIXEL_COUNT
# allocate
putvarchr MEMORY_FILE4 TEXTURE_SIZE 0 byte
# @TODO: reset position? all examples i can find omit this
get PALETTE_COLOR_COUNT byte MEMORY_FILE1
# build up `uint32 texture_palette[PALETTE_COLOR_COUNT]`
set PALETTE_SIZE long PALETTE_COLOR_COUNT
math PALETTE_SIZE + 1 # first palette entry is implicitly 0
math PALETTE_SIZE * 4
set PALETTE_POINTER long 0
# allocate and reset position
putvarchr MEMORY_FILE3 PALETTE_SIZE 0 byte
log MEMORY_FILE3 0 0
# write implicit palette entry
putvarchr MEMORY_FILE3 PALETTE_POINTER 0x00000000 long
math PALETTE_POINTER + 4
for I = 0 < PALETTE_COLOR_COUNT
set COLOR long 0xFF000000
get COLOR_CHANNELS threebyte MEMORY_FILE1
math COLOR | COLOR_CHANNELS
# write palette entry
putvarchr MEMORY_FILE3 PALETTE_POINTER COLOR long
math PALETTE_POINTER + 4
next I
# decompress texture
goto 0 MEMORY_FILE3 SEEK_SET
goto 0 MEMORY_FILE4 SEEK_SET
calldll MEMORY_FILE8 "rcru_rle" tcc RET MEMORY_FILE1 MEMORY_FILE4 TEXTURE_PIXEL_COUNT MEMORY_FILE3
goto RET MEMORY_FILE1 SEEK_CUR
# save texture
string TEXTURE_SIZE_STRING P "%TEXTURE_WIDTH%x%TEXTURE_HEIGHT%"
string TEXTURE_FILENAME_WITHOUT_EXTENSION P "%BUNDLE_NAME%/texture%TEXTURE_INDEX%__%TEXTURE_SIZE_STRING%"
callfunction SAVE_TEXTURE 1
# add texture to `uint32 decompressed_textures[{pixel count of all textures}]`
if TEXTURE_INDEX != 0
append # enable
endif
log MEMORY_FILE5 0 TEXTURE_SIZE MEMORY_FILE4
if TEXTURE_INDEX != 0
append # disable
endif
# write struct fields
putvarchr MEMORY_FILE6 TEXTURE_ARRAY_POINTER TEXTURE_WIDTH long
math TEXTURE_ARRAY_POINTER + 4
putvarchr MEMORY_FILE6 TEXTURE_ARRAY_POINTER TEXTURE_HEIGHT long
math TEXTURE_ARRAY_POINTER + 4
putvarchr MEMORY_FILE6 TEXTURE_ARRAY_POINTER TEXTURE_OFFSET long
math TEXTURE_ARRAY_POINTER + 4
putvarchr MEMORY_FILE6 TEXTURE_ARRAY_POINTER TEXTURE_SIZE long
math TEXTURE_ARRAY_POINTER + 4
math TEXTURE_OFFSET + TEXTURE_SIZE
next TEXTURE_INDEX
# extract slices from textures
if TEXTURE_COUNT > 1
# mark where the texture index list starts
savepos INDICES_BASE_OFFSET MEMORY_FILE1
# mark where the rectangle list starts
set RECTANGLES_BASE_OFFSET long INDICES_BASE_OFFSET
math RECTANGLES_BASE_OFFSET + RECTANGLE_COUNT
for RECTANGLE_INDEX = 0 < RECTANGLE_COUNT
# calculate where we are in the texture index list
set INDEX_OFFSET long RECTANGLE_INDEX
math INDEX_OFFSET + INDICES_BASE_OFFSET
# calculate where we are in the rectangle list
set RECTANGLE_OFFSET long RECTANGLE_INDEX
math RECTANGLE_OFFSET * 8
math RECTANGLE_OFFSET + RECTANGLES_BASE_OFFSET
# get current texture index
goto INDEX_OFFSET MEMORY_FILE1 SEEK_SET
get TEXTURE_INDEX byte MEMORY_FILE1
# get current rectangle
goto RECTANGLE_OFFSET MEMORY_FILE1 SEEK_SET
get RECTANGLE_X short MEMORY_FILE1
get RECTANGLE_Y short MEMORY_FILE1
get RECTANGLE_W short MEMORY_FILE1
get RECTANGLE_H short MEMORY_FILE1
# pick the right element from texture_array
set TEXTURE_INFO_OFFSET long TEXTURE_INDEX
math TEXTURE_INFO_OFFSET * 16
# get struct fields for this texture_array element
goto TEXTURE_INFO_OFFSET MEMORY_FILE6 SEEK_SET
get TEXTURE_WIDTH long MEMORY_FILE6
get TEXTURE_HEIGHT long MEMORY_FILE6
get TEXTURE_OFFSET long MEMORY_FILE6
get TEXTURE_SIZE long MEMORY_FILE6
set SLICE_PIXEL_COUNT long RECTANGLE_W
math SLICE_PIXEL_COUNT * RECTANGLE_H
# build up `uint32 texture_slice[SLICE_PIXEL_COUNT]`
set SLICE_SIZE long SLICE_PIXEL_COUNT
math SLICE_SIZE * 4
# allocate
putvarchr MEMORY_FILE7 SLICE_SIZE 0 byte
# @TODO: reset position? all examples i can find omit this
# slice texture
goto TEXTURE_OFFSET MEMORY_FILE5 SEEK_SET
goto 0 MEMORY_FILE7 SEEK_SET
calldll MEMORY_FILE8 "rcru_slice" tcc "" MEMORY_FILE5 MEMORY_FILE7 TEXTURE_WIDTH TEXTURE_HEIGHT RECTANGLE_X RECTANGLE_Y RECTANGLE_W RECTANGLE_H
# save texture slice
string RECTANGLE_SIZE_STRING P "%RECTANGLE_W%x%RECTANGLE_H%"
string SLICE_FILENAME_WITHOUT_EXTENSION P "%BUNDLE_NAME%/texture%TEXTURE_INDEX%_slice%RECTANGLE_INDEX%__%RECTANGLE_SIZE_STRING%"
callfunction SAVE_TEXTURE_SLICE 1
next RECTANGLE_INDEX
elif TEXTURE_COUNT == 1
set TEXTURE_INDEX long 0
goto 0 MEMORY_FILE6 SEEK_SET
get TEXTURE_WIDTH long MEMORY_FILE6
get TEXTURE_HEIGHT long MEMORY_FILE6
get TEXTURE_OFFSET long MEMORY_FILE6
get TEXTURE_SIZE long MEMORY_FILE6
for RECTANGLE_INDEX = 0 < RECTANGLE_COUNT
get RECTANGLE_X short MEMORY_FILE1
get RECTANGLE_Y short MEMORY_FILE1
get RECTANGLE_W short MEMORY_FILE1
get RECTANGLE_H short MEMORY_FILE1
set SLICE_PIXEL_COUNT long RECTANGLE_W
math SLICE_PIXEL_COUNT * RECTANGLE_H
# build up `uint32 texture_slice[SLICE_PIXEL_COUNT]`
set SLICE_SIZE long SLICE_PIXEL_COUNT
math SLICE_SIZE * 4
# allocate
putvarchr MEMORY_FILE7 SLICE_SIZE 0 byte
# @TODO: reset position? all examples i can find omit this
# slice texture
goto TEXTURE_OFFSET MEMORY_FILE5 SEEK_SET
goto 0 MEMORY_FILE7 SEEK_SET
calldll MEMORY_FILE8 "rcru_slice" tcc "" MEMORY_FILE5 MEMORY_FILE7 TEXTURE_WIDTH TEXTURE_HEIGHT RECTANGLE_X RECTANGLE_Y RECTANGLE_W RECTANGLE_H
# save texture slice
string RECTANGLE_SIZE_STRING P "%RECTANGLE_W%x%RECTANGLE_H%"
string SLICE_FILENAME_WITHOUT_EXTENSION P "%BUNDLE_NAME%/texture%TEXTURE_INDEX%_slice%RECTANGLE_INDEX%__%RECTANGLE_SIZE_STRING%"
callfunction SAVE_TEXTURE_SLICE 1
next RECTANGLE_INDEX
endif
next BUNDLE_INDEX
else
print "unsupported archive"
print " ARCHIVE_TYPE_NAME: %ARCHIVE_TYPE_NAME%"
print " ARCHIVE_TYPE_NUMBER: %ARCHIVE_TYPE_NUMBER%"
cleanexit
endif
startfunction CHECK_ARCHIVE_TYPE_NUMBER
if CHECK_ARCHIVE_TYPE_NUMBER_ARG1 != CHECK_ARCHIVE_TYPE_NUMBER_ARG2
print "expected ARCHIVE_TYPE_NUMBER to be %CHECK_ARCHIVE_TYPE_NUMBER_ARG2%, but got %CHECK_ARCHIVE_TYPE_NUMBER_ARG1%"
cleanexit
endif
endfunction
startfunction DECOMPRESS_ARCHIVE
savepos ARCHIVE_DATA_OFFSET 0
get ARCHIVE_SIZE asize 0
math ARCHIVE_SIZE - ARCHIVE_DATA_OFFSET
comtype gzip
clog MEMORY_FILE1 ARCHIVE_DATA_OFFSET ARCHIVE_SIZE ARCHIVE_SIZE 0
endfunction
startfunction READ_7_BIT_ENCODED_INT
# https://github.com/microsoft/referencesource/blob/e0bf122d0e52a42688b92bb4be2cfd66ca3c2f07/mscorlib/system/io/binaryreader.cs#L582-L599
set R7BEI_COUNT long 0
set R7BEI_SHIFT long 0
set R7BEI_B byte 0
do
if R7BEI_SHIFT == 35
print "bad 7-bit int32:"
print " R7BEI_COUNT: %R7BEI_COUNT%"
print " R7BEI_SHIFT: %R7BEI_SHIFT%"
print " R7BEI_B: %R7BEI_B%"
cleanexit
endif
get R7BEI_B byte MEMORY_FILE1 # can't parameterize this, unfortunately
set R7BEI_TMP1 byte R7BEI_B
math R7BEI_TMP1 & 0x7F
math R7BEI_TMP1 << R7BEI_SHIFT
math R7BEI_COUNT | R7BEI_TMP1
math R7BEI_SHIFT + 7
set R7BEI_TMP2 byte R7BEI_B
math R7BEI_TMP2 & 0x80
while R7BEI_TMP2 != 0
set R7BEI_RET long R7BEI_COUNT
endfunction
startfunction SAVE_TEXTURE
# uncomment the two lines below to save a "raw" file containing all the
# pixels (4 bytes each, rgba) of a texture, with the dimensions included
# in the filename.
# -------------------------------------------------------------------------
# string RAW_TEXTURE_FILENAME P "%TEXTURE_FILENAME_WITHOUT_EXTENSION%.raw"
# log RAW_TEXTURE_FILENAME 0 TEXTURE_SIZE MEMORY_FILE4
# -------------------------------------------------------------------------
# or uncomment the three lines below to feed that file into imagemagick
# and make it spit out a png (windows only).
# -------------------------------------------------------------------------
# string PNG_TEXTURE_FILENAME P "%TEXTURE_FILENAME_WITHOUT_EXTENSION%.png"
# comtype EXECUTE "convert.exe -size %TEXTURE_SIZE_STRING% -depth 8 RGBA:#INPUT# PNG32:#OUTPUT#"
# clog PNG_TEXTURE_FILENAME 0 TEXTURE_SIZE TEXTURE_SIZE MEMORY_FILE4
endfunction
startfunction SAVE_TEXTURE_SLICE
# uncomment the two lines below to save a "raw" file containing all the
# pixels (4 bytes each, rgba) of a texture slice, with the dimensions
# included in the filename.
# -------------------------------------------------------------------------
string RAW_SLICE_FILENAME P "%SLICE_FILENAME_WITHOUT_EXTENSION%.raw"
log RAW_SLICE_FILENAME 0 SLICE_SIZE MEMORY_FILE7
# -------------------------------------------------------------------------
# or uncomment the three lines below to feed that file into imagemagick
# and make it spit out a png (windows only).
# -------------------------------------------------------------------------
# string PNG_SLICE_FILENAME P "%SLICE_FILENAME_WITHOUT_EXTENSION%.png"
# comtype EXECUTE "convert.exe -size %RECTANGLE_SIZE_STRING% -depth 8 RGBA:#INPUT# PNG32:#OUTPUT#"
# clog PNG_SLICE_FILENAME 0 SLICE_SIZE SLICE_SIZE MEMORY_FILE7
endfunction
@leovoel
Copy link
Author

leovoel commented Dec 10, 2023

Hmm I probably messed up something then (been a while...I'm not using Windows anymore so testing this again would be a bit of a pain), it should output files with an extension...but yeah not that it makes much of a difference. Good to hear it worked for you.

@Triforceriku
Copy link

How do i uncomment things?
I'm new to this process

@leovoel
Copy link
Author

leovoel commented Jan 7, 2024

"Uncommenting" means removing the # that precedes a line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment