xoreos  0.0.5
smallfile.cpp
Go to the documentation of this file.
1 /* xoreos - A reimplementation of BioWare's Aurora engine
2  *
3  * xoreos is the legal property of its developers, whose names
4  * can be found in the AUTHORS file distributed with this source
5  * distribution.
6  *
7  * xoreos is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * xoreos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with xoreos. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
25 #include "src/common/scopedptr.h"
26 #include "src/common/error.h"
29 
30 #include "src/aurora/smallfile.h"
31 
32 namespace Aurora {
33 
34 static void readSmallHeader(Common::ReadStream &small, uint32 &type, uint32 &size) {
35  uint32 data = small.readUint32LE();
36 
37  type = data & 0x000000FF;
38  size = data >> 8;
39 }
40 
41 static void writeSmallHeader(Common::WriteStream &small, uint32 type, uint32 size) {
42  type &= 0x000000FF;
43  size &= 0x00FFFFFF;
44 
45  small.writeUint32LE((size << 8) | type);
46 }
47 
48 static void decompress00(Common::ReadStream &small, Common::WriteStream &out, uint32 size) {
49  out.writeStream(small, size);
50 }
51 
52 static void compress00(Common::ReadStream &in, Common::WriteStream &small, uint32 size) {
53  small.writeStream(in, size);
54 }
55 
56 /* Simple LZSS 0x10 decompression.
57  *
58  * Code loosely based on DSDecmp by Barubary, released under the terms of the MIT license.
59  *
60  * See <https://github.com/gravgun/dsdecmp/blob/master/CSharp/DSDecmp/Formats/Nitro/LZ10.cs#L121>
61  * and <https://code.google.com/p/dsdecmp/>.
62  */
63 static void decompress10(Common::ReadStream &small, Common::WriteStream &out, uint32 size) {
64  byte buffer[0x10000];
65  uint32 bufferPos = 0;
66 
67  uint16 flags = 0xFF00;
68 
69  uint32 outSize = 0;
70  while (outSize < size) {
71  // Only our canaries left => Read flags for the next 8 blocks
72  if (flags == 0xFF00)
73  flags = (small.readByte() << 8) | 0x00FF;
74 
75  if (flags & 0x8000) {
76  // Copy from buffer
77 
78  const byte data1 = small.readByte();
79  const byte data2 = small.readByte();
80 
81  // Copy how many bytes from where (relative) in the buffer?
82  const uint8 length = (data1 >> 4) + 3;
83  const uint16 offset = (((data1 & 0x0F) << 8) | data2) + 1;
84 
85  // Direct offset. Add size of the buffer once, to protect from overroll
86  uint32 copyOffset = bufferPos + sizeof(buffer) - offset;
87 
88  // Copy length bytes (and store each back into the buffer)
89  for (uint8 i = 0; i < length; i++, copyOffset++) {
90  if ((copyOffset % sizeof(buffer)) >= outSize)
91  throw Common::Exception("Tried to copy past the buffer");
92 
93  const byte data = buffer[copyOffset % sizeof(buffer)];
94 
95  out.writeByte(data);
96  outSize++;
97 
98  buffer[bufferPos] = data;
99  bufferPos = (bufferPos + 1) % sizeof(buffer);
100  }
101 
102  } else {
103  // Read literal byte
104 
105  const byte data = small.readByte();
106 
107  out.writeByte(data);
108  outSize++;
109 
110  buffer[bufferPos] = data;
111  bufferPos = (bufferPos + 1) % sizeof(buffer);
112  }
113 
114  flags <<= 1;
115  }
116 
117  if (outSize != size)
118  throw Common::Exception("Invalid \"small\" data");
119 }
120 
138 size_t getOccurrenceLength(const byte *newPtr, size_t newLength,
139  const byte *oldPtr, size_t oldLength,
140  size_t &displacement, size_t minDisplacement = 1) {
141  displacement = 0;
142 
143  // Can we actually do anything with the data?
144  if ((minDisplacement > oldLength) || (newLength == 0))
145  return 0;
146 
147  size_t maxLength = 0;
148  for (size_t i = 0; i < oldLength - minDisplacement; i++) {
149  /* Try out every single possible displacement value, from the
150  * start of the old data to the end. */
151 
152  const byte *currentOldPtr = oldPtr + i;
153  size_t currentLength = 0;
154 
155  /* Determine the length we can copy if we go back (oldLength - i) bytes.
156  * Always check the next newLength bytes, because we do LZSS. */
157  for (size_t j = 0; j < newLength; j++, currentLength++)
158  if (currentOldPtr[j] != newPtr[j])
159  break;
160 
161  if (currentLength > maxLength) {
162  // We've bettered our last try
163  maxLength = currentLength;
164  displacement = oldLength - i;
165 
166  // If we can't get any better than that, stop
167  if (maxLength == newLength)
168  break;
169  }
170  }
171 
172  return maxLength;
173 }
174 
175 /* Simple LZSS 0x10 compression.
176  *
177  * Code loosely based on DSDecmp by Barubary, released under the terms of the MIT license.
178  *
179  * See <https://github.com/gravgun/dsdecmp/blob/master/CSharp/DSDecmp/Formats/Nitro/LZ10.cs#L249>
180  * and <https://code.google.com/p/dsdecmp/>.
181  */
182 static void compress10(Common::ReadStream &in, Common::WriteStream &small, uint32 size) {
183  Common::ScopedArray<byte> inBuffer(new byte[size]);
184  if (in.read(inBuffer.get(), size) != size)
186 
187  // Buffer for 8 blocks (max. 2 bytes each), plus their flags byte
188  byte outBuffer[8 * 2 + 1] = { 0 };
189  size_t bufferedBlocks = 0, bufferLength = 1;
190 
191  size_t inRead = 0;
192  while (inRead < size) {
193  // If 8 blocks have been buffered, write them and reset the buffer
194  if (bufferedBlocks == 8) {
195  if (small.write(outBuffer, bufferLength) != bufferLength)
197 
198  bufferedBlocks = 0;
199  bufferLength = 1;
200 
201  outBuffer[0] = 0x00;
202  }
203 
204  /* Look for duplications in the input data:
205  * Try to find an occurrence of data starting from the current place in the
206  * data within the last 0x1000 bytes bytes (the maximum displacement the
207  * format supports) of the already compressed data, but only check the next
208  * 0x12 bytes (the maximum copy length). */
209 
210  const size_t newLength = MIN<size_t>(size - inRead, 0x12);
211  const size_t oldLength = MIN<size_t>(inRead, 0x1000);
212 
213  size_t displacement = 0;
214  const size_t length =
215  getOccurrenceLength(inBuffer.get() + inRead , newLength,
216  inBuffer.get() + inRead - oldLength, oldLength,
217  displacement);
218 
219  /* If the length of the occurrence is at least 3 bytes, we safe space by
220  * referring to the earlier place in the data. If it's shorter (or even
221  * non-existent), then just encode the next byte literally. */
222 
223  if (length >= 3) {
224  inRead += length;
225 
226  // Mark the block as compressed
227  outBuffer[0] |= 1 << (7 - bufferedBlocks);
228 
229  outBuffer[bufferLength ] = ((length - 3) << 4) & 0xF0;
230  outBuffer[bufferLength++] |= ((displacement - 1) >> 8) & 0x0F;
231  outBuffer[bufferLength++] = (displacement - 1) & 0xFF;
232  } else
233  outBuffer[bufferLength++] = inBuffer[inRead++];
234 
235  bufferedBlocks++;
236  }
237 
238  // Write the remaining blocks
239  if (bufferedBlocks > 0)
240  if (small.write(outBuffer, bufferLength) != bufferLength)
242 }
243 
245  uint32 type, uint32 size) {
246 
247  if (type == 0x00)
248  decompress00(small, out, size);
249  else if (type == 0x10)
250  decompress10(small, out, size);
251  else
252  throw Common::Exception("Unsupported type 0x%08X", (uint) type);
253 }
254 
256  uint32 type, size;
257  readSmallHeader(small, type, size);
258 
259  try {
260  ::Aurora::decompress(small, out, type, size);
261  } catch (Common::Exception &e) {
262  e.add("Failed to decompress \"small\" file");
263  throw e;
264  }
265 
266 }
267 
270 
271  assert(in);
272 
273  uint32 type, size;
274  readSmallHeader(*in, type, size);
275 
276  const size_t pos = in->pos();
277 
278  if (type == 0x00)
279  // Uncompressed. Just return a sub stream for the raw data
280  return new Common::SeekableSubReadStream(in.release(), pos, pos + size, true);
281 
282  Common::MemoryWriteStreamDynamic out(true, size);
283 
284  try {
285  ::Aurora::decompress(*in, out, type, size);
286  } catch (Common::Exception &e) {
287  e.add("Failed to decompress \"small\" file");
288  throw e;
289  }
290 
291  out.setDisposable(false);
292  return new Common::MemoryReadStream(out.getData(), out.size(), true);
293 }
294 
296  uint32 type, size;
297  readSmallHeader(small, type, size);
298 
299  Common::MemoryWriteStreamDynamic out(true, size);
300 
301  try {
302  ::Aurora::decompress(small, out, type, size);
303  } catch (Common::Exception &e) {
304  e.add("Failed to decompress \"small\" file");
305  throw e;
306  }
307 
308  out.setDisposable(false);
309  return new Common::MemoryReadStream(out.getData(), out.size(), true);
310 }
311 
314 
315  assert(in);
316  return decompress(*in);
317 }
318 
320  const size_t size = in.size() - in.pos();
321  if (size >= 0xFFFFFF)
322  throw Common::Exception("Small::compress00(): Input stream too large");
323 
324  writeSmallHeader(small, 0x00, size);
325  ::Aurora::compress00(in, small, size);
326 }
327 
329  const size_t size = in.size() - in.pos();
330  if (size >= 0xFFFFFF)
331  throw Common::Exception("Small::compress10(): Input stream too large");
332 
333  writeSmallHeader(small, 0x10, size);
334  ::Aurora::compress10(in, small, size);
335 }
336 
337 } // End of namespace Aurora
void add(const char *s,...) GCC_PRINTF(2
Definition: error.cpp:58
static void compress10(Common::SeekableReadStream &in, Common::WriteStream &small)
Compress this stream into a small file of type 0x10.
Definition: smallfile.cpp:328
size_t getOccurrenceLength(const byte *newPtr, size_t newLength, const byte *oldPtr, size_t oldLength, size_t &displacement, size_t minDisplacement=1)
Determine the maximum size of an LZSS-compressed block.
Definition: smallfile.cpp:138
uint32 readUint32LE()
Read an unsigned 32-bit word stored in little endian (LSB first) order from the stream and return it...
Definition: readstream.h:133
static void compress10(Common::ReadStream &in, Common::WriteStream &small, uint32 size)
Definition: smallfile.cpp:182
Generic interface for a readable data stream.
Definition: readstream.h:64
static void decompress10(Common::ReadStream &small, Common::WriteStream &out, uint32 size)
Definition: smallfile.cpp:63
A stream that dynamically grows as it&#39;s written to.
static void decompress(Common::ReadStream &small, Common::WriteStream &out)
Definition: smallfile.cpp:255
PointerType release()
Returns the plain pointer value and releases ScopedPtr.
Definition: scopedptr.h:103
uint8_t uint8
Definition: types.h:200
Implementing the reading stream interfaces for plain memory blocks.
static void readSmallHeader(Common::ReadStream &small, uint32 &type, uint32 &size)
Definition: smallfile.cpp:34
Exception that provides a stack of explanations.
Definition: error.h:36
A simple scoped smart pointer template.
size_t size() const
Return the number of bytes written to this stream in total.
Basic exceptions to throw.
static void decompress(Common::ReadStream &small, Common::WriteStream &out, uint32 type, uint32 size)
Definition: smallfile.cpp:244
uint16_t uint16
Definition: types.h:202
Implementing the writing stream interfaces for memory blocks.
static void decompress00(Common::ReadStream &small, Common::WriteStream &out, uint32 size)
Definition: smallfile.cpp:48
static void writeSmallHeader(Common::WriteStream &small, uint32 type, uint32 size)
Definition: smallfile.cpp:41
virtual size_t read(void *dataPtr, size_t dataSize)=0
Read data from the stream.
Simple memory based &#39;stream&#39;, which implements the ReadStream interface for a plain memory block...
Definition: memreadstream.h:66
const Exception kWriteError("Write error")
Exception when writing to a stream failed.
Definition: error.h:64
StackException Exception
Definition: error.h:59
const Exception kReadError("Read error")
Exception when reading from a stream failed.
Definition: error.h:62
void setDisposable(bool disposeMemory)
static void compress00(Common::SeekableReadStream &in, Common::WriteStream &small)
"Compress" this stream into an uncompressed small file.
Definition: smallfile.cpp:319
virtual size_t write(const void *dataPtr, size_t dataSize)=0
Write data into the stream.
void writeByte(byte value)
Definition: writestream.h:88
virtual size_t size() const =0
Obtains the total size of the stream, measured in bytes.
virtual size_t pos() const =0
Obtains the current value of the stream position indicator of the stream.
Generic interface for a writable data stream.
Definition: writestream.h:64
PointerType get() const
Returns the plain pointer value.
Definition: scopedptr.h:96
Decompressing "small" files, Nintendo DS LZSS (types 0x00 and 0x10), found in Sonic.
uint32_t uint32
Definition: types.h:204
static void compress00(Common::ReadStream &in, Common::WriteStream &small, uint32 size)
Definition: smallfile.cpp:52
size_t writeStream(ReadStream &stream, size_t n)
Copy n bytes of the given stream into the stream.
Definition: writestream.cpp:72
SeekableSubReadStream provides access to a SeekableReadStream restricted to the range [begin...
Definition: readstream.h:359
Interface for a seekable & readable data stream.
Definition: readstream.h:265
void writeUint32LE(uint32 value)
Definition: writestream.h:104
byte readByte()
Read an unsigned byte from the stream and return it.
Definition: readstream.h:92
uint8 byte
Definition: types.h:209
unsigned int uint
Definition: types.h:211