/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996 - 2001 Gary Henderson (gary.henderson@ntlworld.com) and
 *                           Jerremy Koot (jkoot@snes9x.com)
 *
 * Super FX C emulator code
 * (c) Copyright 1997 - 1999 Ivar (ivar@snes9x.com) and
 *                           Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Ivar, _Demo_ and Gary Henderson.
 * C4 asm and some C emulation code (c) Copyright 2000 zsKnight and _Demo_.
 * C4 C code (c) Copyright 2001 Gary Henderson (gary.henderson@ntlworld.com).
 *
 * (c) Copyright 2014 - 2016 Daniel De Matteis. (UNDER NO CIRCUMSTANCE 
 * WILL COMMERCIAL RIGHTS EVER BE APPROPRIATED TO ANY PARTY)
 *
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: http://www.snes9x.com
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */

#if defined(__unix) || defined(__linux) || defined(__sun) || defined(__DJGPP)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

#include "snapshot.h"
#include "memmap.h"
#include "snes9x.h"
#include "65c816.h"
#include "ppu.h"
#include "cpuexec.h"
#include "display.h"
#include "apu.h"
#include "soundux.h"
#ifdef USE_SA1
#include "sa1.h"
#endif
#include "srtc.h"
#include "sdd1.h"


// notaz: file i/o function pointers for states,
// changing funcs will allow to enable/disable gzipped saves
extern int (*statef_open)(const char* fname, const char* mode);
extern int (*statef_read)(void* p, int l);
extern int (*statef_write)(void* p, int l);
extern void (*statef_close)();

extern uint8* SRAM;

typedef struct
{
   int offset;
   int size;
   int type;
} FreezeData;

enum
{
   INT_V, uint8_ARRAY_V, uint16_ARRAY_V, uint32_ARRAY_V
};

#define Offset(field,structure) \
   ((int) (((char *) (&(((structure)NULL)->field))) - ((char *) NULL)))

#define COUNT(ARRAY) (sizeof (ARRAY) / sizeof (ARRAY[0]))

#undef OFFSET
#define OFFSET(f) Offset(f, SCPUState *)

static FreezeData SnapCPU [] =
{
   {OFFSET(Flags), 4, INT_V},
   {OFFSET(BranchSkip), 1, INT_V},
   {OFFSET(NMIActive), 1, INT_V},
   {OFFSET(IRQActive), 1, INT_V},
   {OFFSET(WaitingForInterrupt), 1, INT_V},
   {OFFSET(WhichEvent), 1, INT_V},
   {OFFSET(Cycles), 4, INT_V},
   {OFFSET(NextEvent), 4, INT_V},
   {OFFSET(V_Counter), 4, INT_V},
   {OFFSET(MemSpeed), 4, INT_V},
   {OFFSET(MemSpeedx2), 4, INT_V},
   {OFFSET(FastROMSpeed), 4, INT_V}
};

#undef OFFSET
#define OFFSET(f) Offset(f, SRegisters *)

static FreezeData SnapRegisters [] =
{
   {OFFSET(PB),  1, INT_V},
   {OFFSET(DB),  1, INT_V},
   {OFFSET(P.W), 2, INT_V},
   {OFFSET(A.W), 2, INT_V},
   {OFFSET(D.W), 2, INT_V},
   {OFFSET(S.W), 2, INT_V},
   {OFFSET(X.W), 2, INT_V},
   {OFFSET(Y.W), 2, INT_V},
   {OFFSET(PC),  2, INT_V}
};

#undef OFFSET
#define OFFSET(f) Offset(f, SPPU *)

