Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

View File

@@ -0,0 +1,643 @@
/** @file GuillotineBinPack.cpp
@author Jukka Jylänki
@brief Implements different bin packer algorithms that use the GUILLOTINE data structure.
This work is released to Public Domain, do whatever you want with it.
*/
#include <utility>
#include <iostream>
#include <limits>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <cmath>
#include "GuillotineBinPack.h"
#pragma warning( disable : 4267 )
namespace rbp {
using namespace std;
GuillotineBinPack::GuillotineBinPack()
:binWidth(0),
binHeight(0)
{
}
GuillotineBinPack::GuillotineBinPack(int width, int height)
{
Init(width, height);
}
void GuillotineBinPack::Init(int width, int height)
{
binWidth = width;
binHeight = height;
#ifdef _DEBUG
disjointRects.Clear();
#endif
// Clear any memory of previously packed rectangles.
usedRectangles.clear();
// We start with a single big free rectangle that spans the whole bin.
Rect n;
n.x = 0;
n.y = 0;
n.width = width;
n.height = height;
freeRectangles.clear();
freeRectangles.push_back(n);
}
void GuillotineBinPack::Insert(std::vector<RectSize> &rects, bool merge,
FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
{
// Remember variables about the best packing choice we have made so far during the iteration process.
int bestFreeRect = 0;
int bestRect = 0;
bool bestFlipped = false;
// Pack rectangles one at a time until we have cleared the rects array of all rectangles.
// rects will get destroyed in the process.
while(rects.size() > 0)
{
// Stores the penalty score of the best rectangle placement - bigger=worse, smaller=better.
int bestScore = std::numeric_limits<int>::max();
for(size_t i = 0; i < freeRectangles.size(); ++i)
{
for(size_t j = 0; j < rects.size(); ++j)
{
// If this rectangle is a perfect match, we pick it instantly.
if (rects[j].width == freeRectangles[i].width && rects[j].height == freeRectangles[i].height)
{
bestFreeRect = i;
bestRect = j;
bestFlipped = false;
bestScore = std::numeric_limits<int>::min();
i = freeRectangles.size(); // Force a jump out of the outer loop as well - we got an instant fit.
break;
}
// If flipping this rectangle is a perfect match, pick that then.
/*else if (rects[j].height == freeRectangles[i].width && rects[j].width == freeRectangles[i].height)
{
bestFreeRect = i;
bestRect = j;
bestFlipped = true;
bestScore = std::numeric_limits<int>::min();
i = freeRectangles.size(); // Force a jump out of the outer loop as well - we got an instant fit.
break;
}*/
// Try if we can fit the rectangle upright.
else if (rects[j].width <= freeRectangles[i].width && rects[j].height <= freeRectangles[i].height)
{
int score = ScoreByHeuristic(rects[j].width, rects[j].height, freeRectangles[i], rectChoice);
if (score < bestScore)
{
bestFreeRect = i;
bestRect = j;
bestFlipped = false;
bestScore = score;
}
}
// If not, then perhaps flipping sideways will make it fit?
/*else if (rects[j].height <= freeRectangles[i].width && rects[j].width <= freeRectangles[i].height)
{
int score = ScoreByHeuristic(rects[j].height, rects[j].width, freeRectangles[i], rectChoice);
if (score < bestScore)
{
bestFreeRect = i;
bestRect = j;
bestFlipped = true;
bestScore = score;
}
}*/
}
}
// If we didn't manage to find any rectangle to pack, abort.
if (bestScore == std::numeric_limits<int>::max())
return;
// Otherwise, we're good to go and do the actual packing.
Rect newNode;
newNode.x = freeRectangles[bestFreeRect].x;
newNode.y = freeRectangles[bestFreeRect].y;
newNode.width = rects[bestRect].width;
newNode.height = rects[bestRect].height;
if (bestFlipped)
std::swap(newNode.width, newNode.height);
// Remove the free space we lost in the bin.
SplitFreeRectByHeuristic(freeRectangles[bestFreeRect], newNode, splitMethod);
freeRectangles.erase(freeRectangles.begin() + bestFreeRect);
// Remove the rectangle we just packed from the input list.
rects.erase(rects.begin() + bestRect);
// Perform a Rectangle Merge step if desired.
if (merge)
MergeFreeList();
// Remember the new used rectangle.
usedRectangles.push_back(newNode);
// Check that we're really producing correct packings here.
debug_assert(disjointRects.Add(newNode) == true);
}
}
/// @return True if r fits inside freeRect (possibly rotated).
bool Fits(const RectSize &r, const Rect &freeRect)
{
return (r.width <= freeRect.width && r.height <= freeRect.height) ||
(r.height <= freeRect.width && r.width <= freeRect.height);
}
/// @return True if r fits perfectly inside freeRect, i.e. the leftover area is 0.
bool FitsPerfectly(const RectSize &r, const Rect &freeRect)
{
return (r.width == freeRect.width && r.height == freeRect.height) ||
(r.height == freeRect.width && r.width == freeRect.height);
}
/*
// A helper function for GUILLOTINE-MAXFITTING. Counts how many rectangles fit into the given rectangle
// after it has been split.
void CountNumFitting(const Rect &freeRect, int width, int height, const std::vector<RectSize> &rects,
int usedRectIndex, bool splitHorizontal, int &score1, int &score2)
{
const int w = freeRect.width - width;
const int h = freeRect.height - height;
Rect bottom;
bottom.x = freeRect.x;
bottom.y = freeRect.y + height;
bottom.height = h;
Rect right;
right.x = freeRect.x + width;
right.y = freeRect.y;
right.width = w;
if (splitHorizontal)
{
bottom.width = freeRect.width;
right.height = height;
}
else // Split vertically
{
bottom.width = width;
right.height = freeRect.height;
}
int fitBottom = 0;
int fitRight = 0;
for(size_t i = 0; i < rects.size(); ++i)
if (i != usedRectIndex)
{
if (FitsPerfectly(rects[i], bottom))
fitBottom |= 0x10000000;
if (FitsPerfectly(rects[i], right))
fitRight |= 0x10000000;
if (Fits(rects[i], bottom))
++fitBottom;
if (Fits(rects[i], right))
++fitRight;
}
score1 = min(fitBottom, fitRight);
score2 = max(fitBottom, fitRight);
}
*/
/*
// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice.
void GuillotineBinPack::InsertMaxFitting(std::vector<RectSize> &rects, std::vector<Rect> &dst, bool merge,
FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
{
dst.clear();
int bestRect = 0;
bool bestFlipped = false;
bool bestSplitHorizontal = false;
// Pick rectangles one at a time and pack the one that leaves the most choices still open.
while(rects.size() > 0 && freeRectangles.size() > 0)
{
int bestScore1 = -1;
int bestScore2 = -1;
///\todo Different sort predicates.
clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size(), CompareRectShortSide);
Rect &freeRect = freeRectangles[0];
for(size_t j = 0; j < rects.size(); ++j)
{
int score1;
int score2;
if (rects[j].width == freeRect.width && rects[j].height == freeRect.height)
{
bestRect = j;
bestFlipped = false;
bestScore1 = bestScore2 = std::numeric_limits<int>::max();
break;
}
else if (rects[j].width <= freeRect.width && rects[j].height <= freeRect.height)
{
CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, false, score1, score2);
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
{
bestRect = j;
bestScore1 = score1;
bestScore2 = score2;
bestFlipped = false;
bestSplitHorizontal = false;
}
CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, true, score1, score2);
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
{
bestRect = j;
bestScore1 = score1;
bestScore2 = score2;
bestFlipped = false;
bestSplitHorizontal = true;
}
}
if (rects[j].height == freeRect.width && rects[j].width == freeRect.height)
{
bestRect = j;
bestFlipped = true;
bestScore1 = bestScore2 = std::numeric_limits<int>::max();
break;
}
else if (rects[j].height <= freeRect.width && rects[j].width <= freeRect.height)
{
CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, false, score1, score2);
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
{
bestRect = j;
bestScore1 = score1;
bestScore2 = score2;
bestFlipped = true;
bestSplitHorizontal = false;
}
CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, true, score1, score2);
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
{
bestRect = j;
bestScore1 = score1;
bestScore2 = score2;
bestFlipped = true;
bestSplitHorizontal = true;
}
}
}
if (bestScore1 >= 0)
{
Rect newNode;
newNode.x = freeRect.x;
newNode.y = freeRect.y;
newNode.width = rects[bestRect].width;
newNode.height = rects[bestRect].height;
if (bestFlipped)
std::swap(newNode.width, newNode.height);
assert(disjointRects.Disjoint(newNode));
SplitFreeRectAlongAxis(freeRect, newNode, bestSplitHorizontal);
rects.erase(rects.begin() + bestRect);
if (merge)
MergeFreeList();
usedRectangles.push_back(newNode);
#ifdef _DEBUG
disjointRects.Add(newNode);
#endif
}
freeRectangles.erase(freeRectangles.begin());
}
}
*/
Rect GuillotineBinPack::Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice,
GuillotineSplitHeuristic splitMethod)
{
// Find where to put the new rectangle.
int freeNodeIndex = 0;
Rect newRect = FindPositionForNewNode(width, height, rectChoice, &freeNodeIndex);
// Abort if we didn't have enough space in the bin.
if (newRect.height == 0)
return newRect;
// Remove the space that was just consumed by the new rectangle.
SplitFreeRectByHeuristic(freeRectangles[freeNodeIndex], newRect, splitMethod);
freeRectangles.erase(freeRectangles.begin() + freeNodeIndex);
// Perform a Rectangle Merge step if desired.
if (merge)
MergeFreeList();
// Remember the new used rectangle.
usedRectangles.push_back(newRect);
// Check that we're really producing correct packings here.
debug_assert(disjointRects.Add(newRect) == true);
return newRect;
}
/// Computes the ratio of used surface area to the total bin area.
float GuillotineBinPack::Occupancy() const
{
///\todo The occupancy rate could be cached/tracked incrementally instead
/// of looping through the list of packed rectangles here.
unsigned long usedSurfaceArea = 0;
for(size_t i = 0; i < usedRectangles.size(); ++i)
usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height;
return (float)usedSurfaceArea / (binWidth * binHeight);
}
/// Returns the heuristic score value for placing a rectangle of size width*height into freeRect. Does not try to rotate.
int GuillotineBinPack::ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice)
{
switch(rectChoice)
{
case RectBestAreaFit: return ScoreBestAreaFit(width, height, freeRect);
case RectBestShortSideFit: return ScoreBestShortSideFit(width, height, freeRect);
case RectBestLongSideFit: return ScoreBestLongSideFit(width, height, freeRect);
case RectWorstAreaFit: return ScoreWorstAreaFit(width, height, freeRect);
case RectWorstShortSideFit: return ScoreWorstShortSideFit(width, height, freeRect);
case RectWorstLongSideFit: return ScoreWorstLongSideFit(width, height, freeRect);
default: assert(false); return std::numeric_limits<int>::max();
}
}
int GuillotineBinPack::ScoreBestAreaFit(int width, int height, const Rect &freeRect)
{
return freeRect.width * freeRect.height - width * height;
}
int GuillotineBinPack::ScoreBestShortSideFit(int width, int height, const Rect &freeRect)
{
int leftoverHoriz = abs(freeRect.width - width);
int leftoverVert = abs(freeRect.height - height);
int leftover = min(leftoverHoriz, leftoverVert);
return leftover;
}
int GuillotineBinPack::ScoreBestLongSideFit(int width, int height, const Rect &freeRect)
{
int leftoverHoriz = abs(freeRect.width - width);
int leftoverVert = abs(freeRect.height - height);
int leftover = max(leftoverHoriz, leftoverVert);
return leftover;
}
int GuillotineBinPack::ScoreWorstAreaFit(int width, int height, const Rect &freeRect)
{
return -ScoreBestAreaFit(width, height, freeRect);
}
int GuillotineBinPack::ScoreWorstShortSideFit(int width, int height, const Rect &freeRect)
{
return -ScoreBestShortSideFit(width, height, freeRect);
}
int GuillotineBinPack::ScoreWorstLongSideFit(int width, int height, const Rect &freeRect)
{
return -ScoreBestLongSideFit(width, height, freeRect);
}
Rect GuillotineBinPack::FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex)
{
Rect bestNode;
memset(&bestNode, 0, sizeof(Rect));
int bestScore = std::numeric_limits<int>::max();
/// Try each free rectangle to find the best one for placement.
for(size_t i = 0; i < freeRectangles.size(); ++i)
{
// If this is a perfect fit upright, choose it immediately.
if (width == freeRectangles[i].width && height == freeRectangles[i].height)
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = width;
bestNode.height = height;
bestScore = std::numeric_limits<int>::min();
*nodeIndex = i;
debug_assert(disjointRects.Disjoint(bestNode));
break;
}
// If this is a perfect fit sideways, choose it.
else if (height == freeRectangles[i].width && width == freeRectangles[i].height)
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = height;
bestNode.height = width;
bestScore = std::numeric_limits<int>::min();
*nodeIndex = i;
debug_assert(disjointRects.Disjoint(bestNode));
break;
}
// Does the rectangle fit upright?
else if (width <= freeRectangles[i].width && height <= freeRectangles[i].height)
{
int score = ScoreByHeuristic(width, height, freeRectangles[i], rectChoice);
if (score < bestScore)
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = width;
bestNode.height = height;
bestScore = score;
*nodeIndex = i;
debug_assert(disjointRects.Disjoint(bestNode));
}
}
// Does the rectangle fit sideways?
else if (height <= freeRectangles[i].width && width <= freeRectangles[i].height)
{
int score = ScoreByHeuristic(height, width, freeRectangles[i], rectChoice);
if (score < bestScore)
{
bestNode.x = freeRectangles[i].x;
bestNode.y = freeRectangles[i].y;
bestNode.width = height;
bestNode.height = width;
bestScore = score;
*nodeIndex = i;
debug_assert(disjointRects.Disjoint(bestNode));
}
}
}
return bestNode;
}
void GuillotineBinPack::SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method)
{
// Compute the lengths of the leftover area.
const int w = freeRect.width - placedRect.width;
const int h = freeRect.height - placedRect.height;
// Placing placedRect into freeRect results in an L-shaped free area, which must be split into
// two disjoint rectangles. This can be achieved with by splitting the L-shape using a single line.
// We have two choices: horizontal or vertical.
// Use the given heuristic to decide which choice to make.
bool splitHorizontal;
switch(method)
{
case SplitShorterLeftoverAxis:
// Split along the shorter leftover axis.
splitHorizontal = (w <= h);
break;
case SplitLongerLeftoverAxis:
// Split along the longer leftover axis.
splitHorizontal = (w > h);
break;
case SplitMinimizeArea:
// Maximize the larger area == minimize the smaller area.
// Tries to make the single bigger rectangle.
splitHorizontal = (placedRect.width * h > w * placedRect.height);
break;
case SplitMaximizeArea:
// Maximize the smaller area == minimize the larger area.
// Tries to make the rectangles more even-sized.
splitHorizontal = (placedRect.width * h <= w * placedRect.height);
break;
case SplitShorterAxis:
// Split along the shorter total axis.
splitHorizontal = (freeRect.width <= freeRect.height);
break;
case SplitLongerAxis:
// Split along the longer total axis.
splitHorizontal = (freeRect.width > freeRect.height);
break;
default:
splitHorizontal = true;
assert(false);
}
// Perform the actual split.
SplitFreeRectAlongAxis(freeRect, placedRect, splitHorizontal);
}
/// This function will add the two generated rectangles into the freeRectangles array. The caller is expected to
/// remove the original rectangle from the freeRectangles array after that.
void GuillotineBinPack::SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal)
{
// Form the two new rectangles.
Rect bottom;
bottom.x = freeRect.x;
bottom.y = freeRect.y + placedRect.height;
bottom.height = freeRect.height - placedRect.height;
Rect right;
right.x = freeRect.x + placedRect.width;
right.y = freeRect.y;
right.width = freeRect.width - placedRect.width;
if (splitHorizontal)
{
bottom.width = freeRect.width;
right.height = placedRect.height;
}
else // Split vertically
{
bottom.width = placedRect.width;
right.height = freeRect.height;
}
// Add the new rectangles into the free rectangle pool if they weren't degenerate.
if (bottom.width > 0 && bottom.height > 0)
freeRectangles.push_back(bottom);
if (right.width > 0 && right.height > 0)
freeRectangles.push_back(right);
debug_assert(disjointRects.Disjoint(bottom));
debug_assert(disjointRects.Disjoint(right));
}
void GuillotineBinPack::MergeFreeList()
{
#ifdef _DEBUG
DisjointRectCollection test;
for(size_t i = 0; i < freeRectangles.size(); ++i)
assert(test.Add(freeRectangles[i]) == true);
#endif
// Do a Theta(n^2) loop to see if any pair of free rectangles could me merged into one.
// Note that we miss any opportunities to merge three rectangles into one. (should call this function again to detect that)
for(size_t i = 0; i < freeRectangles.size(); ++i)
for(size_t j = i+1; j < freeRectangles.size(); ++j)
{
if (freeRectangles[i].width == freeRectangles[j].width && freeRectangles[i].x == freeRectangles[j].x)
{
if (freeRectangles[i].y == freeRectangles[j].y + freeRectangles[j].height)
{
freeRectangles[i].y -= freeRectangles[j].height;
freeRectangles[i].height += freeRectangles[j].height;
freeRectangles.erase(freeRectangles.begin() + j);
--j;
}
else if (freeRectangles[i].y + freeRectangles[i].height == freeRectangles[j].y)
{
freeRectangles[i].height += freeRectangles[j].height;
freeRectangles.erase(freeRectangles.begin() + j);
--j;
}
}
else if (freeRectangles[i].height == freeRectangles[j].height && freeRectangles[i].y == freeRectangles[j].y)
{
if (freeRectangles[i].x == freeRectangles[j].x + freeRectangles[j].width)
{
freeRectangles[i].x -= freeRectangles[j].width;
freeRectangles[i].width += freeRectangles[j].width;
freeRectangles.erase(freeRectangles.begin() + j);
--j;
}
else if (freeRectangles[i].x + freeRectangles[i].width == freeRectangles[j].x)
{
freeRectangles[i].width += freeRectangles[j].width;
freeRectangles.erase(freeRectangles.begin() + j);
--j;
}
}
}
#ifdef _DEBUG
test.Clear();
for(size_t i = 0; i < freeRectangles.size(); ++i)
assert(test.Add(freeRectangles[i]) == true);
#endif
}
}

