Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
alula committed Feb 17, 2023
0 parents commit 3d7a6b3
Show file tree
Hide file tree
Showing 14 changed files with 10,943 additions and 0 deletions.
471 changes: 471 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions 998pack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include <cstdint>
#include <cstdio>
#include <vector>
#include <fstream>
#include <span>

#include "common.h"
#include "compress.h"
#include "stb_image.h"

int main(int argc, char *argv[])
{
printf("998pack - PNG -> Microsoft 998 image format packer - © 2023 Alula\n\n");

if (argc == 1)
{
printf("The output file(s) will be named <input without extension>.998\n\n");

printf("Usage: %s <file1> <file2> ... <fileN>\n", argv[0]);
return 0;
}

printf("Processing %d files...\n", argc - 1);

for (int i = 1; i < argc; i++)
{
std::string input = argv[i];
std::string output = strip_ext(input) + ".998";

printf("Packing: %s -> %s\n", input.c_str(), output.c_str());

stbi_set_flip_vertically_on_load(true);
int width, height, bpp;
uint8_t *data = stbi_load(input.c_str(), &width, &height, &bpp, 4);

if (!data)
{
printf("Error: Failed to load image '%s': %s\n", input.c_str(), stbi_failure_reason());
return 1;
}

if (width > 0xffff || height > 0xffff)
{
printf("Error: Image '%s' is too large: %dx%d\n", input.c_str(), width, height);
return 1;
}

bool hasAlpha = false;
for (int i = 0; i < width * height; i++)
{
if (data[i * 4 + 3] != 0xFF)
{
hasAlpha = true;
break;
}
}

File998 image;
image.width = width;
image.height = height;
image.bpp = hasAlpha ? 32 : 24;

auto stride = (image.width * (image.bpp >> 3) + 3) & 0xFFFFFFFC;

size_t decompressedSize = image.height * stride;
image.data.resize(decompressedSize, 0);

for (int y = 0; y < image.height; y++)
{
for (int x = 0; x < image.width; x++)
{
int srcIdx = (y * image.width + x) * 4;
int dstIdx = (y * stride + x * (image.bpp >> 3));

image.data[dstIdx + 0] = data[srcIdx + 2];
image.data[dstIdx + 1] = data[srcIdx + 1];
image.data[dstIdx + 2] = data[srcIdx + 0];

if (hasAlpha)
image.data[dstIdx + 3] = data[srcIdx + 3];
}
}

stbi_image_free(data);

auto compressed = MsoCompressLZW(image.data);

std::ofstream out(output, std::ios::binary);
auto write = [&]<typename T>(T &value) {
out.write(reinterpret_cast<char *>(&value), sizeof(T));
};

write(image.width);
write(image.height);
write(image.bpp);
out.write(reinterpret_cast<char *>(compressed.data()), compressed.size());
out.flush();
}
}
9 changes: 9 additions & 0 deletions 998pack.manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="998pack" version="6.0.0.0"/>
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>
147 changes: 147 additions & 0 deletions 998unpack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include <cstdint>
#include <cstdio>
#include <vector>
#include <fstream>
#include <span>
#include <ranges>
#include <array>

#include "common.h"
#include "compress.h"
#include "stb_image_write.h"