static FreezeData SnapPPU [] =
{
   {OFFSET(BGMode), 1, INT_V},
   {OFFSET(BG3Priority), 1, INT_V},
   {OFFSET(Brightness), 1, INT_V},
   {OFFSET(VMA.High), 1, INT_V},
   {OFFSET(VMA.Increment), 1, INT_V},
   {OFFSET(VMA.Address), 2, INT_V},
   {OFFSET(VMA.Mask1), 2, INT_V},
   {OFFSET(VMA.FullGraphicCount), 2, INT_V},
   {OFFSET(VMA.Shift), 2, INT_V},
   {OFFSET(BG[0].SCBase), 2, INT_V},
   {OFFSET(BG[0].VOffset), 2, INT_V},
   {OFFSET(BG[0].HOffset), 2, INT_V},
   {OFFSET(BG[0].BGSize), 1, INT_V},
   {OFFSET(BG[0].NameBase), 2, INT_V},
   {OFFSET(BG[0].SCSize), 2, INT_V},

   {OFFSET(BG[1].SCBase), 2, INT_V},
   {OFFSET(BG[1].VOffset), 2, INT_V},
   {OFFSET(BG[1].HOffset), 2, INT_V},
   {OFFSET(BG[1].BGSize), 1, INT_V},
   {OFFSET(BG[1].NameBase), 2, INT_V},
   {OFFSET(BG[1].SCSize), 2, INT_V},

   {OFFSET(BG[2].SCBase), 2, INT_V},
   {OFFSET(BG[2].VOffset), 2, INT_V},
   {OFFSET(BG[2].HOffset), 2, INT_V},
   {OFFSET(BG[2].BGSize), 1, INT_V},
   {OFFSET(BG[2].NameBase), 2, INT_V},
   {OFFSET(BG[2].SCSize), 2, INT_V},

   {OFFSET(BG[3].SCBase), 2, INT_V},
   {OFFSET(BG[3].VOffset), 2, INT_V},
   {OFFSET(BG[3].HOffset), 2, INT_V},
   {OFFSET(BG[3].BGSize), 1, INT_V},
   {OFFSET(BG[3].NameBase), 2, INT_V},
   {OFFSET(BG[3].SCSize), 2, INT_V},

   {OFFSET(CGFLIP), 1, INT_V},
   {OFFSET(CGDATA), 256, uint16_ARRAY_V},
   {OFFSET(FirstSprite), 1, INT_V},
#define O(N) \
    {OFFSET (OBJ[N].HPos), 2, INT_V}, \
    {OFFSET (OBJ[N].VPos), 2, INT_V}, \
    {OFFSET (OBJ[N].Name), 2, INT_V}, \
    {OFFSET (OBJ[N].VFlip), 1, INT_V}, \
    {OFFSET (OBJ[N].HFlip), 1, INT_V}, \
    {OFFSET (OBJ[N].Priority), 1, INT_V}, \
    {OFFSET (OBJ[N].Palette), 1, INT_V}, \
    {OFFSET (OBJ[N].Size), 1, INT_V}

   O(0), O(1), O(2), O(3), O(4), O(5), O(6), O(7),
   O(8), O(9), O(10), O(11), O(12), O(13), O(14), O(15),
   O(16), O(17), O(18), O(19), O(20), O(21), O(22), O(23),
   O(24), O(25), O(26), O(27), O(28), O(29), O(30), O(31),
   O(32), O(33), O(34), O(35), O(36), O(37), O(38), O(39),
   O(40), O(41), O(42), O(43), O(44), O(45), O(46), O(47),
   O(48), O(49), O(50), O(51), O(52), O(53), O(54), O(55),
   O(56), O(57), O(58), O(59), O(60), O(61), O(62), O(63),
   O(64), O(65), O(66), O(67), O(68), O(69), O(70), O(71),
   O(72), O(73), O(74), O(75), O(76), O(77), O(78), O(79),
   O(80), O(81), O(82), O(83), O(84), O(85), O(86), O(87),
   O(88), O(89), O(90), O(91), O(92), O(93), O(94), O(95),
   O(96), O(97), O(98), O(99), O(100), O(101), O(102), O(103),
   O(104), O(105), O(106), O(107), O(108), O(109), O(110), O(111),
   O(112), O(113), O(114), O(115), O(116), O(117), O(118), O(119),
   O(120), O(121), O(122), O(123), O(124), O(125), O(126), O(127),
#undef O
   {OFFSET(OAMPriorityRotation), 1, INT_V},
   {OFFSET(OAMAddr), 2, INT_V},
   {OFFSET(OAMFlip), 1, INT_V},
   {OFFSET(OAMTileAddress), 2, INT_V},
   {OFFSET(IRQVBeamPos), 2, INT_V},
   {OFFSET(IRQHBeamPos), 2, INT_V},
   {OFFSET(VBeamPosLatched), 2, INT_V},
   {OFFSET(HBeamPosLatched), 2, INT_V},
   {OFFSET(HBeamFlip), 1, INT_V},
   {OFFSET(VBeamFlip), 1, INT_V},
   {OFFSET(HVBeamCounterLatched), 1, INT_V},
   {OFFSET(MatrixA), 2, INT_V},
   {OFFSET(MatrixB), 2, INT_V},
   {OFFSET(MatrixC), 2, INT_V},
   {OFFSET(MatrixD), 2, INT_V},
   {OFFSET(CentreX), 2, INT_V},
   {OFFSET(CentreY), 2, INT_V},
   {OFFSET(Joypad1ButtonReadPos), 1, INT_V},
   {OFFSET(Joypad2ButtonReadPos), 1, INT_V},
   {OFFSET(Joypad3ButtonReadPos), 1, INT_V},
   {OFFSET(CGADD), 1, INT_V},
   {OFFSET(FixedColourRed), 1, INT_V},
   {OFFSET(FixedColourGreen), 1, INT_V},
   {OFFSET(FixedColourBlue), 1, INT_V},
   {OFFSET(SavedOAMAddr), 2, INT_V},
   {OFFSET(ScreenHeight), 2, INT_V},
   {OFFSET(WRAM), 4, INT_V},
   {OFFSET(ForcedBlanking), 1, INT_V},
   {OFFSET(OBJNameSelect), 2, INT_V},
   {OFFSET(OBJSizeSelect), 1, INT_V},
   {OFFSET(OBJNameBase), 2, INT_V},
   {OFFSET(OAMReadFlip), 1, INT_V},
   {OFFSET(VTimerEnabled), 1, INT_V},
   {OFFSET(HTimerEnabled), 1, INT_V},
   {OFFSET(HTimerPosition), 2, INT_V},
   {OFFSET(Mosaic), 1, INT_V},
   {OFFSET(Mode7HFlip), 1, INT_V},
   {OFFSET(Mode7VFlip), 1, INT_V},
   {OFFSET(Mode7Repeat), 1, INT_V},
   {OFFSET(Window1Left), 1, INT_V},
   {OFFSET(Window1Right), 1, INT_V},
   {OFFSET(Window2Left), 1, INT_V},
   {OFFSET(Window2Right), 1, INT_V},
#define O(N) \
    {OFFSET (ClipWindowOverlapLogic[N]), 1, INT_V}, \
    {OFFSET (ClipWindow1Enable[N]), 1, INT_V}, \
    {OFFSET (ClipWindow2Enable[N]), 1, INT_V}, \
    {OFFSET (ClipWindow1Inside[N]), 1, INT_V}, \
    {OFFSET (ClipWindow2Inside[N]), 1, INT_V}

   O(0), O(1), O(2), O(3), O(4), O(5),

#undef O

   {OFFSET(CGFLIPRead), 1, INT_V},
   {OFFSET(Need16x8Mulitply), 1, INT_V},
   {OFFSET(BGMosaic), 4, uint8_ARRAY_V},
   {OFFSET(OAMData), 512 + 32, uint8_ARRAY_V},
   {OFFSET(Need16x8Mulitply), 1, INT_V},
   {OFFSET(MouseSpeed), 2, uint8_ARRAY_V}
};