View File

@@ -0,0 +1,134 @@
/** @file GuillotineBinPack.h
@author Jukka Jylänki
@brief Implements different bin packer algorithms that use the GUILLOTINE data structure.
This work is released to Public Domain, do whatever you want with it.
*/
#pragma once
#include <vector>
#include "Rect.h"
namespace rbp {
/** GuillotineBinPack implements different variants of bin packer algorithms that use the GUILLOTINE data structure
to keep track of the free space of the bin where rectangles may be placed. */
class GuillotineBinPack
{
public:
/// The initial bin size will be (0,0). Call Init to set the bin size.
GuillotineBinPack();
/// Initializes a new bin of the given size.
GuillotineBinPack(int width, int height);
/// (Re)initializes the packer to an empty bin of width x height units. Call whenever
/// you need to restart with a new bin.
void Init(int width, int height);
/// Specifies the different choice heuristics that can be used when deciding which of the free subrectangles
/// to place the to-be-packed rectangle into.
enum FreeRectChoiceHeuristic
{
RectBestAreaFit, ///< -BAF
RectBestShortSideFit, ///< -BSSF
RectBestLongSideFit, ///< -BLSF
RectWorstAreaFit, ///< -WAF
RectWorstShortSideFit, ///< -WSSF
RectWorstLongSideFit ///< -WLSF
};
/// Specifies the different choice heuristics that can be used when the packer needs to decide whether to
/// subdivide the remaining free space in horizontal or vertical direction.
enum GuillotineSplitHeuristic
{
SplitShorterLeftoverAxis, ///< -SLAS
SplitLongerLeftoverAxis, ///< -LLAS
SplitMinimizeArea, ///< -MINAS, Try to make a single big rectangle at the expense of making the other small.
SplitMaximizeArea, ///< -MAXAS, Try to make both remaining rectangles as even-sized as possible.
SplitShorterAxis, ///< -SAS
SplitLongerAxis ///< -LAS
};
/// Inserts a single rectangle into the bin. The packer might rotate the rectangle, in which case the returned
/// struct will have the width and height values swapped.
/// @param merge If true, performs free Rectangle Merge procedure after packing the new rectangle. This procedure
/// tries to defragment the list of disjoint free rectangles to improve packing performance, but also takes up
/// some extra time.
/// @param rectChoice The free rectangle choice heuristic rule to use.
/// @param splitMethod The free rectangle split heuristic rule to use.
Rect Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
/// Inserts a list of rectangles into the bin.
/// @param rects The list of rectangles to add. This list will be destroyed in the packing process.
/// @param merge If true, performs Rectangle Merge operations during the packing process.
/// @param rectChoice The free rectangle choice heuristic rule to use.
/// @param splitMethod The free rectangle split heuristic rule to use.
void Insert(std::vector<RectSize> &rects, bool merge,
FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice.
// void InsertMaxFitting(std::vector<RectSize> &rects, std::vector<Rect> &dst, bool merge,
// FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
/// Computes the ratio of used/total surface area. 0.00 means no space is yet used, 1.00 means the whole bin is used.
float Occupancy() const;
/// Returns the internal list of disjoint rectangles that track the free area of the bin. You may alter this vector
/// any way desired, as long as the end result still is a list of disjoint rectangles.
std::vector<Rect> &GetFreeRectangles() { return freeRectangles; }
/// Returns the list of packed rectangles. You may alter this vector at will, for example, you can move a Rect from
/// this list to the Free Rectangles list to free up space on-the-fly, but notice that this causes fragmentation.
std::vector<Rect> &GetUsedRectangles() { return usedRectangles; }
/// Performs a Rectangle Merge operation. This procedure looks for adjacent free rectangles and merges them if they
/// can be represented with a single rectangle. Takes up Theta(|freeRectangles|^2) time.
void MergeFreeList();
private:
int binWidth;
int binHeight;
/// Stores a list of all the rectangles that we have packed so far. This is used only to compute the Occupancy ratio,
/// so if you want to have the packer consume less memory, this can be removed.
std::vector<Rect> usedRectangles;
/// Stores a list of rectangles that represents the free area of the bin. This rectangles in this list are disjoint.
std::vector<Rect> freeRectangles;
#ifdef _DEBUG
/// Used to track that the packer produces proper packings.
DisjointRectCollection disjointRects;
#endif
/// Goes through the list of free rectangles and finds the best one to place a rectangle of given size into.
/// Running time is Theta(|freeRectangles|).
/// @param nodeIndex [out] The index of the free rectangle in the freeRectangles array into which the new
/// rect was placed.
/// @return A Rect structure that represents the placement of the new rect into the best free rectangle.
Rect FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex);
static int ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice);
// The following functions compute (penalty) score values if a rect of the given size was placed into the
// given free rectangle. In these score values, smaller is better.
static int ScoreBestAreaFit(int width, int height, const Rect &freeRect);
static int ScoreBestShortSideFit(int width, int height, const Rect &freeRect);
static int ScoreBestLongSideFit(int width, int height, const Rect &freeRect);
static int ScoreWorstAreaFit(int width, int height, const Rect &freeRect);
static int ScoreWorstShortSideFit(int width, int height, const Rect &freeRect);
static int ScoreWorstLongSideFit(int width, int height, const Rect &freeRect);
/// Splits the given L-shaped free rectangle into two new free rectangles after placedRect has been placed into it.
/// Determines the split axis by using the given heuristic.
void SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method);
/// Splits the given L-shaped free rectangle into two new free rectangles along the given fixed split axis.
void SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal);
};
}