int main(int argc, char *argv[])
{
printf("998unpack - Microsoft 998 -> PNG image format unpacker - © 2023 Alula\n\n");

if (argc == 1)
{
printf("The output file(s) will be named <input without extension>.png\n\n");

printf("Usage: %s <file1> <file2> ... <fileN>\n", argv[0]);
return 0;
}

printf("Processing %d files...\n", argc - 1);

for (int i = 1; i < argc; i++)
{
std::string input = argv[i];
std::string output = strip_ext(input) + ".png";

printf("Unpacking: %s -> %s\n", input.c_str(), output.c_str());

std::ifstream file(input, std::ios::binary);

File998 image{};

auto read = [&]<typename T>(T &value)
{
file.read(reinterpret_cast<char *>(&value), sizeof(T));
};

bool compressed = true;

read(image.width);
read(image.height);
read(image.bpp);

std::array<uint32_t, 256> palette;

// idfk what this format is and I don't want to bother
// if (image.bpp == 0)
// {
// uint8_t unknown;
// read(unknown);
// image.bpp = 8;
// }
// else
if (image.bpp == 1)
{
image.bpp = 8;
uint8_t entry_count;
read(entry_count);

for (int i = 0; i < entry_count; i++)
{
uint32_t color = 0;
read(color);

if ((color & 0xff000000) == 0)
color |= 0xff000000;

uint32_t oc = color;
color &= 0xff00ff00;
color |= ((oc >> 16) & 0xff);
color |= ((oc << 16) & 0xff0000);

palette[i] = color;
}
}
else if (image.bpp != 24 && image.bpp != 32)
{
printf("Error: Invalid color depth, unsupported or not a 998 file?\n");
return 1;
}

auto stride = (image.width * (image.bpp >> 3) + 3) & 0xFFFFFFFC;

size_t decompressedSize = image.height * stride;
image.data.resize(decompressedSize, 0);

std::vector<uint8_t> compressedData;
auto pos = file.tellg();
file.seekg(0, std::ios::end);
auto end = file.tellg();
file.seekg(pos);

compressedData.resize(end - pos);
file.read(reinterpret_cast<char *>(compressedData.data()), compressedData.size());

if (compressed)
{
MsoUncompressLZW(compressedData, image.data);
}
else
{
image.data = std::move(compressedData);
image.data.resize(decompressedSize);
}

stbi_flip_vertically_on_write(1);

if (image.bpp == 8)
{
std::vector<uint8_t> pixels = std::move(image.data);
image.bpp = 32;

stride = (image.width * (image.bpp >> 3) + 3) & 0xFFFFFFFC;
decompressedSize = image.height * stride;

image.data.resize(decompressedSize);

uint32_t *data = reinterpret_cast<uint32_t *>(image.data.data());

for (uint32_t i = 0; i < static_cast<uint32_t>(image.width) * static_cast<uint32_t>(image.height); i++)
{
data[i] = palette[pixels[i]];
}
}
else
{
// swap RGB -> BGR
for (uint32_t y = 0; y < image.height; y++)
{
for (uint32_t x = 0; x < image.width; x++)
{
size_t offset = y * stride + x * (image.bpp / 8);
std::swap(image.data[offset + 0], image.data[offset + 2]);
}
}
}

stbi_write_png(output.c_str(), image.width, image.height, image.bpp / 8, image.data.data(), stride);
}

return 0;
}
9 changes: 9 additions & 0 deletions 998unpack.manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="998pack" version="6.0.0.0"/>
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>
34 changes: 34 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
project(988unpack LANGUAGES CXX)

cmake_minimum_required(VERSION 3.18)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

add_library(common STATIC stb_image.cpp compress.cpp)

add_executable(998pack 998pack.cpp)
add_executable(998unpack 998unpack.cpp)
target_link_libraries(998pack common)
target_link_libraries(998unpack common)

if(MSVC)
ADD_CUSTOM_COMMAND(
TARGET 998pack
POST_BUILD
DEPENDS
COMMAND mt.exe -manifest "${CMAKE_SOURCE_DIR}/998pack.manifest.xml" -inputresource:"$<TARGET_FILE:998pack>"\;\#1 -updateresource:"$<TARGET_FILE:998pack>"\;\#1
)
ENDIF(MSVC)

if(MSVC)
ADD_CUSTOM_COMMAND(
TARGET 998pack
POST_BUILD
DEPENDS
COMMAND mt.exe -manifest "${CMAKE_SOURCE_DIR}/998unpack.manifest.xml" -inputresource:"$<TARGET_FILE:998unpack>"\;\#1 -updateresource:"$<TARGET_FILE:998unpack>"\;\#1
)
ENDIF(MSVC)
7 changes: 7 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright 2023 Alula

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 998tools

Toolkit for manipulating Microsoft 998 image format files, used in Microsoft Office and UIRibbon.dll.

## Usage

### 998pack

```
The output file(s) will be named <input without extension>.998
Usage: 998pack <file1> <file2> ... <fileN>
```

### 998unpack

```
The output file(s) will be named <input without extension>.png
Usage: 998unpack <file1> <file2> ... <fileN>
```
Loading

0 comments on commit 3d7a6b3

Please sign in to comment.