#undef OFFSET
#define OFFSET(f) Offset(f, SDMA *)

static FreezeData SnapDMA [] =
{
#define O(N) \
    {OFFSET (TransferDirection) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (AAddressFixed) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (AAddressDecrement) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (TransferMode) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (ABank) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (AAddress) + N * sizeof ( SDMA), 2, INT_V}, \
    {OFFSET (Address) + N * sizeof ( SDMA), 2, INT_V}, \
    {OFFSET (BAddress) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (TransferBytes) + N * sizeof ( SDMA), 2, INT_V}, \
    {OFFSET (HDMAIndirectAddressing) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (IndirectAddress) + N * sizeof ( SDMA), 2, INT_V}, \
    {OFFSET (IndirectBank) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (Repeat) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (LineCount) + N * sizeof ( SDMA), 1, INT_V}, \
    {OFFSET (FirstLine) + N * sizeof ( SDMA), 1, INT_V}

   O(0), O(1), O(2), O(3), O(4), O(5), O(6), O(7)
#undef O
};

#undef OFFSET
#define OFFSET(f) Offset(f, SAPU *)

static FreezeData SnapAPU [] =
{
   {OFFSET(Cycles), 4, INT_V},
   {OFFSET(ShowROM), 1, INT_V},
   {OFFSET(Flags), 1, INT_V},
   {OFFSET(KeyedChannels), 1, INT_V},
   {OFFSET(OutPorts), 4, uint8_ARRAY_V},
   {OFFSET(DSP), 0x80, uint8_ARRAY_V},
   {OFFSET(ExtraRAM), 64, uint8_ARRAY_V},
   {OFFSET(Timer), 3, uint16_ARRAY_V},
   {OFFSET(TimerTarget), 3, uint16_ARRAY_V},
   {OFFSET(TimerEnabled), 3, uint8_ARRAY_V},
   {OFFSET(TimerValueWritten), 3, uint8_ARRAY_V}
};