51
external/layeredfs/3rd_party/Rect.cpp vendored Normal file
View File

@@ -0,0 +1,51 @@
/** @file Rect.cpp
@author Jukka Jylänki
This work is released to Public Domain, do whatever you want with it.
*/
#include <utility>
#include "Rect.h"
namespace rbp {
/*
#include "clb/Algorithm/Sort.h"
int CompareRectShortSide(const Rect &a, const Rect &b)
{
using namespace std;
int smallerSideA = min(a.width, a.height);
int smallerSideB = min(b.width, b.height);
if (smallerSideA != smallerSideB)
return clb::sort::TriCmp(smallerSideA, smallerSideB);
// Tie-break on the larger side.
int largerSideA = max(a.width, a.height);
int largerSideB = max(b.width, b.height);
return clb::sort::TriCmp(largerSideA, largerSideB);
}
*/
/*
int NodeSortCmp(const Rect &a, const Rect &b)
{
if (a.x != b.x)
return clb::sort::TriCmp(a.x, b.x);
if (a.y != b.y)
return clb::sort::TriCmp(a.y, b.y);
if (a.width != b.width)
return clb::sort::TriCmp(a.width, b.width);
return clb::sort::TriCmp(a.height, b.height);
}
*/
bool IsContainedIn(const Rect &a, const Rect &b)
{
return a.x >= b.x && a.y >= b.y
&& a.x+a.width <= b.x+b.width
&& a.y+a.height <= b.y+b.height;
}
}

94
external/layeredfs/3rd_party/Rect.h vendored Normal file
View File

@@ -0,0 +1,94 @@
/** @file Rect.h
@author Jukka Jylänki
This work is released to Public Domain, do whatever you want with it.
*/
#pragma once
#include <vector>
#include <cassert>
#include <cstdlib>
#ifdef _DEBUG
/// debug_assert is an assert that also requires debug mode to be defined.
#define debug_assert(x) assert(x)
#else
#define debug_assert(x)
#endif
//using namespace std;
namespace rbp {
struct RectSize
{
int width;
int height;
};
struct Rect
{
int x;
int y;
int width;
int height;
};
/// Performs a lexicographic compare on (rect short side, rect long side).
/// @return -1 if the smaller side of a is shorter than the smaller side of b, 1 if the other way around.
/// If they are equal, the larger side length is used as a tie-breaker.
/// If the rectangles are of same size, returns 0.
int CompareRectShortSide(const Rect &a, const Rect &b);
/// Performs a lexicographic compare on (x, y, width, height).
int NodeSortCmp(const Rect &a, const Rect &b);
/// Returns true if a is contained in b.
bool IsContainedIn(const Rect &a, const Rect &b);
class DisjointRectCollection
{
public:
std::vector<Rect> rects;
bool Add(const Rect &r)
{
// Degenerate rectangles are ignored.
if (r.width == 0 || r.height == 0)
return true;
if (!Disjoint(r))
return false;
rects.push_back(r);
return true;
}
void Clear()
{
rects.clear();
}
bool Disjoint(const Rect &r) const
{
// Degenerate rectangles are ignored.
if (r.width == 0 || r.height == 0)
return true;
for(size_t i = 0; i < rects.size(); ++i)
if (!Disjoint(rects[i], r))
return false;
return true;
}
static bool Disjoint(const Rect &a, const Rect &b)
{
if (a.x + a.width <= b.x ||
b.x + b.width <= a.x ||
a.y + a.height <= b.y ||
b.y + b.height <= a.y)
return true;
return false;
}
};
}

6488
external/layeredfs/3rd_party/lodepng.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

2020
external/layeredfs/3rd_party/lodepng.h vendored Normal file

File diff suppressed because it is too large Load Diff

