Author Topic: Magic & Mayhem Engine Open Source Clone  (Read 22271 times)

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Magic & Mayhem Engine Open Source Clone
« on: August 30, 2015, 04:07:37 pm »
Hi, folks!

Besides X-COM, there is another game using very similar and powerful isometric engine (among the most advanced to my knowledge) by the same designer (Julian Gollop). The game is Magic & Mayhem, based of Lords of Chaos - an old Amiga game.

The game has a lot of issues, due to the publisher interfering with developers and enforcing the usual christmas deadline (November release date speaks for itself). Running the game modern computer is pain too (although I've succeeded running it on OSX with PlayOnMac, after disabling opengl). Maybe now is the time to fix it and bring back to life as an open source project?

Most game data resides as open text in *.cfg files, but a some things are hidden under binary and few *.cfgs are encrypted, which is solved with waiting for the engine to decrypt them and then just dumping them from memory.

Tinkered a little with Magic & Mayhem engine (without using any debugger) gets some knowledge on how to decode binary data (I've successfully decoded all sprites), but unfortunately not the most interesting thing, which is the map encryption algorithm, required to get factual knowledge of how maps are stored and rendered.

Algorithm decoding CFG files would be nice too, because distributing decoded files would be copyright infringement.

Does anybody know how to decode *.map files?

I'll share with you my Magic & Mayhem engine facts and guesses:
- All map tiles are 64x48 (with light source coming from the southwest), where 16 pixels are used for height. There are no wall or floor tiles, like in X-Com style engines.
- Map is drawn in layers, using either priority queue or zbuffer.
- Water is procedurally generated between layers, as specified by `WaterLevel` variable in level files.
- Creatures have width/height size (TileSizeXY) and TileHeight, which determine their blocking profile and when they drown (water level gets past their height).
- Creatures move on grid lattice (directly using mapping to 64x32 tiles tops), like in older RTS games (Warcraft and Starcraft). Creatures can move only 8 directions, which is especially noticeable with flyers, where bat flies in zig-zag.
- Non-creature game objects (like trees and doors) are made completely out of tiles. Quote from doors.cfg "Terrain tiles need to be assigned a door number, the tile numbers that make up the door are listed [...] along with the animations for the door opening and closing"
- Game maps files (*.map) inside ./Realms appear to be compressed using the same algorithm as the txt files inside of ./CFG/Encrypted folder. The *.nod files are not compressed, but I guess they hold something like precomputed movement maps for AI.
- Due to the rhomboidal minimap shape, the map format appears to be rhomboidal too. So the Y-axis is diagonal to the viewport (like in X-com), as opposed to the vertical successive Y's, in games like Civilization II. The map is also toroidal, meaning that its west edge transitions into it's east edge, Pacman-style ( https://en.wikipedia.org/wiki/Wraparound_(video_games) )
- The map drawing algorithm can be copied from: https://sourceforge.net/p/pentagram/code/HEAD/tree/pentagram/trunk/world/ItemSorter.cpp which should give correct occlusion for a world made of arbitrary bounding boxes. I.e. projectiles and spells like tornado will render correctly.
- the lightmaps are implemented by simply altering the tile and creature brightness. Fog of war appears to be more complex.
- The sprite format used for creatures and tilesets is described in https://github.com/saniv/sau/blob/master/src/fmt_spr.c
- "It was designed first as a multiplayer game and we sort of retrofitted the single-player experience, so it didn’t work so well unfortunately, for me." -- Julian Gollop
« Last Edit: September 20, 2015, 06:07:47 am by Nikita_Sadkov »

Offline robin

  • Commander
  • *****
  • Posts: 1224
  • ULTIMATE ROOKIE
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #1 on: September 07, 2015, 09:45:18 am »
I loved the game. A open source port would be sweet, though it wasn't anywhere near as popular as UFO.

Offline Bloax

  • Colonel
  • ****
  • Posts: 322
  • do you want to be any of those things
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #2 on: September 08, 2015, 12:43:56 pm »
Oh man, a proper version of this game would be amazing.

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #3 on: September 08, 2015, 12:52:59 pm »
I loved the game. A open source port would be sweet, though it wasn't anywhere near as popular as UFO.

I'm currently working on my own game for Steam: https://steamcommunity.com/sharedfiles/filedetails/?id=514233906

Most of the assets ain't free, because I paid a lot for the art, but the engine is open source and very similar to Magic & Mayhem. While my game is turn-based, the engine can easily be adapted for real-time games.

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #4 on: September 20, 2015, 06:02:25 am »
The following ordering algorithm solves the problem with z-fighting tiles:
https://sourceforge.net/p/pentagram/code/HEAD/tree/pentagram/trunk/world/ItemSorter.cpp

Dunno why it is so obscure, because a lot of isometric games use more naive algorithms getting various occlusion problems.

Still it is computationally intensive (cant immediately employ zbuffer) and requires knowing bounding boxes, but in case of Magic & Mayhem that is not a problem.

I would love input from openxcom devs on what algorithm they use to get draw order correctly. Is it possible to use zbuffer to solve the problem?

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #5 on: May 28, 2016, 04:55:50 pm »
Okay. I have reversed the encryption scheme. Here is the decoder:
https://github.com/saniv/sau/blob/master/unsorted/scraps/mmdecrypt.c

*.cfg are also compressed in addition to being encrypted.

*.map are encrypted too, but don't appear to be compressed.

My guess is that they did that to prevent people looking up game mechanics without paying for official strategy guide. Still, enctypting map files?  :o

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #6 on: May 29, 2016, 12:36:47 pm »
The decompressor is ready and added to https://github.com/saniv/sau/blob/master/unsorted/scraps/mmdecrypt.c

*.map files were compressed too, although LZ77 algorithm isnt as good as ZIP, which made me think that they aint compressed, because ZIP was able to future compress them. Magic & Mayhem engine also supports RLE and uncompressed files in place of LZ77, which should be great, because no need to write LZ77 compressor to mess with maps and savegames.

So now we have known:
1. graphics format.
2. *.cfg/*.map/savegame compression format.
3. most of game configuration is plain text

still unknown are:
1. anim file formats.
2. map format.
3. various nuances of drawing nicelsy shaded isometric view with these sprites.

Offline MMFan

  • Squaddie
  • *
  • Posts: 6
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #7 on: March 30, 2020, 08:54:34 pm »
The decompressor is ready and added to [GitHub link]

*.map files were compressed too, although LZ77 algorithm isnt as good as ZIP, which made me think that they aint compressed, because ZIP was able to future compress them. Magic & Mayhem engine also supports RLE and uncompressed files in place of LZ77, which should be great, because no need to write LZ77 compressor to mess with maps and savegames.

So now we have known:
1. graphics format.
2. *.cfg/*.map/savegame compression format.
3. most of game configuration is plain text

still unknown are:
1. anim file formats.
2. map format.
3. various nuances of drawing nicelsy shaded isometric view with these sprites.

Just came across this thread after searching online and am gutted to see these GitHub links are no longer available. Would you kindly reconsider uploading the source?

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #8 on: March 31, 2020, 05:46:41 pm »
Just came across this thread after searching online and am gutted to see these GitHub links are no longer available. Would you kindly reconsider uploading the source?
Yeah. The github got banned to due to the one repository having a WIP version of a book touching US immigration policy. Apparently a lot of people don't support open borders and they mass reported me. GitHub then just banned me outright, instead of that single repository, to avoid any controversy.

Anyway, here is the code for LZ77 unpacker and the sprite format handling routines.

Code: [Select]
// Magic & Mayhem decryption and unpacking routines
// Author: Nikita Sadkov
// License: BSD
// Decscription:
//   Routines used to unpack *.cfg and *.map files.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define HIDWORD(x)  (*((uint32_t*)&(x)+1))

#define COMP_PLAIN  0
#define COMP_RLE    1
#define COMP_LZ77   2

#define HEADER_SIZE 20

typedef struct {
  uint32_t Seed; //seed for the PRNG used in decryption
  uint32_t UnpackedSize; //size of data[] after decompression
  uint32_t Checksum1; //checksum of decrypted data[]
  uint32_t Checksum2; //checksum of uncompressed data[]
  uint32_t Compression; //0=plain-text, 1=RLE, 2=LZ77
} __attribute__ ((packed)) header;

int filesize(char *filename) {
  int Size;
  FILE *F = fopen(filename, "rb");
  if (!F) return -1;
 
  fseek(F, 0L, SEEK_END);
  Size = ftell(F);
  fseek(F, 0L, SEEK_SET);
  fclose(F);

  return Size;
}

uint32_t PRNG_State;
uint32_t PRNG_Map[256];
int PRNG_MapReady = 0;

void init_prng_map() {
  int i;
  for (i = 0; i < 0xF9; i++) PRNG_Map[i] = i+1;
  PRNG_Map[0xF9] = 0;
}

uint32_t prng(uint32_t *table) {
  uint32_t a, b, c;
  a = table[0];
  table[0] = PRNG_Map[a];
  b = table[1];
  table[1] = PRNG_Map[b];
  c = table[b + 2] ^ table[a + 2];
  table[a + 2] = c;
  return c;
}

#define _WORD uint16_t


#define LZDictSize 4096
uint8_t LZDict[LZDictSize];

typedef struct {
  uint8_t *Ptr;
  uint8_t BitPtr;
  uint32_t Value;
} __attribute__ ((packed)) lz_input;

void lz_unpack(uint8_t *Input, uint8_t *Output, int UnpackedSize) {
  lz_input LZInput;
  lz_input *LZ;
  int Count;
  uint8_t Bit;
  uint8_t *PtrInc;
  int ValueBit;
  char NextBit;
  char Value;
  char NextBit2;
  uint32_t BackRefBit;
  int BackRefOff;
  int BackRefI;
  int BackRefLen;
  char NextBit3;
  uint32_t LowBit;
  uint32_t HighBit;
  char NextBit4;
  int Value2;
  int DictIndex;
  int CountSave;

  memset(LZDict, 0, LZDictSize);

  LZ = &LZInput;

  LZ->Ptr = Input;
  LZ->BitPtr = 0x80;
  LZ->Value = 0;

  Count = 0;
  CountSave = 0;
  DictIndex = 1;
  while ( 1 ) {
    Bit = LZ->BitPtr;
    if ( Bit == 0x80 )
    {
      PtrInc = LZ->Ptr + 1;
      LZ->Value = *LZ->Ptr;
      LZ->Ptr = PtrInc;
    }
    ValueBit = LZ->Value & Bit;
    NextBit = Bit >> 1;
    LZ->BitPtr = NextBit;
    if ( !NextBit )
      LZ->BitPtr = 0x80;
    if ( ValueBit )
    {
      HighBit = 0x80;
      Value = 0;
      do
      {
        Bit = LZ->BitPtr;
        if ( Bit == 0x80 )
        {
          PtrInc = LZ->Ptr + 1;
          LZ->Value = *LZ->Ptr;
          LZ->Ptr = PtrInc;
        }
        if ( Bit & LZ->Value )
          Value |= HighBit;
        HighBit >>= 1;
        NextBit2 = Bit >> 1;
        LZ->BitPtr = NextBit2;
        if ( !NextBit2 )
          LZ->BitPtr = 0x80;
      }
      while ( HighBit );
      *Output++ = Value;
      ++Count;
      LZDict[DictIndex] = Value;
      CountSave = Count;
      DictIndex = ((uint16_t)DictIndex + 1) & 0xFFF;
      goto loop_continue;
    }
    BackRefBit = 0x800;
    BackRefOff = 0;
    do
    {
      Bit = LZ->BitPtr;
      if ( Bit == 0x80 )
      {
        PtrInc = LZ->Ptr + 1;
        LZ->Value = *LZ->Ptr;
        LZ->Ptr = PtrInc;
      }
      if ( Bit & LZ->Value )
        BackRefOff |= BackRefBit;
      BackRefBit >>= 1;
      NextBit3 = Bit >> 1;
      LZ->BitPtr = NextBit3;
      if ( !NextBit3 )
        LZ->BitPtr = 0x80;
    } while ( BackRefBit );
    if ( !BackRefOff )
      return;
    LowBit = 8;
    BackRefLen = 0;
    do
    {
      Bit = LZ->BitPtr;
      if ( Bit == 0x80 )
      {
        PtrInc = LZ->Ptr + 1;
        LZ->Value = *LZ->Ptr;
        Count = CountSave;
        LZ->Ptr = PtrInc;
      }
      if ( Bit & LZ->Value )
        BackRefLen |= LowBit;
      LowBit >>= 1;
      NextBit4 = Bit >> 1;
      LZ->BitPtr = NextBit4;
      if ( !NextBit4 )
        LZ->BitPtr = 0x80;
    }
    while ( LowBit );
    BackRefI = 0;
    if ( BackRefLen + 1 >= 0 ) {
      do
      {
        Value2 = LZDict[((uint16_t)BackRefOff + (uint16_t)BackRefI) & 0xFFF];
        *Output++ = Value2;
        ++Count;
        CountSave = Count;
        if ( Count == UnpackedSize ) return;
        LZDict[DictIndex] = Value2;
        ++BackRefI;
        DictIndex = ((uint16_t)DictIndex + 1) & 0xFFF;
      } while (BackRefI < BackRefLen+2);
    }
loop_continue:
    if ( Count == UnpackedSize )
      return;
  }
}

void prng_init(uint32_t *table, uint32_t seed) {
  uint32_t *p;
  int i, count;
  int64_t t;
  uint32_t a;
  uint32_t k;
  int32_t *q;
  int32_t b;

  if (!PRNG_MapReady) {
    for (i = 0; i < 0xF9; i++) PRNG_Map[i] = i+1;
    PRNG_Map[0xF9] = 0;
  }

  PRNG_State = seed;
  table[0] = 0;
  table[1] = 103;
  p = table + 251;
  count = 250;
  for (i=0; i < 250; i++)
  {
    t = 0x41C64E6DULL * PRNG_State;
    HIDWORD(t) <<= 16;
    t += 0xFFFF00003039ULL;
    PRNG_State = t;
    *p = HIDWORD(t) & 0xFFFF0000 | ((uint32_t)t >> 16);
    --p;
  }
  a = 0xFFFFFFFF;
  k = 0x80000000;
  q = table + 5;
  do {
    b = *q;
    *q = k | a & b;
    q += 7;
    k >>= 1;
    a >>= 1;
  } while ( k );
}

// it seeds random number generator with a key
// then it uses generated random numbers to XOR the input
void decrypt(void *Input, int Size) {
  int I, Len;
  uint32_t Table[256];
  uint32_t *P = (uint32_t*)Input;
  memset(Table, 0, 256*sizeof(uint32_t));

  prng_init(Table, 1234567890u);
  Table[254] = 0;

  prng_init(Table, *P++);

  Len = (Size-4)/4;
  for (I=0; I<Len; I++) *P++ ^= prng(Table);

  Len = (Size-4) % 4;
  for (I=0; I<Len; I++) *((uint8_t*)P) ^= prng(Table);
}

uint32_t checksum(void *Data, int Size) {
  int I;
  uint32_t Sum=0, Elem, *P=(uint32_t*)Data;
  Size /= 4;
  for (I = 0; I < Size; I++) {
    Elem = *P++;
    if (I&1) Sum += Elem;
    else Sum ^= Elem;
  }
  return Sum;
}

int main(int argc, char **argv) {
  FILE *InFile, *OutFile;
  uint32_t *Input;
  uint8_t *Output;
  int InSize, OutSize, UnpackedSize;
  header *H;

  if (argc!=3) {
    printf("Usage: mmdecrypt <input> <output>\n");
    return 0;
  }

  InSize = filesize(argv[1]);

  if (InSize <= 20) { //cant be smaller or equal to the header
    printf("Bad file: %s\n", argv[1]);
    return 0;
  }

  InFile = fopen(argv[1], "rb");
  OutFile = fopen(argv[2], "wb");

  Input = (uint32_t*)malloc(InSize*2);
  fread(Input, 1, InSize, InFile);

  init_prng_map();
  decrypt(Input, InSize);

  H = (header*)Input;
  printf("unpacked size: %d\n", H->UnpackedSize);

  if (H->Checksum1 == checksum(H+1, InSize-sizeof(*H))) {
    UnpackedSize = H->UnpackedSize;
  } else {
    UnpackedSize = InSize;
    printf("bad checksum for decrypted data.\n");
  }

  Output = (uint8_t*)malloc(UnpackedSize*2);

  if (COMP_PLAIN == H->Compression) {
    printf("compression: uncompressed\n");
    memcpy(Output, (uint8_t*)(H+1), UnpackedSize);
  } else if (COMP_RLE == H->Compression) {
    printf("compression: RLE\n");
  } else if (COMP_LZ77 == H->Compression) {
    printf("compression: LZ77\n");
    lz_unpack((uint8_t*)(H+1), Output, UnpackedSize);
  } else {
    printf("compression: unknown (%x); aborting\n", H->Compression);
    return 0;
  }

  if (H->Checksum2 != checksum(Output, UnpackedSize)) {
    printf("bad checksum for decompressed data.\n");
  }

  fwrite(Output, 1, UnpackedSize, OutFile);

  fclose(InFile);
  fclose(OutFile);

  printf("done!\n");

  return 0;
}

Code: [Select]
//Credits go to SNV

#include "common.h"

typedef struct { // Total: 24 bytes
  u1 Id[4];     // SPR\0
  s4 Size;      // size of the whole file
  u4 U1;  // 0x4 for all creatures and tilesets
          // 0x2 for interf64.spr LOGO.spr magico64.spr magico80.spr
          //         overlay.spr scanner.spr Scrollbutt.spr
  u4 Frames;  // number of Frames
  u4 NPals;   // number of palettes
  u4 U2;  // 0 for battlebuttons.spr border.spr Buttons.spr
          //       CheckBox.spr interf64.spr magico80.spr markers.spr
          //       overlay.spr radiobuttons.spr scrollbar.spr selbut.spr
          //       slider.spr SplIcons.spr spotIcons.spr timer.spr
          // LOGO.spr, scanner.spr, Scrollbutt.spr have it 0xbf000000
          // magico64.spr has it 0xe3000000

  // u1 Palette[256*3*NPals];
  // u4 FrameOffsets[Frames];

} __attribute__ ((packed)) header;


typedef struct {
  u4 Deltas; // offset into Deltas
  u4 Pixels; // offset int Pixels
} __attribute__ ((packed)) row;


typedef struct {
  u4 Size;
  u4 W;  // width
  u4 H;  // height
  s4 CX; // center X
  s4 CY; // center Y
  u1 Name[8]; // Name of the file, this frame belongs to.
  u4 P;  // index of palette, this sprite uses
  u4 T1; // unknown offset 1
  u4 T2; // unknown offset 2

  //line Lines[Height];
  //u4 Deltas[];
  //u1 Pixels[];

  // sometimes, (when header->U1 != 2) there are two tables at the end,
  // pointed by T1 and T2. their purpose is unknown to me and without using them
  // output image is garbled.
  // their size seems has no relation to image size, being
  // somewhere between 80-84
  //u1 Table1[];
  //u1 Table2[];
} __attribute__ ((packed)) frame;


typedef struct {
  s4 X;
  s4 Y;
  u1 Name[8]; // Frame name in associated .spr file
  u4 U1;
  u4 U2;
  s4 X2;
  u4 U3;
  s4 Y2;
  u4 U4;
  u4 U5;
} __attribute__ ((packed)) aniFrame;

typedef struct {
  u1 Id[4]; // ANI\0
  u4 Size;  // size of whole file
  u4 U1;
  u4 U2;
  u4 U3;
  u4 NAnis; // number of animations
  u1 SprName[24]; // Name of associated .spr file;
  // u4 Offsets[NAnis]; // animation offsets
} __attribute__ ((packed)) aniHeader;



static void sprDecompile(char *Output, char *Input) {
  int I, J, K, X, Y, C;
  char Tmp[1024], Name[16];
  u4 *Lens, *Offs, *CU;
  int CTOffset; // Frame table offset
  char *LastName = "NIL Name, unused name";
  pic *P, *B;
  row *R;
  int L = fileSize(Input);
  header *H = (header*)readFile(0, L, Input);

  memset(Name, 0, 16);

  if (!H || memcmp(H->Id, "SPR\0", 4)) {
    printf("Invalid SPR file: %s\n", Input);
    abort();
  }

  printf("NFrames=%d NPals=%d U1=%d U2=#%x\n"
        , H->Frames, H->NPals, H->U1, H->U2);


  CTOffset = sizeof(header) + 256*3*H->NPals;

  if (H->U1 == 2) CTOffset -= 4;

  Offs = (u4*)((u1*)H+CTOffset);
  Lens = ns(u4, H->Frames);
  times (I, H->Frames) Offs[I] += CTOffset+4*H->Frames;
  times (I, H->Frames) Lens[I] = (I==H->Frames-1?H->Size:Offs[I+1])-Offs[I];

  times (I, H->Frames) {
    frame *F = (frame*)((u1*)H + Offs[I]);

    memcpy(Name, F->Name, 8);

    printf("%d,%s: O=#%x L=#%x W=%d H=%d CX=%d CY=%d P=%d, T1=#%x T2=#%x S1=%d S2=%d\n"
       , I, Name, Offs[I], Lens[I], F->W, F->H, F->CX, F->CY, F->P
       , F->T1, F->T2, F->T2 - F->T1, Lens[I]-F->T2);

    P = picNew(F->W?F->W:1, F->H?F->H:1, 8);

    if (H->NPals) {
      if (F->P >= H->NPals) F->P=0;
      u1 *Pal = (u1*)H+sizeof(header) + F->P*256*3;
      times (J, 256) {
        P->P[J*4+0] = Pal[J*3+0];
        P->P[J*4+1] = Pal[J*3+1];
        P->P[J*4+2] = Pal[J*3+2];
        P->P[J*4+3] = 0;
      }
    } else {
      times (J, 256) {
        P->P[J*4+0] = J;
        P->P[J*4+1] = J;
        P->P[J*4+2] = J;
        P->P[J*4+3] = 0;
      }
    }

    CU = ns(u4,256);

    P->K = 0x253;

    if (H->U1 == 2) L--;
    R = (row*)(F+1);

    for(Y=0; Y < F->H; Y++) {
      u1 *Deltas = (u1*)F + R[Y].Deltas;
      u1 *Pixels = (u1*)F + R[Y].Pixels;
      for(X=0, K=0; X < F->W; K=~K) {
        C = *Deltas++;
        unless (K) {X += C; continue;}
        while (C-- && X < F->W) {X++; CU[*Pixels++]++;}
      }
    }

    times (J, 256) if (!CU[J]) P->K = J;
    picClear(P, P->K);

    for(Y=0; Y < F->H; Y++) {
      u1 *Deltas = (u1*)F + R[Y].Deltas;
      u1 *Pixels = (u1*)F + R[Y].Pixels;
      for(X=0, K=0; X < F->W; K=~K) {
        C = *Deltas++;
        unless (K) {X += C; continue;}
        while (C-- && X < F->W) picPut(P,  X++, Y, *Pixels++);
      }
    }

    sprintf(Tmp, "%s/%s_%04d.png", Output, Name, I);
    pngSave(Tmp, P);
  }
}

int sprInit(format *F) {
  F->Type = FMT_ARCHIVE;
  F->Name = "spr";
  F->Description = "Magic & Mayhem sprites";
  F->Decompile = sprDecompile;
  return 1;
}

« Last Edit: March 31, 2020, 05:50:58 pm by Nikita_Sadkov »

Offline MMFan

  • Squaddie
  • *
  • Posts: 6
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #9 on: March 31, 2020, 07:43:49 pm »
Yeah. The github got banned to due to the one repository having a WIP version of a book touching US immigration policy. Apparently a lot of people don't support open borders and they mass reported me. GitHub then just banned me outright, instead of that single repository, to avoid any controversy.

No way; that's ridiculous… sorry to hear that. I did wonder if it had been banned due to some kind of copyright issue but that's just depressing. I was going to ask about re-uploading it to my own profile but I understand if you would prefer I didn't.

Anyway, thank you very much for sharing your work. After being put on lockdown I re-installed Magic & Mayhem for old time's sake and felt like seeing if I could tinker with any of the game files. I've only recently become familiar with reading files at the byte-level as I primarily work with front-end tech, so this will be incredibly helpful to further my understanding of low-level programming.

Thanks again, and take care.

Edit: just noticed, who/what is "SNV" (mentioned in the opening comment of fmt_spr.c)?
« Last Edit: March 31, 2020, 08:02:52 pm by MMFan »

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #10 on: March 31, 2020, 08:05:29 pm »
No way; that's ridiculous… sorry to hear that. I did wonder if it had been banned due to some kind of copyright issue but that's just depressing. I was going to ask about re-uploading it to my own profile but I understand if you would prefer I didn't.

Anyway, thank you very much for sharing your work. After being put on lockdown I re-installed Magic & Mayhem for old time's sake and felt like seeing if I could tinker with any of the game files. I've only recently become familiar with reading files at the byte-level as I primarily work with front-end tech, so this will be incredibly helpful to further my understanding of low-level programming.

Thanks again, and take care.
No. I haven't got a single copyright complaint at github or youtube, despite that SAU project reverse engineering formats of several games. In my Youtube videos I also used some copyrighted videogame content - not a single strike. Yet I got countless politically related strikes for all kinds of sensitive issues, from animal cruelty to invasion of privacy, which resulted in a ban at Vimeo, for a video of PrivatBank employees refusing to speak Ukrainian, but speaking Russian. PrivatBank did everything to take that video down from everywhere and threatened me with police.

So I guess you will be totally okay with reverse engineering or modding. Just don't claim decompiled code as your own. Although I heard people were actually selling Half Life 2 and Skyrim mods, and both Valve and Bethesda allowed that. Some Japanese musicians also sell albums of video game music interpretations. Square-Enix for now closed eyes on that.
« Last Edit: March 31, 2020, 08:09:09 pm by Nikita_Sadkov »

Offline MMFan

  • Squaddie
  • *
  • Posts: 6
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #11 on: April 01, 2020, 01:37:30 am »
Wow, that sounds like a lot of hassle. Do you still share your code online anywhere (e.g. GitLab, SourceForge)?

I just noticed that fmt_spr.c requires the file "common.h" in order to be compiled. I'm new to C programming, but I'm guessing that's a custom file you made which contains helper functions (such as "times" and "picNew")? Could I trouble you once again and ask for this file?

The .cfg decrypter works perfectly, so thank you again for that.

Offline Nikita_Sadkov

  • Colonel
  • ****
  • Posts: 286
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #12 on: April 01, 2020, 01:47:50 am »
Wow, that sounds like a lot of hassle. Do you still share your code online anywhere (e.g. GitLab, SourceForge)?
In future I will setup a personal site written in my own programming language - Symta, which is especially geared towards the shared nothing architecture.

I just noticed that fmt_spr.c requires the file "common.h" in order to be compiled. I'm new to C programming, but I'm guessing that's a custom file you made which contains helper functions (such as "times" and "picNew")? Could I trouble you once again and ask for this file?

The .cfg decrypter works perfectly, so thank you again for that.
Yeah. common.h/common.c files typically have stuff used by other C files.

Code: [Select]
#ifndef COMMON_H
#define COMMON_H

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <assert.h>
#include <math.h>

typedef void u0;

typedef unsigned char u1;
typedef unsigned short u2;
typedef unsigned int u4;
typedef unsigned long long u8;

typedef signed char s1;
typedef signed short s2;
typedef signed int s4;
typedef long long s8;

typedef float f4;
typedef double f8;


static s2 s2le(u1 *P) {return (P[1]<<8) | P[0];}
static s4 s4le(u1 *P) {return (P[3]<<24) | (P[2]<<16) | (P[1]<<8) | P[0];}
static u2 u2le(u1 *P) {return (P[1]<<8) | P[0];}
static u4 u4le(u1 *P) {return (P[3]<<24) | (P[2]<<16) | (P[1]<<8) | P[0];}

static s2 s2be(u1 *P) {return (P[0]<<8) | P[1];}
static s4 s4be(u1 *P) {return (P[0]<<24) | (P[1]<<16) | (P[2]<<8) | P[3];}
static u2 u2be(u1 *P) {return (P[0]<<8) | P[1];}
static u4 u4be(u1 *P) {return (P[0]<<24) | (P[1]<<16) | (P[2]<<8) | P[3];}

static u8 _ru8_tmp;
static u4 _ru4_tmp;
static u2 _ru2_tmp;
#define ru8(X) (_ru8_tmp = *(u8*)X, X+=8, _ru8_tmp)
#define ru4(X) (_ru4_tmp = *(u4*)X, X+=4, _ru4_tmp)
#define ru2(X) (_ru2_tmp = *(u2*)X, X+=2, _ru2_tmp)

#define ru4be(X) (_ru4_tmp = u4be(X), X+=4, _ru4_tmp)
#define ru2be(X) (_ru2_tmp = u2be(X), X+=2, _ru2_tmp)

#define unless(x) if(!(x))
#define times(i,e) for(i=0; i<(e); i++)
#define n(Type) ((Type*)memset(malloc(sizeof(Type)), 0, sizeof(Type)))
#define ns(Type, Sz) ((Type*)memset(malloc(sizeof(Type)*(Sz)), 0, sizeof(Type)*(Sz)))

static void s2bePut(u1 *P, int V) {
  P[0]=(((u4)V>>8)&0xff);
  P[1]=(((u4)V)&0xff);
}

static void s4bePut(u1 *P, int V) {
  P[0]=(((u4)V>>24)&0xff);
  P[1]=(((u4)V>>16)&0xff);
  P[2]=(((u4)V>>8)&0xff);
  P[3]=(((u4)V)&0xff);
}


typedef struct {char *Root; int Size; char **Names;} fileList;

typedef struct pic pic;
struct pic {
  char *N;      // name
  int C;        // compression (0=uncompressed, 1=grayscale, 2=RLE)
  int W;        // width
  int H;        // height
  int B;        // bits per pixel
  int I;        // bytes per line
  int S;        // compressed/uncompressed size of D
  int K;        // color key (-1 means no color key)
  int BK;       // bounding key (for selection)
  int SK;       // shadow key (for shadow)
  int X;        // X displacement
  int Y;        // Y displacement
  int BoxW;     // bounding box width
  int BoxH;     // bounding box height
  int Delay;    // animation delay
  int SharedPalette;
  u1 *P;        // palette
  u1 *D;        // pixels
  pic *Proxy; // this pic acts as a proxy, for other pic.
};

typedef struct {
  int NPics;
  pic **Pics;
} fac;

typedef struct {
  char *Name;  // animation name
  int NFacs;   // number of faces
  fac *Facs;   // faces
} ani;

typedef struct {
  int ColorKey;
  u1 *Palette;
  int NAnis; // number of animations
  ani *Anis;
} spr;




// misc utils
static int max(int A, int B) {return A>B ? A : B;}
static int min(int A, int B) {return A<B ? A : B;}
void hd(u1 *P, int S); // hex dump
char *downcase(char *t);
char *upcase(char *t);

// filesystem utils
int fileExist(char *File);
int fileSize(char *File);
int folderP(char *Name);
int fileP(char *Name);
char *loadFile(char *name);
void removeFile(char *fn);
void tmpName(char *rn, char *pat);
char *shell(char *fmt, ...);
void makePath(char *Path);
void *readFile(u4 Offset, u4 Length, char *FileName);
int writeFile(u4 Offset, u4 Length, char *FileName, void *Data);
void pathParts(char *Dir, char *Name, char *Ext, char *Path);

// enumerates all files recursively, returns them in sorted order
fileList *getFileList(char *DirName);
void freeFileList(fileList *FL);

// Out image and sprite formats (should work as a common denominator)
pic *picNew(int W, int H, int BPP);
void picFree(pic *P);
pic *picDup(pic *P);
pic *picClear(pic *P, u4 V);
#define PIC_FLIP_X 0x01
#define PIC_FLIP_Y 0x02
void picBlt(pic *Destination, pic *Source, int Flags
           ,int DestinationX, int DestinationY
           ,int SourceX, int SourceY
           ,int BlitRegionWidth, int BlitRegionHeight);
spr *sprNew();
void sprDel(spr *S);
pic *picProxy(pic *Src, int X, int Y, int W, int H);
void saveFrames(char *Output, spr *S);
spr *loadFrames(char *DirName);
void picPut(pic *P, int X, int Y, u4 C);
void picPut24(pic *P, int X, int Y, u4 C);
void picPut32(pic *P, int X, int Y, u4 C);
u4 picGet(pic *P, int X, int Y);
u4 picGet24(pic *P, int X, int Y);
u4 picGet32(pic *P, int X, int Y);

#define R8G8B8(R,G,B) (((R)<<16)|((G)<<8)|(B))
#define R8G8B8A8(R,G,B,A) (((A)<<24)|((R)<<16)|((G)<<8)|(B))

#define fromR8G8B8(R,G,B,C) do { \
  u4 _fromC = (C)&0xFFFFFFFF; \
  B = (((_fromC)>> 0)&0xFF); \
  G = (((_fromC)>> 8)&0xFF); \
  R = (((_fromC)>>16)&0xFF); \
 } while (0)
#define fromR8G8B8A8(R,G,B,A,C) do { \
  u4 _fromC = (C)&0xFFFFFFFF; \
  B = (((_fromC)>> 0)&0xFF); \
  G = (((_fromC)>> 8)&0xFF); \
  R = (((_fromC)>>16)&0xFF); \
  A = (((_fromC)>>24)&0xFF); \
 } while (0)
#define fromR5G6B5(R,G,B,C) do { \
  u4 _fromC = (C)&0xFFFF; \
  B = ((((_fromC)>> 0)&0x1f)<<3)|0x7; \
  G = ((((_fromC)>> 5)&0x3f)<<2)|0x3; \
  R = ((((_fromC)>>11)&0x1f)<<3)|0x7; \
 } while (0)
#define fromR5G5B5A1(A,R,G,B,C) do { \
  u4 _fromC = (C)&0xFFFF; \
  A = (_fromC)&1; \
  B = ((((_fromC)>> 1)&0x1f)<<3)|0x7; \
  G = ((((_fromC)>> 6)&0x1f)<<3)|0x7; \
  R = ((((_fromC)>>11)&0x1f)<<3)|0x7; \
 } while (0)
#define fromA1R5G5B5(A,R,G,B,C) do { \
  u4 _fromC = (C)&0xFFFF; \
  B = ((((_fromC)>> 0)&0x1f)<<3)|0x7; \
  G = ((((_fromC)>> 5)&0x1f)<<3)|0x7; \
  R = ((((_fromC)>>10)&0x1f)<<3)|0x7; \
  A = ((_fromC)>>15)&1; \
 } while (0)
#define fromA4R4G4B4(A,R,G,B,C) do { \
  u4 _fromC = (C)&0xFFFF; \
  B = ((((_fromC)>> 0)&0xf)<<4)|0xf; \
  G = ((((_fromC)>> 4)&0xf)<<4)|0xf; \
  R = ((((_fromC)>> 8)&0xf)<<4)|0xf; \
  A = ((((_fromC)>>12)&0xf)<<4)|0xf; \
  B |= B>>4; \
  G |= G>>4; \
  R |= R>>4; \
  A |= A>>4; \
 } while (0)

void rgb_to_hsv(u1 r, u1 g, u1 b, double *h, double *s, double *v);
void hsv_to_rgb(double h, double s, double v, u1 *returncol);

void pcxSave(char *File, pic *P);
pic *pcxLoad(char *File);
void pcxSavePalette(char *File, u1 *Palette);

void gifSave(char *Output, spr *S);
spr *gifLoad(char *Input);

void tgaSave(char *File, pic *P);
pic *tgaLoad(char *File);

void lbmSave(char *File, pic *P);
pic *lbmLoad(char *File);

void bmpSave(char *File, pic *P);
pic *bmpLoad(char *File);

void pngSave(char *Output, pic *P);

spr *impLoad(char *Input);
void impSave(char *Output, spr *S);

spr *mam_sprLoad(char *Input);

void mpqDecompile(char *OutDir, char *FileName);
void mpqCompile(char *FileName, fileList *FL);
// StormLib cant handle Warcraft 2 and Diablo MPQs
void mpqDecompileOld(char *OutDir, char *FileName, char *ListFile);

// zlib utils
u1 *inflateBufIE(int OLen, int Len, u1 *In, int IgnoreError);
u1 *inflateBuf(int OLen, int Len, u1 *In);

typedef struct {
  int L;  // length
  u1 *D; // data
} bytes;
void bytesDel(bytes *B);


void wavSave(char *Output, u1 *Q, int L, int Bits, int Chns, int Freq);

#define FMT_IMAGE     0
#define FMT_SPRITE    1
#define FMT_ARCHIVE   2
#define FMT_BYTES     3 /* plain raw buffer */
#define FMT_TEXT      4 /* plain text */
#define FMT_PCM       5
#define FMT_MIDI      6
#define FMT_2D        7 /* 2d vector graphics */
#define FMT_3D        8 /* 3d vector graphics */

#define MAX_FORMATS 4096

typedef struct {
  int Type;
  char *Name;
  char *Description;
  void *Save;
  void *Load;
  void *Compile;
  void *Decompile;
} format;

extern int NFormats;
extern format Formats[MAX_FORMATS];

extern char ProgDir[];
extern char ProgName[];

#endif


Offline MMFan

  • Squaddie
  • *
  • Posts: 6
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #13 on: April 01, 2020, 07:33:54 pm »
Quote
In future I will setup a personal site written in my own programming language - Symta, which is especially geared towards the shared nothing architecture.

Cool - I searched online out of curiosity and saw this: https://github.com/Tanami/symta - is that your repo or just a coincidence?

Unfortunately when I try compiling the sprite extractor it now complains that there are undefined references (e.g. fileSize, readFile, pngSave). It could be because I'm still new to this, but is it possible there are still missing files that are required for this to work?

Offline WaldoTheRanger

  • Colonel
  • ****
  • Posts: 112
    • View Profile
Re: Magic & Mayhem Engine Open Source Clone
« Reply #14 on: April 01, 2020, 08:13:05 pm »
which resulted in a ban at Vimeo, for a video of PrivatBank employees refusing to speak Ukrainian, but speaking Russian. PrivatBank did everything to take that video down from everywhere and threatened me with police.

Ever considered uploading on bitchute or vlare.tv?
neither of them would give a crap about what a big bad bank wants, and it sounds like an important thing to have out there.