#undef OFFSET
#define OFFSET(f) Offset(f, SAPURegisters *)

static FreezeData SnapAPURegisters [] =
{
   {OFFSET(P)   , 1, INT_V},
   {OFFSET(YA.W), 2, INT_V},
   {OFFSET(X)   , 1, INT_V},
   {OFFSET(S)   , 1, INT_V},
   {OFFSET(PC)  , 2, INT_V}
};

#undef OFFSET
#undef OFFSET1
#define OFFSET(f) Offset(f,SSoundData *)

static FreezeData SnapSoundData [] =
{
   {OFFSET(master_volume_left), 2, INT_V},
   {OFFSET(master_volume_right), 2, INT_V},
   {OFFSET(echo_volume_left), 2, INT_V},
   {OFFSET(echo_volume_right), 2, INT_V},
   {OFFSET(echo_enable), 4, INT_V},
   {OFFSET(echo_feedback), 4, INT_V},
   {OFFSET(echo_ptr), 4, INT_V},
   {OFFSET(echo_buffer_size), 4, INT_V},
   {OFFSET(echo_write_enabled), 4, INT_V},
   {OFFSET(echo_channel_enable), 4, INT_V},
   {OFFSET(pitch_mod), 4, INT_V},
   {OFFSET(dummy), 3, uint32_ARRAY_V},
#define O(N) \
    {OFFSET (channels [N].state), 4, INT_V}, \
    {OFFSET (channels [N].type), 4, INT_V}, \
    {OFFSET (channels [N].volume_left), 2, INT_V}, \
    {OFFSET (channels [N].volume_right), 2, INT_V}, \
    {OFFSET (channels [N].hertz), 4, INT_V}, \
    {OFFSET (channels [N].count), 4, INT_V}, \
    {OFFSET (channels [N].loop), 1, INT_V}, \
    {OFFSET (channels [N].envx), 4, INT_V}, \
    {OFFSET (channels [N].left_vol_level), 2, INT_V}, \
    {OFFSET (channels [N].right_vol_level), 2, INT_V}, \
    {OFFSET (channels [N].envx_target), 2, INT_V}, \
    {OFFSET (channels [N].env_error), 4, INT_V}, \
    {OFFSET (channels [N].erate), 4, INT_V}, \
    {OFFSET (channels [N].direction), 4, INT_V}, \
    {OFFSET (channels [N].attack_rate), 4, INT_V}, \
    {OFFSET (channels [N].decay_rate), 4, INT_V}, \
    {OFFSET (channels [N].sustain_rate), 4, INT_V}, \
    {OFFSET (channels [N].release_rate), 4, INT_V}, \
    {OFFSET (channels [N].sustain_level), 4, INT_V}, \
    {OFFSET (channels [N].sample), 2, INT_V}, \
    {OFFSET (channels [N].decoded), 16, uint16_ARRAY_V}, \
    {OFFSET (channels [N].previous16), 2, uint16_ARRAY_V}, \
    {OFFSET (channels [N].sample_number), 2, INT_V}, \
    {OFFSET (channels [N].last_block), 1, INT_V}, \
    {OFFSET (channels [N].needs_decode), 1, INT_V}, \
    {OFFSET (channels [N].block_pointer), 4, INT_V}, \
    {OFFSET (channels [N].sample_pointer), 4, INT_V}, \
    {OFFSET (channels [N].mode), 4, INT_V}

   O(0), O(1), O(2), O(3), O(4), O(5), O(6), O(7)
#undef O
};

#ifdef USE_SA1

#undef OFFSET
#define OFFSET(f) Offset(f, SSA1Registers *)

static FreezeData SnapSA1Registers [] =
{
   {OFFSET(PB),  1, INT_V},
   {OFFSET(DB),  1, INT_V},
   {OFFSET(P.W), 2, INT_V},
   {OFFSET(A.W), 2, INT_V},
   {OFFSET(D.W), 2, INT_V},
   {OFFSET(S.W), 2, INT_V},
   {OFFSET(X.W), 2, INT_V},
   {OFFSET(Y.W), 2, INT_V},
   {OFFSET(PC),  2, INT_V}
};

#undef OFFSET
#define OFFSET(f) Offset(f, SSA1 *)

static FreezeData SnapSA1 [] =
{
   {OFFSET(Flags), 4, INT_V},
   {OFFSET(NMIActive), 1, INT_V},
   {OFFSET(IRQActive), 1, INT_V},
   {OFFSET(WaitingForInterrupt), 1, INT_V},
   {OFFSET(op1), 2, INT_V},
   {OFFSET(op2), 2, INT_V},
   {OFFSET(arithmetic_op), 4, INT_V},
   {OFFSET(sum), 8, INT_V},
   {OFFSET(overflow), 1, INT_V}
};
#endif