2596
external/layeredfs/3rd_party/rapidxml.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
#ifndef RAPIDXML_PRINT_HPP_INCLUDED
#define RAPIDXML_PRINT_HPP_INCLUDED
// Copyright (C) 2006, 2009 Marcin Kalicinski
// Version 1.13
// Revision $DateTime: 2009/05/13 01:46:17 $
//! \file rapidxml_print.hpp This file contains rapidxml printer implementation
#include "rapidxml.hpp"
// Only include streams if not disabled
#ifndef RAPIDXML_NO_STREAMS
#include <ostream>
#include <iterator>
#endif
namespace rapidxml
{
///////////////////////////////////////////////////////////////////////
// Printing flags
const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.
///////////////////////////////////////////////////////////////////////
// Internal
//! \cond internal
namespace internal
{
///////////////////////////////////////////////////////////////////////////
// Internal character operations
// Copy characters from given range to given output iterator
template<class OutIt, class Ch>
inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)
{
while (begin != end)
*out++ = *begin++;
return out;
}
// Copy characters from given range to given output iterator and expand
// characters into references (&lt; &gt; &apos; &quot; &amp;)
template<class OutIt, class Ch>
inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)
{
while (begin != end)
{
if (*begin == noexpand)
{
*out++ = *begin; // No expansion, copy character
}
else
{
switch (*begin)
{
case Ch('<'):
*out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');
break;
case Ch('>'):
*out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');
break;
case Ch('\''):
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');
break;
case Ch('"'):
*out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');
break;
case Ch('&'):
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';');
break;
default:
*out++ = *begin; // No expansion, copy character
}
}
++begin; // Step to next character
}
return out;
}
// Fill given output iterator with repetitions of the same character
template<class OutIt, class Ch>
inline OutIt fill_chars(OutIt out, int n, Ch ch)
{
for (int i = 0; i < n; ++i)
*out++ = ch;
return out;
}
// Find character
template<class Ch, Ch ch>
inline bool find_char(const Ch *begin, const Ch *end)
{
while (begin != end)
if (*begin++ == ch)
return true;
return false;
}
///////////////////////////////////////////////////////////////////////////
// Internal printing operations
template<class OutIt, class Ch>
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags);
template<class OutIt, class Ch>
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
template<class OutIt, class Ch>
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);
// Print node
template<class OutIt, class Ch>
inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
// Print proper node type
switch (node->type())
{
// Document
case node_document:
out = print_children(out, node, flags, indent);
break;
// Element
case node_element:
out = print_element_node(out, node, flags, indent);
break;
// Data
case node_data:
out = print_data_node(out, node, flags, indent);
break;
// CDATA
case node_cdata:
out = print_cdata_node(out, node, flags, indent);
break;
// Declaration
case node_declaration:
out = print_declaration_node(out, node, flags, indent);
break;
// Comment
case node_comment:
out = print_comment_node(out, node, flags, indent);
break;
// Doctype
case node_doctype:
out = print_doctype_node(out, node, flags, indent);
break;
// Pi
case node_pi:
out = print_pi_node(out, node, flags, indent);
break;
// Unknown
default:
assert(0);
break;
}
// If indenting not disabled, add line break after node
if (!(flags & print_no_indenting))
*out = Ch('\n'), ++out;
// Return modified iterator
return out;
}
// Print children of the node
template<class OutIt, class Ch>
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())
out = print_node(out, child, flags, indent);
return out;
}
// Print attributes of the node
template<class OutIt, class Ch>
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)
{
for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())
{
if (attribute->name() && attribute->value())
{
// Print attribute name
*out = Ch(' '), ++out;
out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);
*out = Ch('='), ++out;
// Print attribute value using appropriate quote type
if (find_char<Ch, Ch('"')>(attribute->value(), attribute->value() + attribute->value_size()))
{
*out = Ch('\''), ++out;
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out);
*out = Ch('\''), ++out;
}
else
{
*out = Ch('"'), ++out;
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out);
*out = Ch('"'), ++out;
}
}
}
return out;
}
// Print data node
template<class OutIt, class Ch>
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_data);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
return out;
}
// Print data node
template<class OutIt, class Ch>
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_cdata);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'); ++out;
*out = Ch('!'); ++out;
*out = Ch('['); ++out;
*out = Ch('C'); ++out;
*out = Ch('D'); ++out;
*out = Ch('A'); ++out;
*out = Ch('T'); ++out;
*out = Ch('A'); ++out;
*out = Ch('['); ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch(']'); ++out;
*out = Ch(']'); ++out;
*out = Ch('>'); ++out;
return out;
}
// Print element node
template<class OutIt, class Ch>
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_element);
// Print element name and attributes, if any
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
out = copy_chars(node->name(), node->name() + node->name_size(), out);
out = print_attributes(out, node, flags);
// If node is childless
if (node->value_size() == 0 && !node->first_node())
{
// Print childless node tag ending
*out = Ch('/'), ++out;
*out = Ch('>'), ++out;
}
else
{
// Print normal node tag ending
*out = Ch('>'), ++out;
// Test if node contains a single data node only (and no other nodes)
xml_node<Ch> *child = node->first_node();
if (!child)
{
// If node has no children, only print its value without indenting
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
}
else if (child->next_sibling() == 0 && child->type() == node_data)
{
// If node has a sole data child, only print its value without indenting
out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);
}
else
{
// Print all children with full indenting
if (!(flags & print_no_indenting))
*out = Ch('\n'), ++out;
out = print_children(out, node, flags, indent + 1);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
}
// Print node end
*out = Ch('<'), ++out;
*out = Ch('/'), ++out;
out = copy_chars(node->name(), node->name() + node->name_size(), out);
*out = Ch('>'), ++out;
}
return out;
}
// Print declaration node
template<class OutIt, class Ch>
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
// Print declaration start
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('?'), ++out;
*out = Ch('x'), ++out;
*out = Ch('m'), ++out;
*out = Ch('l'), ++out;
// Print attributes
out = print_attributes(out, node, flags);
// Print declaration end
*out = Ch('?'), ++out;
*out = Ch('>'), ++out;
return out;
}
// Print comment node
template<class OutIt, class Ch>
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_comment);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('!'), ++out;
*out = Ch('-'), ++out;
*out = Ch('-'), ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch('-'), ++out;
*out = Ch('-'), ++out;
*out = Ch('>'), ++out;
return out;
}
// Print doctype node
template<class OutIt, class Ch>
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_doctype);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('!'), ++out;
*out = Ch('D'), ++out;
*out = Ch('O'), ++out;
*out = Ch('C'), ++out;
*out = Ch('T'), ++out;
*out = Ch('Y'), ++out;
*out = Ch('P'), ++out;
*out = Ch('E'), ++out;
*out = Ch(' '), ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch('>'), ++out;
return out;
}
// Print pi node
template<class OutIt, class Ch>
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
{
assert(node->type() == node_pi);
if (!(flags & print_no_indenting))
out = fill_chars(out, indent, Ch('\t'));
*out = Ch('<'), ++out;
*out = Ch('?'), ++out;
out = copy_chars(node->name(), node->name() + node->name_size(), out);
*out = Ch(' '), ++out;
out = copy_chars(node->value(), node->value() + node->value_size(), out);
*out = Ch('?'), ++out;
*out = Ch('>'), ++out;
return out;
}
}
//! \endcond
///////////////////////////////////////////////////////////////////////////
// Printing
//! Prints XML to given output iterator.
//! \param out Output iterator to print to.
//! \param node Node to be printed. Pass xml_document to print entire document.
//! \param flags Flags controlling how XML is printed.
//! \return Output iterator pointing to position immediately after last character of printed text.
template<class OutIt, class Ch>
inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)
{
return internal::print_node(out, &node, flags, 0);
}
#ifndef RAPIDXML_NO_STREAMS
//! Prints XML to given output stream.
//! \param out Output stream to print to.
//! \param node Node to be printed. Pass xml_document to print entire document.
//! \param flags Flags controlling how XML is printed.
//! \return Output stream.
template<class Ch>
inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)
{
print(std::ostream_iterator<Ch>(out), node, flags);
return out;
}
//! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.
//! \param out Output stream to print to.
//! \param node Node to be printed.
//! \return Output stream.
template<class Ch>
inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)
{
return print(out, node);
}
#endif
}
#endif

View File

@@ -0,0 +1,37 @@
// stb_dxt.cpp - Real-Time DXT1/DXT5 compressor
// Based on original by fabian "ryg" giesen v1.04
// Custom version, modified by Yann Collet
//
/*
BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
You can contact the author at :
- RygsDXTc source repository : http://code.google.com/p/rygsdxtc/
*/
#define STB_DXT_IMPLEMENTATION
#include "stb_dxt.h"

1043
external/layeredfs/3rd_party/stb_dxt.h vendored Normal file

File diff suppressed because it is too large Load Diff

9
external/layeredfs/LICENSE vendored Normal file
View File

@@ -0,0 +1,9 @@
Treat the license for this code as though it were the Apache-2.0 license,
WITH THE FOLLOWING MODIFICATIONS:
If you integrate LayeredFS into your launcher of choice, you MUST NOT modify the
existing commandline argument names.
If you add any commandline arguments, they MUST begin with `--layered-`.
I'm sure this lax wording is not legally binding, but please don't be an asshole.

38
external/layeredfs/config.cpp vendored Normal file
View File

@@ -0,0 +1,38 @@
#include <string.h>
#include <windows.h>
#include <shellapi.h>
#include "config.h"
#include "utils.h"
#define VERBOSE_FLAG L"--layered-verbose"
#define DEVMODE_FLAG L"--layered-devmode"
namespace layeredfs {
config_t config{};
void load_config(void) {
LPWSTR *szArglist;
int nArgs;
int i;
szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if (NULL == szArglist) {
return;
}
for (i = 0; i < nArgs; i++) {
if (lstrcmpW(szArglist[i], VERBOSE_FLAG) == 0) {
config.verbose_logs = true;
} else if (lstrcmpW(szArglist[i], DEVMODE_FLAG) == 0) {
config.developer_mode = true;
}
}
// Free memory allocated for CommandLineToArgvW arguments.
LocalFree(szArglist);
logf("Options: %ls=%d %ls=%d", VERBOSE_FLAG, config.verbose_logs, DEVMODE_FLAG, config.developer_mode);
}
}

13
external/layeredfs/config.h vendored Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
namespace layeredfs {
typedef struct config {
bool verbose_logs = false;
bool developer_mode = false;
} config_t;
extern config_t config;
void load_config(void);
}

847
external/layeredfs/hook.cpp vendored Normal file
View File

