1 module rsrc;
2 
3 
4 struct RSRCResource
5 {
6     int typeNum;
7     ushort resourceID;
8     bool purgeable;
9     string name = null;
10     const(ubyte)[] content;
11     ushort nameOffset;
12     uint dataOffset;
13 }
14 
15 struct RSRCType
16 {
17     char[4] id;
18     int numRes()
19     {
20         return cast(int)(resIndices.length);
21     }
22     int[] resIndices; // indices of resources of this type
23 }
24 
25 struct RSRCWriter
26 {
27     ubyte[] buffer;
28     RSRCResource[] resources;
29     RSRCType[] types;
30 
31     void addType(char[4] id)
32     {
33         types ~= RSRCType(id, []);
34     }
35 
36 
37     void addResource(int typeNum, ushort resourceID, bool purgeable, string name, const(ubyte)[] content)
38     {
39         types[typeNum].resIndices ~= cast(int)(resources.length);
40         resources ~= RSRCResource(typeNum, resourceID, purgeable, name, content);
41     }
42 
43     ubyte[] write()
44     {
45         // compute resource data
46         ubyte[] resourceData;
47         foreach(ref r; resources)
48         {
49             r.dataOffset = cast(uint)resourceData.length;
50             resourceData.writeBE_uint(cast(uint)(r.content.length));
51             resourceData ~= r.content;
52         }
53 
54         ubyte[] resourceMap = buildResourceMap();
55 
56         ubyte[] buffer;
57 
58         // Offset from beginning of resource file to resource data. Basically guaranteed to be 0x100.
59         buffer.writeBE_uint(0x100);
60 
61         /// Offset from beginning of resource file to resource map.
62         buffer.writeBE_uint(0x100 + cast(uint)(resourceData.length));
63 
64         // Length of resource data
65         buffer.writeBE_uint(cast(uint)(resourceData.length));
66 
67         // Length of resource map
68         buffer.writeBE_uint(cast(uint)(resourceMap.length));
69 
70         // System-reserved data. In practice, this is usually all null bytes.
71         foreach(n; 0..112)
72             buffer.writeBE_ubyte(0);
73         foreach(n; 0..128)
74             buffer.writeBE_ubyte(0);
75 
76         // Undocumented, but rez does fill the first 16
77         // bytes of resourceMap with the same beginning of
78         // the resource file header
79         resourceMap[0..16] = buffer[0..16];
80         return buffer ~ resourceData ~ resourceMap;
81     }
82 
83     ubyte[] buildResourceMap()
84     {
85         ubyte[] buf;
86         foreach(n; 0..16)
87             buf.writeBE_ubyte(0); // Note: modified afterwards to duplicate file header
88 
89         foreach(n; 0..4)
90            buf.writeBE_ubyte(0);
91 
92         // Don't know what this number of for, but it changes with builds
93         // distort => 0x0A00
94         // panagement => 0x0900
95         // couture => 0x0A00
96         // graillon => 0x0A00
97         buf.writeBE_ushort(0x0A00);
98 
99 
100         buf.writeBE_ushort(0);
101 
102         ubyte[] resourceName = buildResourceNameList();
103         ubyte[] typelist = buildTypeList();
104 
105         // Offset from beginning of resource map to type list.
106         buf.writeBE_ushort(28); // = size of resource map header
107 
108         // Offset from beginning of resource map to resource name list.
109         buf.writeBE_ushort(cast(ushort)(28 + typelist.length));
110 
111         return buf ~ typelist ~ resourceName;
112     }
113 
114     ubyte[] buildTypeList()
115     {
116         ubyte[] buf;
117         buf.writeBE_ushort( cast(ushort)(types.length - 1));
118 
119         ubyte[] referenceLists;
120 
121         int offsetOfFirstReferenceList = 2 + cast(int)(types.length) * 8;
122 
123         int resMysteriousID = 0x100_0000;
124 
125         foreach(t; types)
126         {
127             // Resource type. This is usually a 4-character ASCII mnemonic, but may be any 4 bytes.
128             foreach(n; 0..4)
129                 buf.writeBE_ubyte(cast(ubyte)(t.id[n]));
130 
131             // Number of resources of this type in the map minus 1.
132             buf.writeBE_ushort( cast(ushort)(t.numRes - 1));
133 
134             // Offset from beginning of type list to reference list for resources of this type.
135             buf.writeBE_ushort( cast(ushort)( offsetOfFirstReferenceList + referenceLists.length) );
136 
137             // build reference list for this type
138             foreach(rindex; 0..t.numRes)
139             {
140                 const(RSRCResource)* res = &resources[t.resIndices[rindex]];
141 
142                 // Resource ID
143                 referenceLists.writeBE_ushort(res.resourceID);
144 
145                 // Offset from beginning of resource name list to length of resource name, or -1 (0xffff) if none.
146                 referenceLists.writeBE_ushort(res.nameOffset);
147 
148                 // Resource attributes. Combination of ResourceAttrs flags, see below. (Note: packed into 4 bytes together with the next 3 bytes.)
149                 referenceLists.writeBE_ubyte(res.purgeable ? (1 << 5) : 0);
150 
151                 // Offset from beginning of resource data to length of data for this resource. (Note: packed into 4 bytes together with the previous 1 byte.)
152                 uint doffset = res.dataOffset;
153                 referenceLists.writeBE_ubyte((doffset & 0xff0000) >> 16);
154                 referenceLists.writeBE_ubyte((doffset & 0x00ff00) >> 8);
155                 referenceLists.writeBE_ubyte((doffset & 0x0000ff)     );
156 
157                 // Reserved for handle to resource (in memory). "Should be 0 in file."
158                 // But it is not actually zero when written by Rez, unknown use...
159                 referenceLists.writeBE_uint(resMysteriousID);
160                 resMysteriousID += 0x100_0000;
161             }
162         }
163 
164         return buf ~ referenceLists;
165     }
166 
167     ubyte[] buildResourceNameList()
168     {
169         ubyte[] buf;
170         foreach(ref r; resources)
171         {
172             string name = r.name;
173 
174             if (name !is null)
175             {
176                 r.nameOffset = cast(ushort)(buf.length);
177                 buf.writeBE_ubyte(cast(ubyte)(name.length));
178                 foreach(char ch; name)
179                     buf.writeBE_ubyte(cast(ubyte)ch);
180             }
181             else
182                 r.nameOffset = 0xffff;
183         }
184         return buf;
185     }
186 
187 }
188 
189 void writeBE_ubyte(ref ubyte[] buf, ubyte b)
190 {
191     buf ~= b;
192 }
193 
194 void writeBE_ushort(ref ubyte[] buf, ushort s)
195 {
196     buf ~= (s >> 8) & 0xff;
197     buf ~= (s >> 0) & 0xff;
198 }
199 
200 void writeBE_uint(ref ubyte[] buf, uint u)
201 {
202     buf ~= (u >> 24) & 0xff;
203     buf ~= (u >> 16) & 0xff;
204     buf ~= (u >>  8) & 0xff;
205     buf ~= (u >>  0) & 0xff;
206 }
207 
208 ubyte[] makeRSRC_pstring(string s)
209 {
210     import std.stdio;
211     assert(s.length <= 255);
212     ubyte[] buf;
213     buf.writeBE_ubyte(cast(ubyte)(s.length));
214     foreach(char ch; s)
215     {
216         buf.writeBE_ubyte(cast(ubyte)(ch));
217     }
218     return buf;
219 }
220 
221 ubyte[] makeRSRC_fourCC(char[4] ch)
222 {
223     ubyte[] buf;
224     buf.writeBE_ubyte(ch[0]);
225     buf.writeBE_ubyte(ch[1]);
226     buf.writeBE_ubyte(ch[2]);
227     buf.writeBE_ubyte(ch[3]);
228     return buf;
229 }
230 
231 ubyte[] makeRSRC_fourCC_string(string fourcc)
232 {
233     assert(fourcc.length == 4);
234     ubyte[] buf;
235     buf.writeBE_ubyte(fourcc[0]);
236     buf.writeBE_ubyte(fourcc[1]);
237     buf.writeBE_ubyte(fourcc[2]);
238     buf.writeBE_ubyte(fourcc[3]);
239     return buf;
240 }
241 
242 ubyte[] makeRSRC_cstring(string s)
243 {
244     ubyte[] buf;
245     foreach(char ch; s)
246     {
247         buf.writeBE_ubyte(cast(ubyte)(ch));
248     }
249     buf.writeBE_ubyte(0);
250     return buf;
251 }