//static char ROMFilename [_MAX_PATH];
//static char SnapshotFilename [_MAX_PATH];

static void Freeze();
static int Unfreeze();
void FreezeStruct(char* name, void* base, FreezeData* fields,
                  int num_fields);
void FreezeBlock(char* name, uint8* block, int size);

int UnfreezeStruct(char* name, void* base, FreezeData* fields,
                   int num_fields);
int UnfreezeBlock(char* name, uint8* block, int size);

bool8 Snapshot(const char* filename)
{
   return (S9xFreezeGame(filename));
}

bool8 S9xFreezeGame(const char* filename)
{
   if (statef_open(filename, "wb"))
   {
      Freeze();
      statef_close();
      return (TRUE);
   }
   return (FALSE);
}


bool8 S9xUnfreezeGame(const char* filename)
{
   if (statef_open(filename, "rb"))
   {
      int result;
      if ((result = Unfreeze()) != SUCCESS)
      {
         switch (result)
         {
         case WRONG_FORMAT:
            S9xMessage(S9X_ERROR, S9X_WRONG_FORMAT,
                       "File not in Snes9x freeze format");
            S9xReset();
            break;
         case WRONG_VERSION:
            S9xMessage(S9X_ERROR, S9X_WRONG_VERSION,
                       "Incompatable Snes9x freeze file format version");
            S9xReset();
            break;
         default:
            // should never happen
            break;
         }
         statef_close();
         return (FALSE);
      }
      statef_close();
      return (TRUE);
   }


   return (FALSE);
}

static void Freeze()
{
   char buffer[1024];
   int i;

   S9xSetSoundMute(TRUE);

   S9xSRTCPreSaveState();

   for (i = 0; i < 8; i++)
   {
      SoundData.channels [i].previous16 [0] = (int16) SoundData.channels [i].previous [0];
      SoundData.channels [i].previous16 [1] = (int16) SoundData.channels [i].previous [1];
   }
   sprintf(buffer, "%s:%04d\n", SNAPSHOT_MAGIC, SNAPSHOT_VERSION);
   statef_write(buffer, strlen(buffer));
   sprintf(buffer, "NAM:%06d:%s%c", strlen(Memory.ROMFilename) + 1,
           Memory.ROMFilename, 0);
   statef_write(buffer, strlen(buffer) + 1);
   FreezeStruct("CPU", &CPU, SnapCPU, COUNT(SnapCPU));
   FreezeStruct("REG", &Registers, SnapRegisters, COUNT(SnapRegisters));
   FreezeStruct("PPU", &PPU, SnapPPU, COUNT(SnapPPU));
   FreezeStruct("DMA", DMA, SnapDMA, COUNT(SnapDMA));

   // RAM and VRAM
   FreezeBlock("VRA", Memory.VRAM, 0x10000);
   FreezeBlock("RAM", Memory.RAM, 0x20000);
   FreezeBlock("SRA", SRAM, 0x20000);
   FreezeBlock("FIL", Memory.FillRAM, 0x8000);
   if (Settings.APUEnabled)
   {
      SAPURegisters spcregs;

      // APU
      FreezeStruct("APU", &APU, SnapAPU, COUNT(SnapAPU));
      // copy all SPC700 regs to savestate compatible struct
      spcregs.P  = IAPU.P;
      spcregs.YA.W = IAPU.YA.W;
      spcregs.X  = IAPU.X;
      spcregs.S  = IAPU.S;
      spcregs.PC = IAPU.PC - IAPU.RAM;
      FreezeStruct("ARE", &spcregs, SnapAPURegisters,
                   COUNT(SnapAPURegisters));

      FreezeBlock("ARA", IAPU.RAM, 0x10000);
      FreezeStruct("SOU", &SoundData, SnapSoundData,
                   COUNT(SnapSoundData));
   }
#ifdef USE_SA1
   if (Settings.SA1)
   {
      SA1Registers.PC = SA1.PC - SA1.PCBase;
      S9xSA1PackStatus();
      FreezeStruct("SA1", &SA1, SnapSA1, COUNT(SnapSA1));
      FreezeStruct("SAR", &SA1Registers, SnapSA1Registers,
                   COUNT(SnapSA1Registers));
   }
#endif
   S9xSetSoundMute(FALSE);
}