@@ -0,0 +1,847 @@
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include "hook.h"
// all good code mixes C and C++, right?
using std::string;
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
#include <fstream>
#include "external/hash-library/md5.h"
#include "3rd_party/lodepng.h"
#include "3rd_party/stb_dxt.h"
#include "3rd_party/GuillotineBinPack.h"
#include "3rd_party/rapidxml_print.hpp"
#include "util/detour.h"
#include "config.h"
#include "utils.h"
#include "texture_packer.h"
#include "modpath_handler.h"
// let me use the std:: version, dammit
#undef max
#undef min
#define VER_STRING "1.9-SPICE"
#ifdef _DEBUG
#define DBG_VER_STRING "_DEBUG"
#else
#define DBG_VER_STRING
#endif
#define VERSION VER_STRING DBG_VER_STRING
// debugging
//#define ALWAYS_CACHE
namespace layeredfs {
time_t dll_time{};
bool initialized;
enum img_format {
ARGB8888REV,
DXT5,
UNSUPPORTED_FORMAT,
};
enum compress_type {
NONE,
AVSLZ,
UNSUPPORTED_COMPRESS,
};
typedef struct image {
string name;
string name_md5;
img_format format;
compress_type compression;
string ifs_mod_path;
int width;
int height;
const string cache_folder() { return CACHE_FOLDER "/" + ifs_mod_path; }
const string cache_file() { return cache_folder() + "/" + name_md5; };
} image_t;
// ifs_textures["data/graphics/ver04/logo.ifs/tex/4f754d4f424f092637a49a5527ece9bb"] will be "konami"
std::unordered_map<string, image_t> ifs_textures;
typedef std::unordered_set<string> string_set;
char *avs_file_to_string(avs::core::avs_file_t f, rapidxml::xml_document<> &allocator) {
avs::core::avs_stat stat{};
avs::core::avs_fs_fstat(f, &stat);
char *ret = allocator.allocate_string(nullptr, stat.filesize + 1);
avs::core::avs_fs_read(f, (uint8_t *) ret, stat.filesize);
ret[stat.filesize] = '\0';
return ret;
}
bool is_binary_prop(avs::core::avs_file_t f) {
avs::core::avs_fs_lseek(f, 0, SEEK_SET);
unsigned char head;
auto read = avs::core::avs_fs_read(f, &head, 1);
bool ret = (read == 1) && head == 0xA0;
avs::core::avs_fs_lseek(f, 0, SEEK_SET);
//logf("detected binary: %s (read %d byte %02x)", ret ? "true": "false", read, head&0xff);
return ret;
}
avs::core::property_ptr prop_from_file_handle(avs::core::avs_file_t f) {
void *prop_buffer = nullptr;
avs::core::property_ptr prop = nullptr;
int ret = 0;
int flags =
avs::core::PROP_READ |
avs::core::PROP_WRITE |
avs::core::PROP_CREATE |
avs::core::PROP_APPEND |
avs::core::PROP_BIN_PLAIN_NODE_NAMES;
auto memsize = avs::core::property_read_query_memsize_long(
(avs::core::avs_reader_t) avs::core::avs_fs_read,
f, nullptr, nullptr, nullptr);
if (memsize < 0) {
// normal prop
flags &= ~avs::core::PROP_BIN_PLAIN_NODE_NAMES;
avs::core::avs_fs_lseek(f, 0, SEEK_SET);
memsize = avs::core::property_read_query_memsize(
(avs::core::avs_reader_t) avs::core::avs_fs_read,
f, nullptr, nullptr);
if (memsize < 0) {
logf("Couldn't get memsize %08X (%s)", memsize, avs::core::error_str(memsize).c_str());
goto FAIL;
}
}
prop_buffer = malloc(memsize);
prop = avs::core::property_create(flags, prop_buffer, memsize);
if ((int32_t) (intptr_t) prop < 0) {
logf("Couldn't create prop (%s)", avs::core::error_str((int32_t) (intptr_t) prop).c_str());
goto FAIL;
}
avs::core::avs_fs_lseek(f, 0, SEEK_SET);
ret = avs::core::property_insert_read(prop, 0, (avs::core::avs_reader_t) avs::core::avs_fs_read, f);
avs::core::avs_fs_close(f);
if (ret < 0) {
logf("Couldn't read prop (%s)", avs::core::error_str(ret).c_str());
goto FAIL;
}
return prop;
FAIL:
avs::core::avs_fs_close(f);
if (prop) {
avs::core::property_destroy(prop);
}
if (prop_buffer) {
free(prop_buffer);
}
return nullptr;
}
char *prop_to_xml_string(avs::core::property_ptr prop, rapidxml::xml_document<> &allocator) {
avs::core::node_stat dummy{};
auto prop_size = avs::core::property_node_query_stat(prop, nullptr, &dummy);
char *xml = allocator.allocate_string(nullptr, prop_size);
auto written = avs::core::property_mem_write(prop, (uint8_t *) xml, prop_size);
if (written > 0) {
xml[written] = '\0';
} else {
xml[0] = '\0';
logf("property_mem_write failed (%s)", avs::core::error_str(written).c_str());
}
return xml;
}
void rapidxml_dump_to_file(const string &out, const rapidxml::xml_document<> &xml) {
std::ofstream out_file;
out_file.open(out.c_str());
// this is 3x faster than writing directly to the output file
std::string s;
print(std::back_inserter(s), xml, rapidxml::print_no_indenting);
out_file << s;
out_file.close();
}
bool rapidxml_from_avs_filepath(
string const &path,
rapidxml::xml_document<> &doc,
rapidxml::xml_document<> &doc_to_allocate_with
) {
avs::core::avs_file_t f = avs::core::avs_fs_open(path.c_str(), 1, 420);
if ((int32_t) f < 0) {
logf("Couldn't open prop");
return false;
}
// if it's not binary, don't even bother parsing with avs
char *xml = nullptr;
if (is_binary_prop(f)) {
auto prop = prop_from_file_handle(f);
if (!prop)
return false;
xml = prop_to_xml_string(prop, doc_to_allocate_with);
// clean up
auto buffer = avs::core::property_desc_to_buffer(prop);
avs::core::property_destroy(prop);
if (buffer) {
free(buffer);
}
} else {
xml = avs_file_to_string(f, doc_to_allocate_with);
}
avs::core::avs_fs_close(f);
try {
// parse_declaration_node: to get the header <?xml version="1.0" encoding="shift-jis"?>
doc.parse<rapidxml::parse_declaration_node | rapidxml::parse_no_utf8>(xml);
} catch (const rapidxml::parse_error &e) {
logf("Couldn't parse xml (%s byte %d)", e.what(), (int) (e.where<char>() - xml));
return false;
}
return true;
}
void list_pngs_onefolder(string_set &names, string const &folder) {
auto search_path = folder + "/*.png";
const auto extension_len = strlen(".png");
WIN32_FIND_DATAA fd;
HANDLE hFind = FindFirstFileA(search_path.c_str(), &fd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
// read all (real) files in current folder
// , delete '!' read other 2 default folder . and ..
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
fd.cFileName[strlen(fd.cFileName) - extension_len] = '\0';
names.insert(fd.cFileName);
}
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
}
}
string_set list_pngs(string const &folder) {
string_set ret;
for (auto &mod : available_mods()) {
auto path = mod + "/" + folder;
list_pngs_onefolder(ret, path);
list_pngs_onefolder(ret, path + "/tex");
}
return ret;
}
#define FMT_4U16(arr) "%u %u %u %u", (arr)[0], (arr)[1], (arr)[2], (arr)[3]
#define FMT_2U16(arr) "%u %u", (arr)[0], (arr)[1]
rapidxml::xml_node<> *allocate_node_and_attrib(
rapidxml::xml_document<> *doc,
const char *node_name,
const char *node_value,
const char *attr_name,
const char *attr_value) {
auto node = doc->allocate_node(rapidxml::node_element, node_name, node_value);
node->append_attribute(doc->allocate_attribute(attr_name, attr_value));
return node;
}
bool add_images_to_list(string_set &extra_pngs, rapidxml::xml_node<> *texturelist_node,
string const &ifs_path, string const &ifs_mod_path, compress_type compress) {
auto start = time();
vector<Bitmap *> textures;
for (auto it = extra_pngs.begin(); it != extra_pngs.end(); ++it) {
logf_verbose("New image: %s", it->c_str());
string png_tex = *it + ".png";
auto png_loc = find_first_modfile(ifs_mod_path + "/" + png_tex);
if (!png_loc)
png_loc = find_first_modfile(ifs_mod_path + "/tex/" + png_tex);
if (!png_loc)
continue;
FILE *f = fopen(png_loc->c_str(), "rb");
if (!f) // shouldn't happen but check anyway
continue;
unsigned char header[33];
// this may read less bytes than expected but lodepng will die later anyway
fread(header, 1, 33, f);
fclose(f);
unsigned width, height;
LodePNGState state = {};
if (lodepng_inspect(&width, &height, &state, header, 33)) {
logf("couldn't inspect png");
continue;
}
textures.push_back(new Bitmap(*it, width, height));
}
auto pack_start = time();
vector<Packer *> packed_textures;
logf("Packing textures for %d textures", textures.size());
if (!pack_textures(textures, packed_textures)) {
logf("Couldn't pack textures :(");
return false;
}
logf("Textures packed in %d ms into %d objects", time() - pack_start, packed_textures.size());
// because the property API, being
// a) written by Konami
// b) not documented since lol DLLs
// is absolutely garbage to work with for merging XMLs, we use rapidxml instead
// thus I hope my sanity is restored.
auto document = texturelist_node->document();
for (unsigned int i = 0; i < packed_textures.size(); i++) {
Packer *canvas = packed_textures[i];
char tex_name[8];
snprintf(tex_name, 8, "ctex%03d", i);
auto canvas_node = document->allocate_node(rapidxml::node_element, "texture");
texturelist_node->append_node(canvas_node);
canvas_node->append_attribute(document->allocate_attribute("format", "argb8888rev"));
canvas_node->append_attribute(document->allocate_attribute("mag_filter", "nearest"));
canvas_node->append_attribute(document->allocate_attribute("min_filter", "nearest"));
canvas_node->append_attribute(document->allocate_attribute("name", document->allocate_string(tex_name)));
canvas_node->append_attribute(document->allocate_attribute("wrap_s", "clamp"));
canvas_node->append_attribute(document->allocate_attribute("wrap_t", "clamp"));
char tmp[64];
uint16_t size[2] = {(uint16_t) canvas->width, (uint16_t) canvas->height};
snprintf(tmp, sizeof(tmp), FMT_2U16(size));
canvas_node->append_node(
allocate_node_and_attrib(document, "size", document->allocate_string(tmp), "__type", "2u16"));
for (unsigned int j = 0; j < canvas->bitmaps.size(); j++) {
Bitmap *texture = canvas->bitmaps[j];
auto tex_node = document->allocate_node(rapidxml::node_element, "image");
canvas_node->append_node(tex_node);
tex_node->append_attribute(
document->allocate_attribute("name", document->allocate_string(texture->name.c_str())));
uint16_t coords[4];
coords[0] = texture->packX * 2;
coords[1] = (texture->packX + texture->width) * 2;
coords[2] = texture->packY * 2;
coords[3] = (texture->packY + texture->height) * 2;
snprintf(tmp, sizeof(tmp), FMT_4U16(coords));
tex_node->append_node(
allocate_node_and_attrib(document, "imgrect", document->allocate_string(tmp), "__type",
"4u16"));
coords[0] += 2;
coords[1] -= 2;
coords[2] += 2;
coords[3] -= 2;
snprintf(tmp, sizeof(tmp), FMT_4U16(coords));
tex_node->append_node(
allocate_node_and_attrib(document, "uvrect", document->allocate_string(tmp), "__type", "4u16"));
// generate md5
MD5 md5_tex_name;
md5_tex_name.add(texture->name.c_str(), texture->name.length());
// build image info
image_t image_info;
image_info.name = texture->name;
image_info.name_md5 = md5_tex_name.getHash();
image_info.format = ARGB8888REV;
image_info.compression = compress;
image_info.ifs_mod_path = ifs_mod_path;
image_info.width = texture->width;
image_info.height = texture->height;
auto md5_path = ifs_path + "/tex/" + image_info.name_md5;
ifs_textures[md5_path] = image_info;
}
}
logf("Texture extend total time %d ms", time() - start);
return true;
}
void parse_texturelist(string const &path, string const &norm_path, optional<string> &mod_path) {
bool prop_was_rewritten = false;
// get a reasonable base path
auto ifs_path = norm_path;
ifs_path.resize(ifs_path.size() - strlen("/tex/texturelist.xml"));
logf_verbose("Reading ifs %s", ifs_path.c_str());
auto ifs_mod_path = ifs_path;
string_replace(ifs_mod_path, ".ifs", "_ifs");
// TODO ifs_path gets invalid later this function for unknown reasons
auto ifs_path2 = ifs_path;
if (!find_first_modfolder(ifs_mod_path)) {
logf_verbose("mod folder doesn't exist, skipping");
return;
}
// open the correct file
auto path_to_open = mod_path ? *mod_path : path;
rapidxml::xml_document<> texturelist;
auto success = rapidxml_from_avs_filepath(path_to_open, texturelist, texturelist);
if (!success)
return;
auto texturelist_node = texturelist.first_node("texturelist");
if (!texturelist_node) {
logf("texlist has no texturelist node");
return;
}
auto extra_pngs = list_pngs(ifs_mod_path);
auto compress = NONE;
rapidxml::xml_attribute<> *compress_node;
if ((compress_node = texturelist_node->first_attribute("compress"))) {
if (!_stricmp(compress_node->value(), "avslz")) {
compress = AVSLZ;
} else {
compress = UNSUPPORTED_COMPRESS;
}
}
for (auto texture = texturelist_node->first_node("texture");
texture;
texture = texture->next_sibling("texture")) {
auto format = texture->first_attribute("format");
if (!format) {
logf("Texture missing format %s", path_to_open.c_str());
continue;
}
//<size __type="2u16">128 128</size>
auto size = texture->first_node("size");
if (!size) {
logf("Texture missing size %s", path_to_open.c_str());
continue;
}
auto format_type = UNSUPPORTED_FORMAT;
if (!_stricmp(format->value(), "argb8888rev")) {
format_type = ARGB8888REV;
} else if (!_stricmp(format->value(), "dxt5")) {
format_type = DXT5;
}
for (auto image = texture->first_node("image");
image;
image = image->next_sibling("image")) {
auto name = image->first_attribute("name");
if (!name) {
logf("Texture missing name %s", path_to_open.c_str());
continue;
}
uint16_t dimensions[4];
auto imgrect = image->first_node("imgrect");
auto uvrect = image->first_node("uvrect");
if (!imgrect || !uvrect) {
logf("Texture missing dimensions %s", path_to_open.c_str());
continue;
}
// it's a 4u16
sscanf(imgrect->value(), "%hu %hu %hu %hu",
&dimensions[0],
&dimensions[1],
&dimensions[2],
&dimensions[3]);
// generate md5
MD5 md5_name;
md5_name.add(name->value(), strlen(name->value()));
//logf("Image '%s' compress %d format %d", tmp, compress, format_type);
image_t image_info;
image_info.name = name->value();
image_info.name_md5 = md5_name.getHash();
image_info.format = format_type;
image_info.compression = compress;
image_info.ifs_mod_path = ifs_mod_path;
image_info.width = (dimensions[1] - dimensions[0]) / 2;
image_info.height = (dimensions[3] - dimensions[2]) / 2;
auto md5_path = ifs_path2 + "/tex/" + image_info.name_md5;
ifs_textures[md5_path] = image_info;
// TODO: why does this make it not crash
if (config.verbose_logs) {
log_info("md5", "{}", md5_path);
}
extra_pngs.erase(image_info.name);
}
}
logf_verbose("%d added PNGs for %s", extra_pngs.size(), ifs_path2.c_str());
if (!extra_pngs.empty()) {
if (add_images_to_list(extra_pngs, texturelist_node, ifs_path2, ifs_mod_path, compress))
prop_was_rewritten = true;
}
if (prop_was_rewritten) {
string outfolder = CACHE_FOLDER "/" + ifs_mod_path;
if (!mkdir_p(outfolder)) {
logf("Couldn't create cache folder");
}
string outfile = outfolder + "/texturelist.xml";
rapidxml_dump_to_file(outfile, texturelist);
mod_path = outfile;
}
}
bool cache_texture(string const &png_path, image_t &tex) {
string cache_path = tex.cache_folder();
if (!mkdir_p(cache_path)) {
logf("Couldn't create texture cache folder");
return false;
}
string cache_file = tex.cache_file();
auto cache_time = file_time(cache_file.c_str());
auto png_time = file_time(png_path.c_str());
// the cache is fresh, don't do the same work twice
#ifndef ALWAYS_CACHE
if (cache_time > 0 && cache_time >= dll_time && cache_time >= png_time) {
return true;
}
#endif
// make the cache
FILE *cache;
unsigned error;
unsigned char *image;
unsigned width, height; // TODO use these to check against xml
error = lodepng_decode32_file(&image, &width, &height, png_path.c_str());
if (error) {
logf("can't load png %u: %s\n", error, lodepng_error_text(error));
return false;
}
if ((int) width != tex.width || (int) height != tex.height) {
logf("Loaded png (%dx%d) doesn't match texturelist.xml (%dx%d), ignoring", width, height, tex.width,
tex.height);
return false;
}
size_t image_size = 4 * width * height;
switch (tex.format) {
case ARGB8888REV:
for (size_t i = 0; i < image_size; i += 4) {
// swap r and b
auto tmp = image[i];
image[i] = image[i + 2];
image[i + 2] = tmp;
}
break;
case DXT5: {
size_t dxt5_size = image_size / 4;
auto *dxt5_image = (unsigned char *) malloc(dxt5_size);
rygCompress(dxt5_image, image, width, height, 1);
free(image);
image = dxt5_image;
image_size = dxt5_size;
// the data has swapped endianness for every WORD
for (size_t i = 0; i < image_size; i += 2) {
auto tmp = image[i];
image[i] = image[i + 1];
image[i + 1] = tmp;
}
break;
}
default:
break;
}
auto uncompressed_size = image_size;
if (tex.compression == AVSLZ) {
size_t compressed_size;
auto compressed = lz_compress(image, image_size, &compressed_size);
free(image);
if (compressed == nullptr) {
logf("Couldn't compress");
return false;
}
image = compressed;
image_size = compressed_size;
}
cache = std::fopen(cache_file.c_str(), "wb");
if (!cache) {
logf("can't open cache for writing");
return false;
}
if (tex.compression == AVSLZ) {
uint32_t uncomp_sz = _byteswap_ulong((uint32_t) uncompressed_size);
uint32_t comp_sz = _byteswap_ulong((uint32_t) image_size);
fwrite(&uncomp_sz, 4, 1, cache);
fwrite(&comp_sz, 4, 1, cache);
}
fwrite(image, 1, image_size, cache);
fclose(cache);
free(image);
return true;
}
void handle_texture(string const &norm_path, optional<string> &mod_path) {
logf_verbose("handle_texture");
auto tex_search = ifs_textures.find(norm_path);
if (tex_search == ifs_textures.end()) {
return;
}
logf_verbose("Mapped file %s is found!", norm_path.c_str());
auto tex = tex_search->second;
// remove the /tex/, it's nicer to navigate
auto png_path = find_first_modfile(tex.ifs_mod_path + "/" + tex.name + ".png");
if (!png_path) {
// but maybe they used it anyway
png_path = find_first_modfile(tex.ifs_mod_path + "/tex/" + tex.name + ".png");
if (!png_path)
return;
}
if (tex.compression == UNSUPPORTED_COMPRESS) {
logf("Unsupported compression for %s", png_path->c_str());
return;
}
if (tex.format == UNSUPPORTED_FORMAT) {
logf("Unsupported texture format for %s", png_path->c_str());
return;
}
logf_verbose("Mapped file %s found!", png_path->c_str());
if (cache_texture(*png_path, tex)) {
mod_path = tex.cache_file();
}
}
void hash_filenames(vector<string> &filenames, uint8_t hash[16]) {
MD5 digest;
for (auto &path : filenames) {
digest.add(path.c_str(), path.length());
}
digest.getHash(hash);
}
void merge_xmls(string const &path, string const &norm_path, optional<string> &mod_path) {
auto start = time();
string out;
string out_folder;
rapidxml::xml_document<> merged_xml;
auto merge_path = norm_path;
string_replace(merge_path, ".xml", ".merged.xml");
auto to_merge = find_all_modfile(merge_path);
// nothing to do...
if (to_merge.empty()) {
return;
}
auto starting = mod_path ? *mod_path : path;
out = CACHE_FOLDER "/" + norm_path;
auto out_hashed = out + ".hashed";
uint8_t hash[16];
hash_filenames(to_merge, hash);
uint8_t cache_hash[16] = {0};
FILE *cache_hashfile{};
cache_hashfile = std::fopen(out_hashed.c_str(), "rb");
if (cache_hashfile) {
fread(cache_hash, sizeof(uint8_t), std::size(cache_hash), cache_hashfile);
fclose(cache_hashfile);
}
auto time_out = file_time(out.c_str());
// don't forget to take the input into account
time_t newest = file_time(starting.c_str());
for (auto &path : to_merge)
newest = std::max(newest, file_time(path.c_str()));
// no need to merge - timestamps all up to date, dll not newer, files haven't been deleted
if (time_out >= newest && time_out >= dll_time && memcmp(hash, cache_hash, sizeof(hash)) == 0) {
mod_path = out;
return;
}
auto first_result = rapidxml_from_avs_filepath(starting, merged_xml, merged_xml);
if (!first_result) {
logf("Couldn't merge (can't load first xml %s)", starting.c_str());
return;
}
logf("Merging into %s", starting.c_str());
for (auto &path : to_merge) {
logf(" %s", path.c_str());
rapidxml::xml_document<> rapid_to_merge;
auto merge_load_result = rapidxml_from_avs_filepath(path, rapid_to_merge, merged_xml);
if (!merge_load_result) {
logf("Couldn't merge (can't load xml) %s", path.c_str());
return;
}
// toplevel nodes include doc declaration and mdb node
// getting the last node grabs the mdb node
// document -> mdb entry -> music entry
for (rapidxml::xml_node<> *node = rapid_to_merge.last_node()->first_node(); node; node = node->next_sibling()) {
merged_xml.last_node()->append_node(merged_xml.clone_node(node));
}
}
auto folder_terminator = out.rfind("/");
out_folder = out.substr(0, folder_terminator);
if (!mkdir_p(out_folder)) {
logf("Couldn't create merged cache folder");
}
rapidxml_dump_to_file(out, merged_xml);
cache_hashfile = std::fopen(out_hashed.c_str(), "wb");
if (cache_hashfile) {
fwrite(hash, 1, sizeof(hash), cache_hashfile);
fclose(cache_hashfile);
}
mod_path = out;
logf("Merge took %d ms", time() - start);
}
int hook_avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
if (name == nullptr)
return avs::core::avs_fs_lstat(name, st);
logf_verbose("statting %s", name);
string path = name;
// can it be modded?
auto norm_path = normalise_path(path);
if (!norm_path)
return avs::core::avs_fs_lstat(name, st);
auto mod_path = find_first_modfile(*norm_path);
if (mod_path) {
logf_verbose("Overwriting lstat");
return avs::core::avs_fs_lstat(mod_path->c_str(), st);
}
return avs::core::avs_fs_lstat(name, st);
}
avs::core::avs_file_t hook_avs_fs_open(const char *name, uint16_t mode, int flags) {
if (name == nullptr)
return avs::core::avs_fs_open(name, mode, flags);
logf_verbose("opening %s mode %d flags %d", name, mode, flags);
// only touch reads
if (mode != 1) {
return avs::core::avs_fs_open(name, mode, flags);
}
string path = name;
// can it be modded ie is it under /data ?
auto _norm_path = normalise_path(path);
if (!_norm_path)
return avs::core::avs_fs_open(name, mode, flags);
// unpack success
auto norm_path = *_norm_path;
auto mod_path = find_first_modfile(norm_path);
if (!mod_path) {
// mod ifs paths use _ifs
string_replace(norm_path, ".ifs", "_ifs");
mod_path = find_first_modfile(norm_path);
}
if (string_ends_with(path.c_str(), ".xml")) {
merge_xmls(path, norm_path, mod_path);
}
if (string_ends_with(path.c_str(), "texturelist.xml")) {
parse_texturelist(path, norm_path, mod_path);
} else {
handle_texture(norm_path, mod_path);
}
if (mod_path) {
logf("Using %s", mod_path->c_str());
}
auto to_open = mod_path ? *mod_path : path;
auto ret = avs::core::avs_fs_open(to_open.c_str(), mode, flags);
logf_verbose("returned %d", ret);
return ret;
}
int init() {
logf("IFS layeredFS v"
VERSION
" init...");
// init DLL time
char dll_filename[MAX_PATH];
if (GetModuleFileNameA(GetModuleHandle(nullptr), dll_filename, sizeof(dll_filename))) {
dll_time = file_time(dll_filename);
}
// init functions
load_config();
cache_mods();
// check if required functions are available
if (avs::core::avs_fs_open == nullptr || avs::core::avs_fs_lstat == nullptr
|| avs::core::property_node_read == nullptr) {
logf("optional avs imports not available :(");
return 1;
}
logf("Hooking ifs operations");
initialized = true;
logf("Detected mod folders:");
for (auto &p : available_mods()) {
logf("%s", p.c_str());
}
return 0;
}
}

