====== Falcon 4 IDX/RSC file pairs====== [[https://www.pmctactical.org/forum/viewforum.php?f=47|Falcon 4.0 Forum]], [[:falcon4|Falcon 4 Home]], [[falcon4:campaign|Falcon 4 Campaign]], [[falcon4:cockpits|Falcon 4 Cockpits]], [[falcon4:database|Falcon 4 Database]], [[falcon4:file_formats|Falcon 4 File Formats]], [[falcon4:srtm|Falcon 4 SRTM Terrain]], [[falcon4:terrain|Falcon 4 Terrain]], [[falcon4:textures|Falcon 4 Textures]], [[falcon4:tools|Falcon 4 Tools]] .RSC files in Falcon are "resource bundles". A "resource bundle" is a type of file that can contain one or more (embedded) binary files, of varying types. For example, they can contain images, sounds, and/or miscellaneous binary content. A single .RSC file can (and often does) contain multiple resources, potentially of mixed type (i.e. a resource bundle file could contain several images, several sounds, and several binary files, all at once). The correspondingly-named .IDX file (located in the same folder as the .RSC file in question), stores an index of the contents of the .RSC file. This index provides offset information to the location within the .RSC file's DATA section, where a specific resource's raw binary data begins, as well as the size, in bytes, of the resource's binary data, and some additional metadata describing the type of resource that can be found at that location. The interpretation of the .RSC file's binary data, therefore, is dependent on the information provided in the corresponding .IDX file. **NOTE:** The .IDX file extension in Falcon refers to a variety of different kinds of index files. The formats described below ONLY APPLY to .IDX files that index a corresponding .RSC (resource bundle) file. Other types of .IDX files ARE NOT described below. **NOTE ON DATA TYPES AND ENDIAN-NESS:** The data type "long" refers to a 32-bit (double-WORD) data type. A "short" refers to a 16-bit (double-byte, a.k.a single WORD) data type. The byte order on disk is little-endian, so, for example, the following four sequential bytes on disk { ''%%0xDD%%'', ''%%0xC8%%'', ''%%0x3F%%'', ''%%0x02%%'' }, when treated as a "long", would translate to the value ''%%0x023FC8DD%%'' (the reverse sequence). ===== .IDX FILE FORMAT (for .IDX files that index a corresponding .RSC file) ===== ==== .IDX FILE HEADER SECTION ==== ^Field Name ^Offset (Hex) ^Length (Bytes) ^Data Type ^Description^ |Size | ''%%0x00%%'' |4 |long |The size (in bytes) of the data section in this .IDX file (i.e. the file length minus the header length)| |Version | ''%%0x04%%'' |4 |long |The version number of the file format being used for this .IDX file\\ (must match the version number of the corresponding .RSC file)\\ example: ''%%0x023fc8dd%%''| Immediately following the header section in the .IDX file, comes the data section. Each record in the data section in the .IDX describes one resource that can be found in the corresponding .RSC file. ==== .IDX FILE DATA SECTION ==== The DATA section in the .IDX file contains one DATA record per embedded resource in the corresponding .RSC file. Each DATA record in the .IDX file starts with 2 common fields (ResourceType and ResourceID). The rest of the DATA record (and hence, the size of an individual data record) depends on the specific ResourceType that is specified for that record. === INDIVIDUAL .IDX FILE DATA RECORD -- COMMON FIELDS === NOTE: all field offsets given in the Offset column below, are relative to the start of the individual data record within the .IDX file, not the start of the .IDX file itself. \\ For example, the **ResourceType** field for the first DATA record in the .IDX file begins at **relative offset** = ''%%0x00%%'' (**absolute offset** = ''%%0x08%%'', for the first DATA record in the .IDX file). ---------------------------------------------------- ^Field^Offset(Hex)^Length (Bytes)^Data Type^Description^ |ResourceType|''%%0x00%%''|4|long|The type of resource that this record points to.\\ Values for the ResourceType field can be one of the following:\\ \\ **Hex Value**%% %%**Description**\\ **''%%0x64%%''** Image resource (i.e. an embedded bitmap)\\ **''%%0x65%%''** Sound resource (i.e. an embedded windows .WAV file)\\ **''%%0x66 %%''** Flat file resource (i.e. embedded arbitrary binary content)| |ResourceID|''%%0x04%%''|32|char[32]|A ''%%NULL%%''-terminated ASCII string that identifies this resource.| |NOTE: ALL fields that follow, inside of a single .IDX data record, depend on the specific resource type that is being described by that record.||||| === ADDITIONAL FIELDS FOR RESOURCE TYPE = 0x64 (Image resource) === **TOTAL RECORD LENGTH:** (including the ResourceType and ResourceID fields): 60 bytes ^Field Name^Offset (Hex)^Length (Bytes)^Data Type^Description^ |Flags|0x24|4|long|Bit flags that describe the image format.\\ \\ Bitmasks: \\ \\ **EightBit** = ''%%0x00000001%%'' Image contains a 256-value (8-bit) palette; each image pixel is described by a single byte representing an index into the color palette array (stored separately).\\ \\ **SixteenBit** = ''%%0x00000002%%'' Each image pixel is described by 2 bytes, which, taken together as a 16-bit integer, provide 16 bits of color information per pixel. When this flag is set, no separate palette array exists.\\ \\ **UseColorKey** = ''%%0x40000000%%'' The image uses the first color in the palette (or magenta, for non-paletted images) as the color key (transparency color) -- any pixels using that color should be rendered as transparent| |CenterX|''%%0x28%%''|2|short|Center X pixel (not used??)| |CenterY|''%%0x2A%%''|2|short|Center Y pixel (not used??)| |Width|''%%0x2C%%''|2|short|Width of the image, in pixels| |Height|''%%0x2E%%''|2|short|Height of the image, in pixels| |ImageOffset|''%%0x30%%''|4|long|Offset (in bytes) to the start of the image's pixel data, relative to the start of the .RSC file's DATA section. \\ \\ The actual size of the data starting at that location will be:\\ \\ (Width * Height) bytes long (for an 8-bit paletted image),\\ **or** \\ (Width * Height * 2) bytes long (for a 16-bit image)\\ \\ **Pixel data structure**\\ The first byte in the pixel data array in the .RSC file represents the upper-left pixel of the image. \\ The last byte in the pixel data array in the .RSC file represents the lower-right pixel of the image. \\ The pixel array is stored with the first (top) row's worth of columns first (i.e. byte 0=(row 0, column 0); byte 1=(row 0, column 1), byte M = (row 0, column =width)... (byte N = row=height, column=width)| |PaletteSize|''%%0x34%%''|4|long|Number of entries in the image's palette. Each palette entry consists of 2 bytes (16 bits of color info per palette entry). \\ **NOTE:** PaletteSize = 0 for non-paletted images.| |PaletteOffset|''%%0x38%%''|4|long|Offset (in bytes) to the start of the image's color palette data array, relative to the start of the the .RSC file's DATA section. \\ \\ To convert the 16-bit color values from the palette data array (or from the raw pixel data, in the case of non-paletted images) to 32-bit ARGB color values, use the following (pseudocode): \\ byte A = 0xFF; //alpha byte byte R = (thisPixelPaletteEntryValue & 0x7C00) >> 7; //red byte byte G = (thisPixelPaletteEntryValue & 0x3E0) >> 2; //green byte byte B = (thisPixelPaletteEntryValue & 0x1F) << 3; //blue byte \\ The 16-bit color data in the palette is actually only using 15 bits (5 for red, 5 for green, and 5 for blue). \\ * The low-order (rightmost) 5 bits are the blue bits.\\ * The next higher-order (middle) 5 bits are the green bits.\\ * The next higher-order 5 bits after that (i.e. the leftmost 5 bits) are the red bits.\\ * The high-order bit is not used. \\ \\ The conversion (described above) works by first masking off the relevant bits for a particular color, and then shifting those bits left or right so that each color's bits occupy the 5 most-significant bits in that color's respective byte. \\ \\ After performing the above conversion, you can then combine all the component bytes togther into a single 32-bit integer, as follows (pseudocode): \\ long argb = ((A << 24) | (R << 16) | (G <<8) | B); | === ADDITIONAL FIELDS FOR RESOURCE TYPE = 0x65 (Sound resource) === **TOTAL RECORD LENGTH:** (including the ResourceType and ResourceID fields): 52 bytes ^Field Name^Offset (Hex)^Length (Bytes)^Data Type^Description^ |Flags|''%%0x24%%''|4|long|Bit flags that describe the sound format. \\ \\ Bitmasks:\\ **???**| |Channels|''%%0x28%%''|2|short|Number of channels of audio data in the corresponding sound file.\\ \\ * 1 = mono, \\ * 2 = stereo\\ \\ **NOTE:** This is actually redundant, because this information is also contained in the resource's payload within the .RSC file's DATA section.| |SoundType|''%%0x2A%%''|2|short|This resource's Windows .WAV file encoding type. This field actually contains the value of the **wFormatTag** member of the .WAV file's **WAVEFORMATEX** structure.\\ \\ **NOTE:** This is actually redundant, because this information is also contained in the resource's payload within the .RSC file's DATA section.| |Offset|''%%0x2C%%''|4|long|Offset to the start of the sound resource's .WAV file binary data relative to the start of the .RSC file's DATA section.| |HeaderSize|''%%0x30%%''|4|long|Size, in bytes, of the sound file's header section, located inside the resource's binary data payload within the .RSC file's DATA section.\\ \\ **NOTE:** The length (in bytes) of the sound resource's actual payload within the .RSC file's DATA section can be found by looking at the integer value occupying the 4 bytes starting at this resource's .Offset+4 in the .RSC file's DATA section itself. You need to add 8 to that value to get the total embedded .WAV file size, in bytes, starting at the .Offset itself. \\ \\ **Example:** \\ if the sound resource's Offset was set to ''%%0x00%%'', you would seek to location ''%%0x04%%'' (Offset+4) in the .RSC file's DATA section, then read the next 4 bytes into a "long" integer. Add 8 to the value of that integer, and that's the number of bytes, starting at this resource's .Offset relative to the start of the .RSC file's DATA section, that make up the entire .WAV file binary for this resource.| === ADDITIONAL FIELDS FOR RESOURCE TYPE = 0x66 (Flat [i.e. binary] resource) === **TOTAL RECORD LENGTH:** (including the ResourceType and ResourceID fields): 44 bytes ^Field Name^Offset (Hex)^Length (Bytes)^Data Type^Description^ |Offset|''%%0x24%%''|4|long|Byte offset of the flat resource's binary data relative to the start of the corresponding .RSC file's DATA section| |Size|''%%0x28%%''|4|long|Size, in bytes, of the flat resource's contents| ===== .RSC file format ===== ==== .RSC FILE HEADER SECTION ==== ^Field Name^Offset (Hex)^Length (Bytes)^Data Type^Description^ |Size|''%%0x00%%''|4|long|The size (in bytes) of the data section in this .RSC file (i.e. the file length minus the header length)| |Version|''%%0x04%%''|4|long|The version number of the file format being used for this .RSC file (must match the version number of the corresponding .IDX file)\\ example: ''%%0x023fc8dd%%''| ==== .RSC FILE DATA SECTION ==== Immediately following the HEADER section in the .RSC file, comes the DATA section. The DATA section extends from absolute offset ''%%0x08%%'' in the .RSC file, to the end of the file. Individual records can be extracted from the .RSC file by first parsing the corresponding .IDX file in order to understand the types of resources that the .RSC file contains data for, as well as discovering those resources' locations and sizes within the .RSC file. **NOTE:** all values of all .Offset fields within indidivual records in the .IDX file, specify offsets **relative** to the **start** of the DATA section in the .RSC file. For example, if the .IDX record specifies an offset of ''%%0x00%%'', the actual data would be located in the .RSC file starting at **absolute offset** = ''%%0x08%%'' (i.e., 0 bytes past the start of the DATA section, which itself starts at **absolute offset**=''%%0x08%%'') ===== Example Code in C# ===== The following C# class (F4Resources.F4ResourceBundleReader) illustrates how to read a Falcon Resource Bundle (.RSC file + .IDX file) at a low level. It's not optimized for speed (i.e. it uses SetPixel for setting image colors and it uses Array.Copy for copying raw binary data instead of the faster native memory bit-to-block transfer techniques), but it is more illustrative, as a result. using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Drawing; namespace F4Resources { public enum F4ResourceType : uint { Unknown = 0, ImageResource = 100, SoundResource = 101, FlatResource = 102, } public class F4ResourceBundleReader { [Flags] protected internal enum F4ResourceFlags : uint { EightBit = 0x00000001, SixteenBit = 0x00000002, UseColorKey = 0x40000000, } protected internal class F4ResourceRawDataPackage { public uint Version; public uint Size; public byte[] Data; } protected internal class F4ResourceHeader { public uint Type; public string ID = null; } protected internal class F4FlatResourceHeader : F4ResourceHeader { public uint Offset; public uint Size; } protected internal class F4ImageResourceHeader : F4ResourceHeader { public uint Flags; public ushort CenterX; public ushort CenterY; public ushort Width; public ushort Height; public uint ImageOffset; public uint PaletteSize; public uint PaletteOffset; } protected internal class F4SoundResourceHeader : F4ResourceHeader { public uint Flags; public ushort Channels; public ushort SoundType; public uint Offset; public uint HeaderSize; } protected internal class F4ResourceBundleIndex { public uint Size; public uint NumResources; public uint ResourceIndexVersion; public F4ResourceHeader[] ResourceHeaders; public F4ResourceRawDataPackage ResourceData; } private F4ResourceBundleIndex _resourceIndex = null; public virtual void Load(string resourceBundleIndexPath) { FileInfo resourceIndexFileInfo = new FileInfo(resourceBundleIndexPath); if (resourceIndexFileInfo.Exists) { byte[] bytes = new byte[resourceIndexFileInfo.Length]; using (FileStream fs = new FileStream(resourceBundleIndexPath, FileMode.Open)) { fs.Seek(0, SeekOrigin.Begin); fs.Read(bytes, 0, (int)resourceIndexFileInfo.Length); } _resourceIndex = new F4ResourceBundleIndex(); int curByte = 0; _resourceIndex.Size= BitConverter.ToUInt32(bytes, curByte); curByte += 4; _resourceIndex.ResourceIndexVersion = BitConverter.ToUInt32(bytes, curByte); curByte += 4; uint size = _resourceIndex.Size; List headers = new List(); while (size >0) { _resourceIndex.NumResources++; uint resourceType = BitConverter.ToUInt32(bytes, curByte); curByte += 4; byte[] resourceId = new byte[32]; for (int j = 0; j < 32; j++) { resourceId[j] = bytes[curByte]; curByte++; } string resourceName = Encoding.ASCII.GetString(resourceId); int nullLoc = resourceName.IndexOf('\0'); if (nullLoc > 0) { resourceName = resourceName.Substring(0, nullLoc); } else { resourceName = null; } if (resourceType == (uint)(F4ResourceType.ImageResource)) { F4ImageResourceHeader thisResourceHeader = new F4ImageResourceHeader(); thisResourceHeader.Type = resourceType; thisResourceHeader.ID = resourceName; thisResourceHeader.Flags = BitConverter.ToUInt32(bytes, curByte); curByte += 4; thisResourceHeader.CenterX = BitConverter.ToUInt16(bytes, curByte); curByte += 2; thisResourceHeader.CenterY = BitConverter.ToUInt16(bytes, curByte); curByte += 2; thisResourceHeader.Width = BitConverter.ToUInt16(bytes, curByte); curByte += 2; thisResourceHeader.Height = BitConverter.ToUInt16(bytes, curByte); curByte += 2; thisResourceHeader.ImageOffset = BitConverter.ToUInt32(bytes, curByte); curByte += 4; thisResourceHeader.PaletteSize = BitConverter.ToUInt32(bytes, curByte); curByte += 4; thisResourceHeader.PaletteOffset = BitConverter.ToUInt32(bytes, curByte); curByte += 4; headers.Add(thisResourceHeader); size -= 60; } else if (resourceType == (uint)(F4ResourceType.SoundResource)) { F4SoundResourceHeader thisResourceHeader = new F4SoundResourceHeader(); thisResourceHeader.Type = resourceType; thisResourceHeader.ID = resourceName; thisResourceHeader.Flags = BitConverter.ToUInt32(bytes, curByte); curByte += 4; thisResourceHeader.Channels = BitConverter.ToUInt16(bytes, curByte); curByte += 2; thisResourceHeader.SoundType = BitConverter.ToUInt16(bytes, curByte); curByte += 2; thisResourceHeader.Offset = BitConverter.ToUInt32(bytes, curByte); curByte += 4; thisResourceHeader.HeaderSize = BitConverter.ToUInt32(bytes, curByte); curByte += 4; headers.Add(thisResourceHeader); size -= 52; } else if (resourceType == (uint)(F4ResourceType.FlatResource)) { F4FlatResourceHeader thisResourceHeader = new F4FlatResourceHeader(); thisResourceHeader.Type = resourceType; thisResourceHeader.ID = resourceName; thisResourceHeader.Offset= BitConverter.ToUInt32(bytes, curByte); curByte += 4; thisResourceHeader.Size= BitConverter.ToUInt32(bytes, curByte); curByte += 4; headers.Add(thisResourceHeader); size -= 44; } } _resourceIndex.ResourceHeaders = headers.ToArray(); FileInfo resourceDataFileInfo = new FileInfo( Path.GetDirectoryName(resourceIndexFileInfo.FullName) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(resourceIndexFileInfo.FullName) + ".rsc"); if (resourceDataFileInfo.Exists) { bytes = new byte[resourceDataFileInfo.Length]; using (FileStream fs = new FileStream(resourceDataFileInfo.FullName, FileMode.Open)) { fs.Seek(0, SeekOrigin.Begin); fs.Read(bytes, 0, (int)resourceDataFileInfo.Length); } F4ResourceRawDataPackage rawDataPackage = new F4ResourceRawDataPackage(); curByte = 0; rawDataPackage.Size = BitConverter.ToUInt32(bytes, curByte); curByte += 4; rawDataPackage.Version = BitConverter.ToUInt32(bytes, curByte); curByte += 4; rawDataPackage.Data = new byte[rawDataPackage.Size]; for (int k = 0; k < rawDataPackage.Size; k++) { rawDataPackage.Data[k] = bytes[curByte]; curByte++; } _resourceIndex.ResourceData = rawDataPackage; } else { throw new FileNotFoundException(resourceDataFileInfo.FullName); } } else { throw new FileNotFoundException(resourceBundleIndexPath); } } public int NumResources { get { if (_resourceIndex == null) { return -1; } else { return (int)_resourceIndex.NumResources; } } } public virtual F4ResourceType GetResourceType(int resourceNum) { if (_resourceIndex == null) { return F4ResourceType.Unknown; } else { return (F4ResourceType)_resourceIndex.ResourceHeaders[resourceNum].Type; } } public virtual byte[] GetSoundResource(string resourceId) { F4SoundResourceHeader resourceHeader = FindResourceHeaderByResourceId(resourceId) as F4SoundResourceHeader; return GetSoundResource(resourceHeader); } public virtual byte[] GetSoundResource(int resourceNum) { if (_resourceIndex == null || _resourceIndex.ResourceHeaders == null || resourceNum >= _resourceIndex.ResourceHeaders.Length) { return null; } F4SoundResourceHeader resourceHeader = _resourceIndex.ResourceHeaders[resourceNum] as F4SoundResourceHeader; return GetSoundResource(resourceHeader); } protected virtual byte[] GetSoundResource(F4SoundResourceHeader resourceHeader) { if (resourceHeader == null) return null; int curByte = (int)resourceHeader.Offset; curByte += 4; uint dataSize = BitConverter.ToUInt32(_resourceIndex.ResourceData.Data, curByte); curByte += 4; byte[] toReturn = new byte[dataSize+8]; Array.Copy(_resourceIndex.ResourceData.Data, curByte-8, toReturn, 0, dataSize+8); return toReturn; } public virtual byte[] GetFlatResource(int resourceNum) { if (_resourceIndex == null || _resourceIndex.ResourceHeaders == null || resourceNum >= _resourceIndex.ResourceHeaders.Length) { return null; } F4FlatResourceHeader resourceHeader = _resourceIndex.ResourceHeaders[resourceNum] as F4FlatResourceHeader; return GetFlatResource(resourceHeader); } public virtual byte[] GetFlatResource(string resourceId) { F4FlatResourceHeader resourceHeader = FindResourceHeaderByResourceId(resourceId) as F4FlatResourceHeader; return GetFlatResource(resourceHeader); } protected virtual byte[] GetFlatResource(F4FlatResourceHeader resourceHeader) { if (resourceHeader == null) return null; byte[] bytes = new byte[resourceHeader.Size]; for (int i = 0; i < resourceHeader.Size; i++) { bytes[i] = _resourceIndex.ResourceData.Data[resourceHeader.Offset + i]; } return bytes; } public virtual Bitmap GetImageResource(string resourceId) { F4ImageResourceHeader imageHeader = FindResourceHeaderByResourceId(resourceId) as F4ImageResourceHeader; return GetImageResource(imageHeader); } public virtual Bitmap GetImageResource(int resourceNum) { if (_resourceIndex == null || _resourceIndex.ResourceHeaders == null || resourceNum >= _resourceIndex.ResourceHeaders.Length) { return null; } F4ImageResourceHeader imageHeader = _resourceIndex.ResourceHeaders[resourceNum] as F4ImageResourceHeader; return GetImageResource(imageHeader); } protected virtual Bitmap GetImageResource(F4ImageResourceHeader imageHeader) { if (imageHeader == null) return null; Bitmap toReturn = new Bitmap(imageHeader.Width, imageHeader.Height); ushort[] palette = new ushort[imageHeader.PaletteSize]; if ((imageHeader.Flags & (uint)F4ResourceFlags.EightBit) == (uint)F4ResourceFlags.EightBit) { for (int i = 0; i < palette.Length; i++) { palette[i] = BitConverter.ToUInt16(_resourceIndex.ResourceData.Data, (int)imageHeader.PaletteOffset + (i * 2)); } } int curByte = 0; for (int y = 0; y < imageHeader.Height; y++) { for (int x = 0; x < imageHeader.Width; x++) { int A = 0; int R = 0; int G = 0; int B = 0; if ((imageHeader.Flags & (uint)F4ResourceFlags.EightBit)==(uint)F4ResourceFlags.EightBit) { byte thisPixelPaletteIndex = _resourceIndex.ResourceData.Data[imageHeader.ImageOffset + curByte]; ushort thisPixelPaletteEntry = palette[thisPixelPaletteIndex]; A = 255; R = ((thisPixelPaletteEntry & 0x7C00) >> 10) << 3; G = ((thisPixelPaletteEntry & 0x3E0) >> 5) << 3; B = (thisPixelPaletteEntry & 0x1F) << 3; curByte++; } else if ((imageHeader.Flags & (uint)F4ResourceFlags.SixteenBit) == (uint)F4ResourceFlags.SixteenBit) { ushort thisPixelPaletteEntry = BitConverter.ToUInt16(_resourceIndex.ResourceData.Data, (int)(imageHeader.ImageOffset + curByte)); A = 255; R = ((thisPixelPaletteEntry & 0x7C00) >> 10) << 3; G = ((thisPixelPaletteEntry & 0x3E0) >> 5) << 3; B = (thisPixelPaletteEntry & 0x1F) << 3; curByte+=2; } toReturn.SetPixel(x, y, Color.FromArgb(A, R, G, B)); } } return toReturn; } protected virtual F4ResourceHeader FindResourceHeaderByResourceId(string resourceId) { if (_resourceIndex == null || _resourceIndex.ResourceHeaders == null || resourceId == null) { return null; } for (int i = 0; i < _resourceIndex.ResourceHeaders.Length; i++) { F4ResourceHeader thisResourceHeader = _resourceIndex.ResourceHeaders[i]; string thisResourceId = thisResourceHeader.ID; if (thisResourceId.ToLowerInvariant() == resourceId.ToLowerInvariant()) { return thisResourceHeader; } } return null; } } } The next chunk of C# example code shows the above class being used in a Windows Forms application, and illustrates how one might use the class presented in the previous example. using System; using System.Drawing; using System.Windows.Forms; using System.IO; using System.Runtime.InteropServices; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string resourceBundleIndexPath = @"C:\Microprose\Falcon4\Theaters\Vietnam\art\art\resource\select.idx"; F4Resources.F4ResourceBundleReader resourceBundleReader = new F4Resources.F4ResourceBundleReader(); resourceBundleReader.Load(resourceBundleIndexPath); for (int i = 0; i < resourceBundleReader.NumResources; i++) { Application.DoEvents(); F4Resources.F4ResourceType thisResourceType = resourceBundleReader.GetResourceType(i); switch (thisResourceType) { case F4Resources.F4ResourceType.Unknown: break; case F4Resources.F4ResourceType.ImageResource: //read an image resource (you could assign the image to a picturebox, or save it to disk, or whatever) Bitmap thisImage = resourceBundleReader.GetImageResource(i); break; case F4Resources.F4ResourceType.SoundResource: //read and play a sound file (writes sound to a temp file and calls WinAPI PlaySound to play it) byte[] thisSound = resourceBundleReader.GetSoundResource(i); string tempFile = Path.GetTempFileName(); try { using (FileStream fs = new FileStream(tempFile, FileMode.Create)) { fs.Write(thisSound, 0, thisSound.Length); fs.Flush(); fs.Close(); } PlaySound(tempFile, IntPtr.Zero, SoundFlags.SND_FILENAME); } finally { try { new FileInfo(tempFile).Delete(); } catch (IOException) { } } break; case F4Resources.F4ResourceType.FlatResource: byte[] thisFlatResource = resourceBundleReader.GetFlatResource(i); break; default: break; } } } [Flags] public enum SoundFlags : int { SND_SYNC = 0x0000, // play synchronously (default) SND_ASYNC = 0x0001, // play asynchronously SND_NODEFAULT = 0x0002, // silence (!default) if sound not found SND_MEMORY = 0x0004, // pszSound points to a memory file SND_LOOP = 0x0008, // loop the sound until next sndPlaySound SND_NOSTOP = 0x0010, // don't stop any currently playing sound SND_NOWAIT = 0x00002000, // don't wait if the driver is busy SND_ALIAS = 0x00010000, // name is a registry alias SND_ALIAS_ID = 0x00110000, // alias is a predefined ID SND_FILENAME = 0x00020000, // name is file name SND_RESOURCE = 0x00040004 // name is resource name or atom } [System.Runtime.InteropServices.DllImport("winmm.DLL", EntryPoint = "PlaySound", SetLastError = true, CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true)] private static extern bool PlaySound(string szSound, System.IntPtr hMod, SoundFlags flags); } }