static int Unfreeze(void)
{
   uint32 old_flags;
   uint32 sa1_old_flags;
   // notaz: overflowing the damn Symbian stack again
   char buffer [16];
   char rom_filename [512];
   int result;
   int version;
   unsigned int i;

   unsigned int len = strlen(SNAPSHOT_MAGIC) + 1 + 4 + 1;
   if (statef_read(buffer, len) != (int)len)
      return (WRONG_FORMAT);
   if (strncmp(buffer, SNAPSHOT_MAGIC, strlen(SNAPSHOT_MAGIC)) != 0)
      return (WRONG_FORMAT);
   if ((version = atoi(&buffer [strlen(SNAPSHOT_MAGIC) + 1])) > SNAPSHOT_VERSION)
      return (WRONG_VERSION);

   if ((result = UnfreezeBlock("NAM", (uint8*) rom_filename, 512)) != SUCCESS)
      return (result);

   if (strcasecmp(rom_filename, Memory.ROMFilename) != 0 &&
         strcasecmp(S9xBasename(rom_filename), S9xBasename(Memory.ROMFilename)) != 0)
   {
      S9xMessage(S9X_WARNING, S9X_FREEZE_ROM_NAME,
                 "Current loaded ROM image doesn't match that required by freeze-game file.");
   }

   old_flags     = CPU.Flags;
#ifdef USE_SA1
   sa1_old_flags = SA1.Flags;
#endif
   S9xReset();
   S9xSetSoundMute(TRUE);

   if ((result = UnfreezeStruct("CPU", &CPU, SnapCPU,
                                COUNT(SnapCPU))) != SUCCESS)
      return (result);


   FixROMSpeed();
   CPU.Flags |= old_flags & (DEBUG_MODE_FLAG | TRACE_FLAG |
                             SINGLE_STEP_FLAG | FRAME_ADVANCE_FLAG);
   if ((result = UnfreezeStruct("REG", &Registers, SnapRegisters, COUNT(SnapRegisters))) != SUCCESS)
      return (result);
   if ((result = UnfreezeStruct("PPU", &PPU, SnapPPU, COUNT(SnapPPU))) != SUCCESS)
      return (result);


   IPPU.ColorsChanged = TRUE;
   IPPU.OBJChanged = TRUE;
   CPU.InDMA = FALSE;
   // Restore colors from PPU
   for (i = 0; i < 256; i++)
   {
      IPPU.Red[i] = PPU.CGDATA[i] & 0x1f;
      IPPU.Green[i] = (PPU.CGDATA[i] >> 5) & 0x1f;
      IPPU.Blue[i] = (PPU.CGDATA[i] >> 10) & 0x1f;
   }

   S9xFixColourBrightness();
   IPPU.RenderThisFrame = FALSE;

   if ((result = UnfreezeStruct("DMA", DMA, SnapDMA,
                                COUNT(SnapDMA))) != SUCCESS)
      return (result);

   if ((result = UnfreezeBlock("VRA", Memory.VRAM, 0x10000)) != SUCCESS)
      return (result);

   if ((result = UnfreezeBlock("RAM", Memory.RAM, 0x20000)) != SUCCESS)
      return (result);

   if ((result = UnfreezeBlock("SRA", SRAM, 0x20000)) != SUCCESS)
      return (result);

   if ((result = UnfreezeBlock("FIL", Memory.FillRAM, 0x8000)) != SUCCESS)
      return (result);

   // Restore graphics shadow registers
   GFX.r212c_s = Memory.FillRAM[0x212c];
   GFX.r212d_s = Memory.FillRAM[0x212d];
   GFX.r212e_s = Memory.FillRAM[0x212e];
   GFX.r212f_s = Memory.FillRAM[0x212f];
   GFX.r2130_s = Memory.FillRAM[0x2130];
   GFX.r2131_s = Memory.FillRAM[0x2131];

   if (UnfreezeStruct("APU", &APU, SnapAPU, COUNT(SnapAPU)) == SUCCESS)
   {
      int u;
      SAPURegisters spcregs;

      if ((result = UnfreezeStruct("ARE", &spcregs, SnapAPURegisters,
                                   COUNT(SnapAPURegisters))) != SUCCESS)
         return (result);
      // reload all SPC700 regs from savestate compatible struct
      IAPU.P = spcregs.P;
      IAPU.YA.W = spcregs.YA.W;
      IAPU.X = spcregs.X;
      IAPU.S = spcregs.S;
      IAPU.PC = IAPU.RAM + spcregs.PC;

      if ((result = UnfreezeBlock("ARA", IAPU.RAM, 0x10000)) != SUCCESS)
         return (result);

      if ((result = UnfreezeStruct("SOU", &SoundData, SnapSoundData,
                                   COUNT(SnapSoundData))) != SUCCESS)
         return (result);

      // notaz: just to be sure
      for (u = 0; u < 8; u++)
      {
         SoundData.channels[u].env_ind_attack &= 0xf;
         SoundData.channels[u].env_ind_decay  &= 0x7;
         SoundData.channels[u].env_ind_sustain &= 0x1f;
      }

      S9xSetSoundMute(FALSE);
      S9xAPUUnpackStatus();
      if (APUCheckDirectPage())
         IAPU.DirectPage = IAPU.RAM + 0x100;
      else
         IAPU.DirectPage = IAPU.RAM;
      Settings.APUEnabled = TRUE;
      /*IAPU.APUExecuting*/CPU.APU_APUExecuting = TRUE;
   }
   else
   {
      Settings.APUEnabled = FALSE;
      /*IAPU.APUExecuting*/CPU.APU_APUExecuting = FALSE;
      S9xSetSoundMute(TRUE);
   }
#ifdef USE_SA1
   if ((result = UnfreezeStruct("SA1", &SA1, SnapSA1,
                                COUNT(SnapSA1))) == SUCCESS)
   {
      if ((result = UnfreezeStruct("SAR", &SA1Registers,
                                   SnapSA1Registers, COUNT(SnapSA1Registers))) != SUCCESS)
         return (result);

      S9xFixSA1AfterSnapshotLoad();
      SA1.Flags |= sa1_old_flags & (TRACE_FLAG);
   }
#endif
   S9xFixSoundAfterSnapshotLoad();
   ICPU.ShiftedPB = Registers.PB << 16;
   ICPU.ShiftedDB = Registers.DB << 16;
   S9xSetPCBase(ICPU.ShiftedPB + Registers.PC);

#ifndef ASMCPU
   S9xUnpackStatus();  // not needed
   S9xFixCycles();  // also not needed?
#endif
   S9xReschedule();

   S9xSRTCPostLoadState();
   if (Settings.SDD1)  S9xSDD1PostLoadState();

   return (SUCCESS);
}