14
external/layeredfs/hook.h vendored Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <windows.h>
#include "avs/core.h"
namespace layeredfs {
extern time_t dll_time;
extern bool initialized;
int hook_avs_fs_lstat(const char *name, struct avs::core::avs_stat *st);
avs::core::avs_file_t hook_avs_fs_open(const char *name, uint16_t mode, int flags);
int init(void);
}

227
external/layeredfs/modpath_handler.cpp vendored Normal file
View File

@@ -0,0 +1,227 @@
#include <windows.h>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include "modpath_handler.h"
#include "config.h"
#include "utils.h"
using std::nullopt;
namespace layeredfs {
typedef struct {
std::string name;
std::unordered_set<string> contents;
} mod_contents_t;
std::vector<mod_contents_t> cached_mods;
std::unordered_set<string> walk_dir(const string &path, const string &root) {
std::unordered_set<string> result;
WIN32_FIND_DATAA ffd;
auto contents = FindFirstFileA((path + "/*").c_str(), &ffd);
if (contents != INVALID_HANDLE_VALUE) {
do {
if (!strcmp(ffd.cFileName, ".") ||
!strcmp(ffd.cFileName, "..")) {
continue;
}
string result_path;
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
result_path = root + ffd.cFileName + "/";
logf_verbose(" %s", result_path.c_str());
auto subdir_walk = walk_dir(path + "/" + ffd.cFileName, result_path);
result.insert(subdir_walk.begin(), subdir_walk.end());
} else {
result_path = root + ffd.cFileName;
logf_verbose(" %s", result_path.c_str());
}
result.insert(result_path);
} while (FindNextFileA(contents, &ffd) != 0);
FindClose(contents);
}
return result;
}
void cache_mods(void) {
if (config.developer_mode)
return;
// this is a bit hacky
config.developer_mode = true;
auto avail_mods = available_mods();
config.developer_mode = false;
for (auto &dir : avail_mods) {
logf_verbose("Walking %s", dir.c_str());
mod_contents_t mod;
mod.name = dir;
mod.contents = walk_dir(dir, "");
cached_mods.push_back(mod);
}
}
optional<string> normalise_path(const string &path) {
auto data_pos = path.find("data/");
auto data2_pos = string::npos;
if (data_pos == string::npos) {
data2_pos = path.find("data2/");
if (data2_pos == string::npos)
return nullopt;
}
auto actual_pos = (data_pos != string::npos) ? data_pos : data2_pos;
// if data2 was found, use root data2/.../... instead of just .../...
auto offset = (data2_pos != string::npos) ? 0 : strlen("data/");
auto data_str = path.substr(actual_pos + offset);
// nuke backslash
string_replace(data_str, "\\", "/");
// nuke double slash
string_replace(data_str, "//", "/");
return data_str;
}
vector<string> available_mods() {
vector<string> ret;
string mod_root = MOD_FOLDER "/";
if (config.developer_mode) {
WIN32_FIND_DATAA ffd;
auto mods = FindFirstFileA(MOD_FOLDER "/*", &ffd);
if (mods != INVALID_HANDLE_VALUE) {
do {
if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
!strcmp(ffd.cFileName, ".") ||
!strcmp(ffd.cFileName, "..") ||
!strcmp(ffd.cFileName, "_cache")) {
continue;
}
ret.push_back(mod_root + ffd.cFileName);
} while (FindNextFileA(mods, &ffd) != 0);
FindClose(mods);
}
} else {
for (auto &dir : cached_mods) {
ret.push_back(dir.name);
}
}
std::sort(ret.begin(), ret.end());
return ret;
}
bool mkdir_p(string &path) {
/* Adapted from http://stackoverflow.com/a/2336245/119527 */
const size_t len = strlen(path.c_str());
char _path[MAX_PATH + 1];
char *p;
errno = 0;
/* Copy string so its mutable */
if (len > sizeof(_path) - 1) {
return false;
}
strncpy(_path, path.c_str(), MAX_PATH);
_path[MAX_PATH] = '\0';
/* Iterate the string */
for (p = _path + 1; *p; p++) {
if (*p == '/') {
/* Temporarily truncate */
*p = '\0';
if (!CreateDirectoryA(_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
return false;
}
*p = '/';
}
}
if (!CreateDirectoryA(_path, NULL)) {
if (GetLastError() != ERROR_ALREADY_EXISTS) {
return false;
}
}
return true;
}
// same for files and folders when cached
optional<string> find_first_cached_item(const string &norm_path) {
for (auto &dir : cached_mods) {
auto file_search = dir.contents.find(norm_path);
if (file_search == dir.contents.end()) {
continue;
}
return dir.name + "/" + *file_search;
}
return nullopt;
}
optional<string> find_first_modfile(const string &norm_path) {
if (config.developer_mode) {
for (auto &dir : available_mods()) {
auto mod_path = dir + "/" + norm_path;
if (file_exists(mod_path.c_str())) {
return mod_path;
}
}
} else {
return find_first_cached_item(norm_path);
}
return nullopt;
}
optional<string> find_first_modfolder(const string &norm_path) {
if (config.developer_mode) {
for (auto &dir : available_mods()) {
auto mod_path = dir + "/" + norm_path;
if (folder_exists(mod_path.c_str())) {
return mod_path;
}
}
} else {
return find_first_cached_item(norm_path + "/");
}
return nullopt;
}
vector<string> find_all_modfile(const string &norm_path) {
vector<string> ret;
if (config.developer_mode) {
for (auto &dir : available_mods()) {
auto mod_path = dir + "/" + norm_path;
if (file_exists(mod_path.c_str())) {
ret.push_back(mod_path);
}
}
} else {
for (auto &dir : cached_mods) {
auto file_search = dir.contents.find(norm_path);
if (file_search == dir.contents.end()) {
continue;
}
ret.push_back(dir.name + "/" + *file_search);
}
}
// needed for consistency when hashing names
std::sort(ret.begin(), ret.end());
return ret;
}
}