int FreezeSize(int size, int type)
{
   switch (type)
   {
   case uint16_ARRAY_V:
      return (size * 2);
   case uint32_ARRAY_V:
      return (size * 4);
   default:
      return (size);
   }
}

void FreezeStruct(char* name, void* base, FreezeData* fields,
                  int num_fields)
{
   // Work out the size of the required block
   int len = 0;
   int i;
   int j;
   uint8 *block;
   uint8 *ptr;
   uint16 word;
   uint32 dword;
   int64  qword;

   for (i = 0; i < num_fields; i++)
   {
      if (fields [i].offset + FreezeSize(fields [i].size,
                                         fields [i].type) > len)
         len = fields [i].offset + FreezeSize(fields [i].size,
                                              fields [i].type);
   }

   block = (uint8*)malloc(len);
   ptr   = block;

   // Build the block ready to be streamed out
   for (i = 0; i < num_fields; i++)
   {
      switch (fields [i].type)
      {
         case INT_V:
            switch (fields [i].size)
            {
               case 1:
                  *ptr++ = *((uint8*) base + fields [i].offset);
                  break;
               case 2:
                  word = *((uint16*)((uint8*) base + fields [i].offset));
                  *ptr++ = (uint8)(word >> 8);
                  *ptr++ = (uint8) word;
                  break;
               case 4:
                  dword = *((uint32*)((uint8*) base + fields [i].offset));
                  *ptr++ = (uint8)(dword >> 24);
                  *ptr++ = (uint8)(dword >> 16);
                  *ptr++ = (uint8)(dword >> 8);
                  *ptr++ = (uint8) dword;
                  break;
               case 8:
                  qword = *((int64*)((uint8*) base + fields [i].offset));
                  *ptr++ = (uint8)(qword >> 56);
                  *ptr++ = (uint8)(qword >> 48);
                  *ptr++ = (uint8)(qword >> 40);
                  *ptr++ = (uint8)(qword >> 32);
                  *ptr++ = (uint8)(qword >> 24);
                  *ptr++ = (uint8)(qword >> 16);
                  *ptr++ = (uint8)(qword >> 8);
                  *ptr++ = (uint8) qword;
                  break;
            }
            break;
         case uint8_ARRAY_V:
            memmove(ptr, (uint8*) base + fields [i].offset, fields [i].size);
            ptr += fields [i].size;
            break;
         case uint16_ARRAY_V:
            for (j = 0; j < fields [i].size; j++)
            {
               word = *((uint16*)((uint8*) base + fields [i].offset + j * 2));
               *ptr++ = (uint8)(word >> 8);
               *ptr++ = (uint8) word;
            }
            break;
         case uint32_ARRAY_V:
            for (j = 0; j < fields [i].size; j++)
            {
               dword = *((uint32*)((uint8*) base + fields [i].offset + j * 4));
               *ptr++ = (uint8)(dword >> 24);
               *ptr++ = (uint8)(dword >> 16);
               *ptr++ = (uint8)(dword >> 8);
               *ptr++ = (uint8) dword;
            }
            break;
      }
   }

   FreezeBlock(name, block, len);

   free(block);
}