29
external/layeredfs/modpath_handler.h vendored Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include <string>
#include <vector>
#if 0
#include <experimental/optional>
using std::experimental::optional;
#else
#include <optional>
using std::optional;
#endif
using std::string;
using std::vector;
#define MOD_FOLDER "./data_mods"
#define CACHE_FOLDER MOD_FOLDER "/_cache"
namespace layeredfs {
void cache_mods(void);
vector<string> available_mods();
optional<string> normalise_path(const string &path);
optional<string> find_first_modfile(const string &norm_path);
optional<string> find_first_modfolder(const string &norm_path);
vector<string> find_all_modfile(const string &norm_path);
bool mkdir_p(string &path);
}

69
external/layeredfs/texture_packer.cpp vendored Normal file
View File

@@ -0,0 +1,69 @@
#include "texture_packer.h"
#include <algorithm>
#include "3rd_party/GuillotineBinPack.h"
using namespace rbp;
namespace layeredfs {
Bitmap::Bitmap(const string &name, int width, int height)
: name(name), width(width), height(height) {
}
bool pack_textures(vector<Bitmap *> &textures, vector<Packer *> &packed_textures) {
std::sort(textures.begin(), textures.end(), [](const Bitmap *a, const Bitmap *b) {
return (a->width * a->height) < (b->width * b->height);
});
// pack the bitmaps
while (!textures.empty()) {
auto packer = new Packer(MAX_TEXTURE);
packer->Pack(textures);
packed_textures.push_back(packer);
// failed
if (packer->bitmaps.empty())
return false;
}
return true;
}
Packer::Packer(int max_size)
: width(max_size), height(max_size) {
}
void Packer::Pack(vector<Bitmap *> &bitmaps) {
GuillotineBinPack packer(width, height);
int ww = 0;
int hh = 0;
while (!bitmaps.empty()) {
auto bitmap = bitmaps.back();
Rect rect = packer.Insert(bitmap->width, bitmap->height, false,
GuillotineBinPack::FreeRectChoiceHeuristic::RectBestAreaFit,
GuillotineBinPack::GuillotineSplitHeuristic::SplitLongerAxis);
if (rect.width == 0 || rect.height == 0)
break;
bitmap->packX = rect.x;
bitmap->packY = rect.y;
this->bitmaps.push_back(bitmap);
bitmaps.pop_back();
ww = std::max(rect.x + rect.width, ww);
hh = std::max(rect.y + rect.height, hh);
}
while (width / 2 >= ww)
width /= 2;
while (height / 2 >= hh)
height /= 2;
}
}

35
external/layeredfs/texture_packer.h vendored Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#define MAX_TEXTURE 4096
#include <string>
#include <vector>
using std::string;
using std::vector;
namespace layeredfs {
struct Bitmap {
string name;
int width;
int height;
int packX;
int packY;
Bitmap(const string &name, int width, int height);
};
struct Packer {
int width;
int height;
vector<Bitmap *> bitmaps;
Packer(int max_size);
void Pack(vector<Bitmap *> &bitmaps);
};
bool pack_textures(vector<Bitmap *> &textures, vector<Packer *> &packed_textures);
}

185
external/layeredfs/utils.cpp vendored Normal file
View File

@@ -0,0 +1,185 @@
#include "utils.h"
#include "avs/core.h"
#include "util/utils.h"
namespace layeredfs {
char *snprintf_auto(const char *fmt, ...) {
va_list argList;
va_start(argList, fmt);
size_t len = vsnprintf(NULL, 0, fmt, argList);
auto s = (char *) malloc(len + 1);
vsnprintf(s, len + 1, fmt, argList);
va_end(argList);
return s;
}
int string_ends_with(const char *str, const char *suffix) {
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
return
(str_len >= suffix_len) &&
(0 == strcmp(str + (str_len - suffix_len), suffix));
}
void string_replace(std::string &str, const char *from, const char *to) {
auto to_len = strlen(to);
auto from_len = strlen(from);
size_t offset = 0;
for (auto pos = str.find(from); pos != std::string::npos; pos = str.find(from, offset)) {
str.replace(pos, from_len, to);
// avoid recursion if to contains from
offset = pos + to_len;
}
}
wchar_t *str_widen(const char *src) {
int nchars;
wchar_t *result;
nchars = MultiByteToWideChar(CP_ACP, 0, src, -1, NULL, 0);
if (!nchars) {
abort();
}
result = (wchar_t *) malloc(nchars * sizeof(wchar_t));
if (!MultiByteToWideChar(CP_ACP, 0, src, -1, result, nchars)) {
abort();
}
return result;
}
bool file_exists(const char *name) {
auto res = avs::core::avs_fs_open(name, 1, 420);
if (res > 0)
avs::core::avs_fs_close(res);
return res > 0;
}
bool folder_exists(const char *name) {
WIN32_FIND_DATAA ffd;
HANDLE hFind = FindFirstFileA(name, &ffd);
if (hFind == INVALID_HANDLE_VALUE || !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
return false;
}
FindClose(hFind);
return true;
}
time_t file_time(const char *path) {
auto wide = str_widen(path);
auto hFile = CreateFileW(wide, // file to open
GENERIC_READ, // open for reading
FILE_SHARE_READ, // share for reading
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // normal file
NULL); // no attr. template
free(wide);
if (hFile == INVALID_HANDLE_VALUE)
return 0;
FILETIME mtime;
GetFileTime(hFile, NULL, NULL, &mtime);
CloseHandle(hFile);
ULARGE_INTEGER result;
result.LowPart = mtime.dwLowDateTime;
result.HighPart = mtime.dwHighDateTime;
return result.QuadPart;
}
LONG time(void) {
SYSTEMTIME time;
GetSystemTime(&time);
return (time.wSecond * 1000) + time.wMilliseconds;
}
uint8_t *lz_compress(uint8_t *input, size_t input_length, size_t *compressed_length) {
// check if cstream is unavailable
if (avs::core::cstream_create == nullptr) {
return lz_compress_dummy(input, input_length, compressed_length);
} else {
/*
* Compression using cstream
*/
auto compressor = avs::core::cstream_create(avs::core::CSTREAM_AVSLZ_COMPRESS);
if (!compressor) {
logf("Couldn't create");
return NULL;
}
compressor->in_buf = input;
compressor->in_size = (uint32_t) input_length;
// worst case, for every 8 bytes there will be an extra flag byte
auto to_add = MAX(input_length / 8, 1);
auto compress_size = input_length + to_add;
auto compress_buffer = (unsigned char*)malloc(compress_size);
compressor->out_buf = compress_buffer;
compressor->out_size = (uint32_t) compress_size;
bool ret;
ret = avs::core::cstream_operate(compressor);
if (!ret && !compressor->in_size) {
compressor->in_buf = NULL;
compressor->in_size = -1;
ret = avs::core::cstream_operate(compressor);
}
if (!ret) {
logf("Couldn't operate");
return NULL;
}
if (avs::core::cstream_finish(compressor)) {
logf("Couldn't finish");
return NULL;
}
*compressed_length = compress_size - compressor->out_size;
avs::core::cstream_destroy(compressor);
return compress_buffer;
}
}
uint8_t *lz_compress_dummy(uint8_t *input, size_t input_length, size_t *compressed_length) {
uint8_t *output = (uint8_t *) malloc(input_length + input_length / 8 + 9);
uint8_t *cur_byte = &output[0];
// copy data blocks
for (size_t n = 0; n < input_length / 8; n++) {
// fake flag
*cur_byte++ = 0xFF;
// uncompressed data
for (size_t i = 0; i < 8; i++) {
*cur_byte++ = input[n * 8 + i];
}
}
// remaining bytes
int extra_bytes = input_length % 8;
if (extra_bytes == 0) {
*cur_byte++ = 0x00;
} else {
*cur_byte++ = 0xFF >> (8 - extra_bytes);
for (size_t i = input_length - extra_bytes; i < input_length; i++) {
*cur_byte++ = input[i];
}
for (size_t i = 0; i < 4; i++) {
*cur_byte++ = 0x00;
}
}
// calculate size
*compressed_length = (size_t) (cur_byte - &output[0]);
return output;
}
}

28
external/layeredfs/utils.h vendored Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string>
#include "util/logging.h"
#include "config.h"
#define logf(fmt,...) {char*b=snprintf_auto(fmt,##__VA_ARGS__);log_misc("layeredfs","{}",b?b:":(");if(b)free(b);} void()
#define logf_verbose(...) if (config.verbose_logs) {logf(__VA_ARGS__);} void()
namespace layeredfs {
char *snprintf_auto(const char *fmt, ...);
int string_ends_with(const char *str, const char *suffix);
void string_replace(std::string &str, const char *from, const char *to);
wchar_t *str_widen(const char *src);
bool file_exists(const char *name);
bool folder_exists(const char *name);
time_t file_time(const char *path);
LONG time(void);
uint8_t *lz_compress(uint8_t *input, size_t input_length, size_t *compressed_length);
uint8_t *lz_compress_dummy(uint8_t *input, size_t input_length, size_t *compressed_length);
}