void FreezeBlock(char* name, uint8* block, int size)
{
   char buffer [512];
   sprintf(buffer, "%s:%06d:", name, size);
   statef_write(buffer, strlen(buffer));
   statef_write(block, size);

}

int UnfreezeStruct(char* name, void* base, FreezeData* fields,
                   int num_fields)
{
   // Work out the size of the required block
   int len = 0;
   int i;
   int j;
   uint16 word;
   uint32 dword;
   int64  qword;
   int result;
   uint8 *block;
   uint8 *ptr;

   for (i = 0; i < num_fields; i++)
   {
      if (fields [i].offset + FreezeSize(fields [i].size,
                                         fields [i].type) > len)
         len = fields [i].offset + FreezeSize(fields [i].size,
                                              fields [i].type);
   }

   block = (uint8*)malloc(len);
   ptr = block;

   if ((result = UnfreezeBlock(name, block, len)) != SUCCESS)
   {
      free(block);
      return (result);
   }

   // Unpack the block of data into a C structure
   for (i = 0; i < num_fields; i++)
   {
      switch (fields [i].type)
      {
      case INT_V:
         switch (fields [i].size)
         {
         case 1:
            *((uint8*) base + fields [i].offset) = *ptr++;
            break;
         case 2:
            word  = *ptr++ << 8;
            word |= *ptr++;
            *((uint16*)((uint8*) base + fields [i].offset)) = word;
            break;
         case 4:
            dword  = *ptr++ << 24;
            dword |= *ptr++ << 16;
            dword |= *ptr++ << 8;
            dword |= *ptr++;
            *((uint32*)((uint8*) base + fields [i].offset)) = dword;
            break;
         case 8:
            qword  = (int64) * ptr++ << 56;
            qword |= (int64) * ptr++ << 48;
            qword |= (int64) * ptr++ << 40;
            qword |= (int64) * ptr++ << 32;
            qword |= (int64) * ptr++ << 24;
            qword |= (int64) * ptr++ << 16;
            qword |= (int64) * ptr++ << 8;
            qword |= (int64) * ptr++;
            *((int64*)((uint8*) base + fields [i].offset)) = qword;
            break;
         }
         break;
      case uint8_ARRAY_V:
         memmove((uint8*) base + fields [i].offset, ptr, fields [i].size);
         ptr += fields [i].size;
         break;
      case uint16_ARRAY_V:
         for (j = 0; j < fields [i].size; j++)
         {
            word  = *ptr++ << 8;
            word |= *ptr++;
            *((uint16*)((uint8*) base + fields [i].offset + j * 2)) = word;
         }
         break;
      case uint32_ARRAY_V:
         for (j = 0; j < fields [i].size; j++)
         {
            dword  = *ptr++ << 24;
            dword |= *ptr++ << 16;
            dword |= *ptr++ << 8;
            dword |= *ptr++;
            *((uint32*)((uint8*) base + fields [i].offset + j * 4)) = dword;
         }
         break;
      }
   }

   //    delete block;
   free(block);
   return (result);
}

int UnfreezeBlock(char* name, uint8* block, int size)
{
   char buffer [20];
   int len = 0;
   int rem = 0;

   if (statef_read(buffer, 11) != 11 ||
         strncmp(buffer, name, 3) != 0 || buffer [3] != ':' ||
         (len = atoi(&buffer [4])) == 0)
      return (WRONG_FORMAT);

   if (len > size)
   {
      rem = len - size;
      len = size;
   }

   if (statef_read(block, len) != len)
      return (WRONG_FORMAT);

   if (rem)
   {
      char* junk = (char*)malloc(rem);
      statef_read(junk, rem);
      free(junk);
   }

   return (SUCCESS);
}


