# Copy Protection

WARNING: This page applies only to Atari ST versions. Check the ReDMCSB Release 3 source code for other versions (search for keyword `NOCOPYPROTECTION` and keyword `CPSX`).

This document describes the software side of the copy protection. The game implements 6 copy protection schemes (named CPSA to CPSF) to make sure that the game disk is original and has not been tampered with. The copy protection schemes are spread over many functions and variables in the program to make it as hard as possible to find and crack them all.

- One scheme (A) is very simple and checks for the presence of a specific command line parameter.
- Five schemes (B, C, D, E, F) read a copy protected sector from the floppy disk and make sure it is original. There are two copy protected sectors (sectors that cannot be copied using consumer grade hardware) on the floppy disk: sectors 7 and 247 on track 0, side 0. Each scheme performs its check in a particular way:
  - Scheme B reads both sectors 10 and 247 and checks the contents of sector 247.
  - Scheme C checks fuzzy bits in sector 7 and uses several checksums (using 3 different algorithms) on the code of 2 functions to detect if they have been modified.
  - Scheme D reads sector 247 from time to time. This scheme is mixed with the code to play sounds.
  - Schemes E and F read sector 7 and check for the presence of fuzzy bits. Both use code hidden as bitmaps in `GRAPHICS.DAT`: regular functions in the game are dynamically patched to run these hidden subroutines and then the functions are restored to their original form so that the copy protection code is harder to find and neutralize. Scheme E is mixed with code to draw the dungeon view. Scheme F is mixed with various functions and triggered when the party moves to another map.

In the source code files, all code related to the copy protection is contained within conditional compilation directives using the keyword `NOCOPYPROTECTION`. If this constant is defined then the game is compiled without any copy protection.

- Reading copy protected sectors
- Copy protection scheme A
- Copy protection scheme B
- Copy protection scheme C
- Copy protection scheme D
- Copy protection scheme E
- Copy protection scheme F

## Reading copy protected sectors

Each time the game needs to read a copy protected sector, it uses one of two methods:

- Call the built-in XBIOS function `Floprd()` with appropriate parameters including which sector to read and where to store the sector contents in memory. When this function returns the requested sector is immediately available in the specified memory buffer.
- Call custom subroutines written in assembly code. These calls are asynchronous:
  1. First, a subroutine accesses the FDC (Floppy Disk Controller) chip directly to initiate the read operation and returns before the sector is actually read into the specified memory buffer. Then the main program continues running. There are two such routines in the game:
     - `S0465_CPSD_ReadSector247` which is part of CPSD and initiates a read operation of sector 247
     - A subroutine hidden in graphic #538 which is part of CPSF and initiates a read operation of sector 7
     Both of these subroutines set `G0624_B_FloppyDriveTurnedOn_CPSDF` to `C1_TRUE` and `G0625_i_FloppyDriveDMATimeout_CPSDF` to 259 which is the maximum number of Vertical Blank interrupts to wait for the completion of the read operation.
     A flag is also set in `G0314_i_SectorsReadRequested_CPSDF` to indicate which copy protection sector is being read. `S0465_CPSD_ReadSector247` sets the flag for sector 247 and `F0118_DUNGEONVIEW_DrawSquareD3C_CPSF` sets the flag for sector 7 before the hidden code in graphic #538 is executed.
     The copy protected sectors are read in `G0643_puc_FloppyDiskReadBuffer_CPSDF` (a 1024 bytes buffer) which is also used for reading data from the `GRAPHICS.DAT` file. In order to identify the content of this buffer, another flag is set in `G0644_i_FloppyDiskReadBufferContentType_CPSDF` to indicate which copy protection sector is currently stored in the buffer. `F0064_SOUND_RequestPlay_CPSD` sets the flag for sector 247 before calling `S0465_CPSD_ReadSector247` and `F0118_DUNGEONVIEW_DrawSquareD3C_CPSF` sets the flag for sector 7 before the hidden code in graphic #538 is executed.
     `F0474_MEMORY_LoadGraphic_CPSDF` clears `G0644_i_FloppyDiskReadBufferContentType_CPSDF` if it needs to read data from the floppy disk.
  2. During each Vertical Blank interrupt, `E0017_MAIN_Exception28Handler_VerticalBlank_CPSDF` is executed. If `G0625_i_FloppyDriveDMATimeout_CPSDF > 0` then the subroutine `S0080_CPSDF_CheckDMATransferCompletion` is called to monitor the FDC and check whether the read operation has completed. This happens until the FDC indicates that the read operation is complete (with success or failure) or until `G0625_i_FloppyDriveDMATimeout_CPSDF` reaches 0 (this value is decremented during each interrupt to manage the timeout). The final value of `G0625_i_FloppyDriveDMATimeout_CPSDF` can be:
     - -8 if the requested sector was not found on the floppy disk
     - -2 if the timeout expired before the read operation was completed
     - -1 if the DMA was not configured to read a sector
     - 0 if reading succeeded
     If the read operation succeeded then `G0314_i_SectorsReadRequested_CPSDF` is cleared.
  3. In `E0017_MAIN_Exception28Handler_VerticalBlank_CPSDF`, if the read operation is complete (`G0625_i_FloppyDriveDMATimeout_CPSDF <= 0`) and the floppy disk drive is still on (`G0624_B_FloppyDriveTurnedOn_CPSDF = C1_TRUE`) then the subroutine `S0081_CPSDF_TurnOffFloppyDrive` is called to turn off the floppy drive and clear `G0624_B_FloppyDriveTurnedOn_CPSDF`.
  4. In `S0016_MAIN_DrawMousePointerHiddenScreenAreaIfNecessary_CPSDF` which is called by `E0017_MAIN_Exception28Handler_VerticalBlank_CPSDF`, if the read sector operation failed (`G0625_i_FloppyDriveDMATimeout_CPSDF < 0`) then the flag set in `G0314_i_SectorsReadRequested_CPSDF` is copied to `G0315_i_SectorsReadFailure_CPSDF` and `G0314_i_SectorsReadRequested_CPSDF` and `G0625_i_FloppyDriveDMATimeout_CPSDF` are reset to 0. `G0315_i_SectorsReadFailure_CPSDF` contains the result of the copy protected sectors read operations: for each sector flag, 0 means success and 1 means failure.

Note: the calls to `S0080_CPSDF_CheckDMATransferCompletion` and `S0081_CPSDF_TurnOffFloppyDrive` in `E0017_MAIN_Exception28Handler_VerticalBlank_CPSDF` are done using their absolute addresses stored in G0626_pfv_S0080_CPSDF_CheckDMATransferCompletion`S0080_CPSDF_CheckDMATransferCompletion` and G0627_pfv_S0081_CPSDF_TurnOffFloppyDrive`S0081_CPSDF_TurnOffFloppyDrive` and obtained by calls to `F0211_CPSDF_GetSubroutineAbsoluteAddress` in either `F0448_STARTUP1_InitializeMemoryManager_CPSADEF` (in Dungeon Master) or `F0463_START_InitializeGame_CPSADEF` (in Chaos Strikes Back).

In `F0002_MAIN_GameLoop_CPSDF`, every 64 ticks, if there is a copy protection sector read failure in `G0315_i_SectorsReadFailure_CPSDF` but not in `G0316_i_SectorsReadPreviousFailure_CPSDF` (the previous read succeeded) then `F0428_DIALOG_RequireGameDiskInDrive_NoDialogDrawn` is called which will in turn calls `F0452_FLOPPY_GetDiskTypeInDrive_CPSB` and triggers CPSB and displays a dialog box with the message "THAT'S NOT THE MASTER DISK!"

There must not be any other floppy disk activity while a copy protected sector is being read with the custom subroutines (including another copy protection sector read). The game functions that need to access the floppy disk while being in game wait until the read operation is complete (until `G0625_i_FloppyDriveDMATimeout_CPSDF = 0`) before proceeding. Here is the list of these functions:

- `F0096_DUNGEONVIEW_LoadCurrentMapGraphics_CPSDF`
- `F0118_DUNGEONVIEW_DrawSquareD3C_CPSF` (before installing graphic #538)
- `F0433_STARTEND_ProcessCommand140_SaveGame_CPSCDF` (this one has its own custom builtin routines to read sector 7)
- `S0465_CPSD_ReadSector247`
- `F0474_MEMORY_LoadGraphic_CPSDF`
- `F0477_MEMORY_OpenGraphicsDat_CPSDF`
- `F0478_MEMORY_CloseGraphicsDat_CPSDF`
- `F0484_MEMORY_LoadGraphics_CPSDEF`

Some functions also wait until `G0625_i_FloppyDriveDMATimeout_CPSDF = 0` not because they need to access the floppy disk but rather in order to make sure the last copy protection read operation has completed:

- `F0106_DUNGEONVIEW_TestResetToStep1_CPSF`
- `F0120_DUNGEONVIEW_DrawSquareD2R_CPSF`

## Copy protection scheme A

Keywords in source code: CPSA, CPSADEF

CPSA checks that the main program was started with a specific command line parameter. This check is performed only once when the program starts.
When you boot the computer using the game disk, the boot sector is executed. It loads and runs `SWOOSH.IMG` which in turn runs `START.PRG` with the command line parameter `AUTO`. `START.PRG` decompresses the main program stored in `START.PAK` in memory and runs it using the same command line parameter (it is not specified again, it simply reuses the same command line in place). Note that if you run a `.PRG` file manually from the desktop, the operating system does not ask for command line parameters (like it does for `.TTP` executables).
The first function that is called by `main()` is either `F0448_STARTUP1_InitializeMemoryManager_CPSADEF` (in Dungeon Master) or `F0463_START_InitializeGame_CPSADEF` (in Chaos Strikes Back). These functions check that the first four characters of the command line used to run the program are `AUTO` and then call `F0447_STARTEND_HangIfFalse_CPSA`. If the command line is not the expected one then this function hangs the game with an infinite loop.

## Copy protection scheme B

Keywords in source code: CPSB

CPSB checks that sector 10 can be read (there are only 9 sectors on standard Atari ST floppy disks) and that the copy protection sector 247 can be read and contains two specific strings at expected locations.
This check is performed each time the game requires the game disk in the drive. If the disk is a copy and one of these sectors cannot be read then the game detects the floppy disk as a save game disk.
When the game requires the game disk in the drive, which occurs for example when you start a new game or resume a saved game in order to load graphics or the dungeon, it calls `F0452_FLOPPY_GetDiskTypeInDrive_CPSB` to determine which type of disk is currently in the drive. In order to detect the game disk, this function calls the XBIOS function `Floprd()` twice to read sector 10 and sector 247 from track 0 into a temporarily allocated memory buffer. Standard Atari ST floppies only have 9 sectors per track, numbered from 1 to 9. The original game disk has 10 sectors per track, numbered from 1 to 10 except for sector 8 which is replaced by sector 247. If both sector 7 and sector 247 could be read, the function also checks that the strings "Copyright (c) 1987, Software Heaven, Inc." and "DungeonMaster" are at the expected locations in sector 247. If any call to `Floprd()` fails or if the strings are not found where expected, then the game disk is not detected. This causes the game to permanently display a dialog box with the message "THAT'S NOT THE MASTER DISK!".

## Copy protection scheme C

Keywords in source code: CPSC, CPSCE, CPSCDF

CPSC checks that the copy protection sector 7 can be read and that it contains at least one fuzzy bit.
This check is performed each time the player saves the game. Any change made to this function (in order to crack the copy protection) is detected by 3 other functions in the game that compute checksums of the code of the save game function. These three checksums are computed using 3 different algorithms so that it is virtually impossible to change the function in a way that would both crack the copy protection and not change at least one of the 3 checksum values. Moreover, one of the 3 functions that compute the checksums is also protected againt changes with another checksum computed on its own code.

When saving the game, `F0433_STARTEND_ProcessCommand140_SaveGame_CPSCDF` contains inline assembly code to read sector 7 in a temporarily allocated buffer and check the fuzzy bits values. These operations are performed twice in a row. The game displays a dialog box with the message "THAT'S NOT THE MASTER DISK!" until at least one fuzzy bit is detected between the two sector readings.
In Dungeon Master, the sector is read by calling `Floprd()`. In Chaos Strikes Back, the sector is read using custom code similar to the code in graphic #538, in `S0080_CPSDF_CheckDMATransferCompletion` and in `S0081_CPSDF_TurnOffFloppyDrive`.

The first time a dungeon is loaded after the game has loaded (hence not when restarting a game after the party died) `F0434_STARTEND_IsLoadDungeonSuccessful_CPSC` has to allocate memory for the dungeon map data in `G0279_pppuc_DungeonMapData`. `F0079_CPSC_GetChecksumAdd` is called to check that the function `F0433_STARTEND_ProcessCommand140_SaveGame_CPSCDF` has not been modified. If the function was modified then memory is not allocated and `G0279_pppuc_DungeonMapData` is not initialized correctly. This will crash the game.

When the player opens the save game menu by pressing CTRL-S on the keyboard or by clicking on the disk icon in the champion inventory, the command `C140_COMMAND_SAVE_GAME` is issued. When processing that command in `F0380_COMMAND_ProcessQueue_CPSC`, `F0464_CPSC_GetChecksumSub` is called to check that the function `F0433_STARTEND_ProcessCommand140_SaveGame_CPSCDF` has not been modified. If the function was modified then the champion inventory is closed instead of displaying the save game menu.

When `F0267_MOVE_GetMoveResult_CPSCE` is called to move the party or an object to a destination where MapIndex > 2 and MapX is a multiple of 8 and MapY is a multiple of 4 then it calls `F0413_CPSC_GetChecksumEor` to check that the function `F0433_STARTEND_ProcessCommand140_SaveGame_CPSCDF` has not been modified. If the function was modified then the destination coordinates are altered so that the party or object may move to an invalid location. This will crash the game.

When the player clicks a mouse button while the command queue is locked, `F0359_COMMAND_ProcessClick_CPSC` buffers the click and if `G0440_B_Consequence_StopSearchingMouseInput_CPSC = C0_FALSE` it calls `F0464_CPSC_GetChecksumSub` to check that the function `F0380_COMMAND_ProcessQueue_CPSC` had not been modified. If the function was modified then `G0440_B_Consequence_StopSearchingMouseInput_CPSC` is incremented (equivalent as setting it to `C1_TRUE`) which causes `F0358_COMMAND_GetCommandFromMouseInput_CPSC` to stop searching in mouse input lists, thus ignoring mouse clicks indefinitely.

## Copy protection scheme D

Keywords in source code: CPSD, CPSADEF, CPSCDF, CPSDF, CPSDEF

CPSD checks that the copy protection sector 247 can be read. No check is made on the actual contents of this sector.
It is spread over 3 functions and can be in two distinct states. The current state of CPSD is stored in `G0240_i_Graphic559_State_CPSD`.

### State 1: the value of `G0240_i_Graphic559_State_CPSD` is even

The next step to execute is 'Read sector 247'. CPSD starts with this step because `G0240_i_Graphic559_State_CPSD` is set to 4 (even) when the game starts running (as loaded from graphic #559).

Each time `F0064_SOUND_RequestPlay_CPSD` is called to play a sound immediately (when the party movement is blocked, when a champion swallows, when a teleporter buzzes and when the party screams because of death or a fall through a pit), `G0586_i_ImmediateSoundCount_CPSD` is incremented. When `G0586_i_ImmediateSoundCount_CPSD >= 88` and there is no copy protection sector being read (`G0625_i_FloppyDriveDMATimeout_CPSDF = 0`) then

- `G0586_i_ImmediateSoundCount_CPSD` is reset to 0.
- The value of `G0240_i_Graphic559_State_CPSD` is changed so it becomes odd (+3). This moves CPSD to State 2.
- A flag is set in `G0644_i_FloppyDiskReadBufferContentType_CPSDF` to indicate that the buffer contains sector 247.
- `S0465_CPSD_ReadSector247` is called to read sector 247 in the last 512 bytes of `G0643_puc_FloppyDiskReadBuffer_CPSDF`.
- The values of `G0329_i_Useless_CPSD` and `G0058_i_Graphic562_Useless_CPSD` are also updated but these variables are never used anywhere else in the program.

### State 2: the value of `G0240_i_Graphic559_State_CPSD` is odd

Sector 247 was read and the next step to execute is 'Apply consequence and Reset CPSD to step 1' (there is no consequence if the read operation succeeded).

- Apply consequence:
  - In `F0002_MAIN_GameLoop_CPSDF`, every 64 ticks, if there is a copy protection sector read failure (flag set in `G0315_i_SectorsReadFailure_CPSDF`) and the previous read succeeded (flag not set in `G0316_i_SectorsReadPreviousFailure_CPSDF`) then the program requires the game disk in the drive. This triggers CPSB and displays a dialog box with the message "THAT'S NOT THE MASTER DISK!"
- Reset CPSD to step 1 (this cannot occur before 'Apply consequence'):
  - When `F0065_SOUND_PlayPendingSound_CPSD` is called by `main()`, if `G0240_i_Graphic559_State_CPSD` is odd and there is no copy protection sector being read (`G0625_i_FloppyDriveDMATimeout_CPSDF = 0`) then
    - if the previous reading of sector 247 was a failure (in `G0316_i_SectorsReadPreviousFailure_CPSDF`, which means `F0002_MAIN_GameLoop_CPSDF` has already displayed the dialog after a read failure and copied `G0315_i_SectorsReadFailure_CPSDF` into `G0316_i_SectorsReadPreviousFailure_CPSDF`) then clear the failure bits for sector 247 in `G0316_i_SectorsReadPreviousFailure_CPSDF` and `G0315_i_SectorsReadFailure_CPSDF`. This resets the status for the next pass of CPSD.
    - `G0240_i_Graphic559_State_CPSD` is incremented so the value becomes even. This moves CPSD back to State 1.

Note: `G0240_i_Graphic559_State_CPSD` is also changed (+4) in `F0210_CPSE_ProcessEvent22_IsFuzzySectorValid_NormalBytes`. If the value was odd it remains odd and if it was even it remains even. Because the effect of this variable is not based directly on its value but only on its oddness, this change of value actually has no effect.

## Copy protection scheme E

Keywords in source code: CPSE, CPSADEF, CPSCE, CPSDEF, CPSEF

When the game is initialized `Floprd()` is called to read sector 7 into `G0295_ac_FuzzySectorReadingBuffer_CPSE` in `F0448_STARTUP1_InitializeMemoryManager_CPSADEF` (DM) or `F0463_START_InitializeGame_CPSADEF` (CSB).

Each time the party moves to a different map `F0003_MAIN_ProcessNewPartyMap_CPSE` is called. It calls `F0096_DUNGEONVIEW_LoadCurrentMapGraphics_CPSDF` which calls `F0484_MEMORY_LoadGraphics_CPSDEF` which in turn calls `Floprd()` to read sector 7 into `G0295_ac_FuzzySectorReadingBuffer_CPSE`.
If `G0418_l_LastEvent22Time_CPSE` is more than 500 ticks in the past (this prevents checking the copy protection on each map change if the player changes multiple times over a short period of time) then:

- Add an event 22 to the timeline expiring 2 (DM) or 3 (CSB) ticks in the future.
  - Upon expiration, `F0261_TIMELINE_Process_CPSEF` calls `F0210_CPSE_ProcessEvent22_IsFuzzySectorValid_NormalBytes`.
  - If this test fails then set `G0022_i_Graphic562_IndirectStopExpiringEvent_CPSE = C00136_TRUE`. This causes an incrementation of `G0330_i_StopExpiringEvents_CPSE` in `F0267_MOVE_GetMoveResult_CPSCE` (`G0022_i_Graphic562_IndirectStopExpiringEvent_CPSE` is also reset to `C00555_FALSE`).
- Read sector 7 in `G0295_ac_FuzzySectorReadingBuffer_CPSE` with `Floprd()`
- Set `G0488_i_Graphic560_RequestCheckFuzzyBitCount_CPSE = C01113_TRUE` in order to trigger a call to `F0356_CPSE_IsFuzzySectorValid_ByteValuesAndFuzzyBitCount` in the next call to `F0077_MOUSE_EnableScreenUpdate_CPSE`
  - `F0356_CPSE_IsFuzzySectorValid_ByteValuesAndFuzzyBitCount` resets `G0488_i_Graphic560_RequestCheckFuzzyBitCount_CPSE`. This function stores its results in two static local variables `G0628_i_LastFuzzyBitCount_CPSE` and `G0629_i_ConsecutiveIdenticalFuzzyBitCounts_CPSE` for comparison during the next call.
  - If the test fails, set `G0189_i_Graphic558_StopAddingEvents_CPSE = C01113_TRUE`
- Set `G0031_i_Graphic562_FuzzySectorAnalyzed_CPSE = C00255_FALSE` in order to trigger either:
  - A call to `F0277_CPSE_IsFuzzySectorValid_FuzzyBits` in the next call to either
    - `F0369_COMMAND_ProcessTypes101To108_ClickInSpellSymbolsArea_CPSE` when the player casts a spell
    - `F0371_COMMAND_ProcessType111To115_ClickInActionArea_CPSE` when the player performs the second action in the list
    - `F0115_DUNGEONVIEW_DrawObjectsCreaturesProjectilesExplosions_CPSEF` when drawing an explosion on the party square
    During this call `F0277_CPSE_IsFuzzySectorValid_FuzzyBits` will:
    - reset `G0031_i_Graphic562_FuzzySectorAnalyzed_CPSE` to `C00136_TRUE`.
    - reset `G0068_i_CheckLastEvent22Time_CPSE` to `C00512_FALSE`
    If the test fails in any of the three functions, set `G0189_i_Graphic558_StopAddingEvents_CPSE = C00136_TRUE`
  - In `F0338_INVENTORY_DecreaseTorchesLightPower_CPSE`, if `G0031_i_Graphic562_FuzzySectorAnalyzed_CPSE != C00136_TRUE`:
    - Calls `F0137_CPSEF_PatchFunctionWithHiddenCodeLauncher` to patch `F0331_CHAMPION_ApplyTimeEffects_CPSF` with a copy of `R0138_CPSEF_HiddenCodeLauncher` copied to a randomly selected buffer from the list in `G0368_api_CodePatches_CPSE` which contains 6 buffers: `G0585_ai_CodePatch0_CPSE`, `G0344_ai_CodePatch1_CPSE`, `G0533_ai_CodePatch2_CPSE`, `G0408_ai_CodePatch3_CPSE`, `G0393_ai_CodePatch4_CPSE`, `G0427_ai_CodePatch5_CPSE`. The patch routine is configured to run hidden code from graphic #21 (`C021_GRAPHIC_CHECK_FUZZY_BITS_IN_FUZZY_SECTOR`) with the address of `G0429_api_Graphic21Result_CPSE` specified as parameter. This array contains 3 (DM) or 4 (CSB) pointers to the following variables:
      - `int G0293_ai_FuzzyBits_CPSE[32]` graphic #21 compares fuzzy bits from sector 7 with the ones here (from previous reading) and then replaces them here
      - `int G0031_i_Graphic562_FuzzySectorAnalyzed_CPSE`
      - `long G0418_l_LastEvent22Time_CPSE`
      - `int G0068_i_CheckLastEvent22Time_CPSE` (Chaos Strikes Back only)
    - When the patched function is executed (just a bit after `F0338_INVENTORY_DecreaseTorchesLightPower_CPSE` in `F0002_MAIN_GameLoop_CPSDF`), the hidden code is executed first and then the function is restored to its original form without the patch. The hidden code in graphic #21 will collect all fuzzy bits from the sector 7 stored into `G0295_ac_FuzzySectorReadingBuffer_CPSE` and store them in `G0293_ai_FuzzyBits_CPSE`. It will then set `G0031_i_Graphic562_FuzzySectorAnalyzed_CPSE` to `C00136_TRUE`. If at least one fuzzy bit was detected then `G0418_l_LastEvent22Time_CPSE` is not affected else it is set to 0. In CSB, `G0068_i_CheckLastEvent22Time_CPSE` is set to `C00512_FALSE`
- CSB only: Set `G0068_i_CheckLastEvent22Time_CPSE = C00555_TRUE` in order to trigger a check in `F0355_INVENTORY_Toggle_CPSE` that the last event 22 occured less than 1000 ticks ago. `G0068_i_CheckLastEvent22Time_CPSE` is reset by graphic #21 if it runs or by `F0277_CPSE_IsFuzzySectorValid_FuzzyBits`.
  - If this test fails then `G0330_i_StopExpiringEvents_CPSE`++

### Consequences

In `F0240_TIMELINE_IsFirstEventExpired_CPSE`, if `G0330_i_StopExpiringEvents_CPSE >= 2` then events do not expire anymore. Consequently, F0256_TIMELINE_ProcessEvent53_Watchdog_CPSE is not called anymore to update G0374_l_WatchdogTime_CPSE and after up to 300 ticks, F0002_MAIN_GameLoop_CPSDF will display the 'SYSTEM ERROR 60' message and stop the game.

In `F0238_TIMELINE_AddEvent_GetEventIndex_CPSE`

- If `G0189_i_Graphic558_StopAddingEvents_CPSE` was changed from its initial value of 3217 then the event is not added to the timeline.
- In CSB, the same happens also if `G0418_l_LastEvent22Time_CPSE = 0`

## Copy protection scheme F

Keywords in source code: CPSF, CPSADEF, CPSCDF, CPSDF, CPSDEF, CPSEF

CPSF checks that the copy protection sector 7 can be read and that it contains at least one fuzzy bit. These operations are performed by executing 2 pieces of code hidden in `GRAPHICS.DAT` as regular bitmaps.
It is spread over many functions and can be in 7 distinct states. The current state of CPSF is stored in `G0069_ui_State_CPSF`. If the floppy disk is original, then the value of `G0069_ui_State_CPSF` will follow a sequence of 7 values corresponding to the 7 states: 15 5 10 16 6 11 13. This sequence will cause two cycles of read and analyze of sector 7, and finally CPSF will stop until the game is booted again. In version 1.1 and above, CPSF is also reset to step 1 when the game is restarted (when loading a game after the party has died) because `F0462_START_StartGame_CPSEF` reinitializes these values:

- `G0069_ui_State_CPSF = 15`
- `G0070_B_FuzzySectorAnalyzed_CPSF = C0_FALSE`
- `G0071_B_FuzzySectorReadingInitiated_CPSF = C0_FALSE`

CPSF is interwoven with the functions responsible for drawing the dungeon view. The full cycle of 7 states may occur over many executions of these functions as it depends on what is visible in the dungeon view (square types, projectiles).

`F0435_STARTEND_LoadGame` sets `G0319_ul_LoadGameTime` to the game time value from the saved game file.

### State 1: `G0069_ui_State_CPSF = 15`: 'First sector 7 reading'

This is the initial state. In versions 1.0, it is set only when the game is loaded. In version 1.1 and above, it is set each time a game is started (when the player starts a new game or restarts from a saved game).

In this state `F0118_DUNGEONVIEW_DrawSquareD3C_CPSF` will perform the following actions if square D3C is a visible teleporter and `G0069_ui_State_CPSF >= 15` and there is no ongoing copy protected sector reading and the player either (loaded the current game before 66531 ticks of total play time (`G0319_ul_LoadGameTime < 66531`) and more than 65530 ticks of total play time have now passed (`G0313_ul_GameTime > 65530`)) or (played for at least 8192 ticks since the game was loaded (`G0313_ul_GameTime > G0319_ul_LoadGameTime + 8192`)):

- Call `F0137_CPSEF_PatchFunctionWithHiddenCodeLauncher` to patch one of the 4 functions (randomly selected):
  - `F0123_DUNGEONVIEW_DrawSquareD1R`
  - `F0124_DUNGEONVIEW_DrawSquareD1C`
  - `F0122_DUNGEONVIEW_DrawSquareD1L`
  - `F0126_DUNGEONVIEW_DrawSquareD0R`
  with a copy of `R0138_CPSEF_HiddenCodeLauncher` copied to a randomly selected buffer from the list in `G0104_api_CodePatches_CPSF` which contains 3 buffers: `G0082_ai_CodePatch0_CPSF`, `G0089_ai_CodePatch1_CPSF`, `G0100_ai_CodePatch2_CPSF`. The patch routine is configured to run hidden code from graphic #538 (`C538_GRAPHIC_READ_FUZZY_SECTOR`) with the address of `G0625_i_FloppyDriveDMATimeout_CPSDF` specified as parameter so that graphic #538 can set the values of both `G0624_B_FloppyDriveTurnedOn_CPSDF` to `C1_TRUE` and `G0625_i_FloppyDriveDMATimeout_CPSDF` to 259. When the patched function is executed (just a bit later in the process of drawing the dungeon view in `F0128_DUNGEONVIEW_Draw_CPSF`), the hidden code is executed first and then the function is restored to its original form without the patch. The hidden code in graphic #538 will initiate a read operation of sector 7 into `G0643_puc_FloppyDiskReadBuffer_CPSDF`.
- `G0069_ui_State_CPSF` is decreased by 10: if the value was 15 it is now 5 (State 2), if the value was 16 it is now 6 (State 5).
- `G0071_B_FuzzySectorReadingInitiated_CPSF = C0_FALSE`

At the end of `F0128_DUNGEONVIEW_Draw_CPSF` after all functions that draw squares have been called, `G0071_B_FuzzySectorReadingInitiated_CPSF` is set to `C1_TRUE`

### State 2: `G0069_ui_State_CPSF = 5`: 'Analyze first sector 7 reading'

In this state `F0120_DUNGEONVIEW_DrawSquareD2R_CPSF` will perform the following actions if square D2R looks like a wall (without an alcove ornament) and `G0069_ui_State_CPSF <= 6` and `G0071_B_FuzzySectorReadingInitiated_CPSF = C1_TRUE` and there is no copy protected sector being read (`G0625_i_FloppyDriveDMATimeout_CPSDF = 0`), that is, if the sector 7 reading operation has completed:

- If sector 7 reading failed or if `G0644_i_FloppyDiskReadBufferContentType_CPSDF` does not contain sector 7 then
  - If there have been two consecutive sector 7 read failures then `G0072_ui_GraceReadRetryCount_CPSF++`
  - If the previous sector 7 reading failed then add 10 to `G0069_ui_State_CPSF`, which causes CPSF to go back to the previous state to read sector 7 again: if current state is State 2 then `G0069_ui_State_CPSF` is set to 15 (State 1), if current state is State 5 then `G0069_ui_State_CPSF` is set to 16 (State 4)
- else
  - Set `G0070_B_FuzzySectorAnalyzed_CPSF = C0_FALSE`. In Chaos Strikes Back only, set `G0073_i_StopFreeingMemory_CPSF = C00555_TRUE`. These values will be overwritten when executing graphic #21 below.
  - Calls `F0137_CPSEF_PatchFunctionWithHiddenCodeLauncher` to patch one of the 2 functions (randomly selected)
    - `F0124_DUNGEONVIEW_DrawSquareD1C`
    - `F0125_DUNGEONVIEW_DrawSquareD0L`
    with a copy of `R0138_CPSEF_HiddenCodeLauncher` copied to a randomly selected buffer from the list in `G0104_api_CodePatches_CPSF` which contains 3 buffers: `G0082_ai_CodePatch0_CPSF`, `G0089_ai_CodePatch1_CPSF`, `G0100_ai_CodePatch2_CPSF`. The patch routine is configured to run hidden code from graphic #21 (`C021_GRAPHIC_CHECK_FUZZY_BITS_IN_FUZZY_SECTOR`) with the address of `G0083_api_Graphic21Result_CPSF` specified as parameter. This array contains 4 pointers to the following variables:
    - `int G0081_ai_FuzzyBits_CPSF[32]` graphic #21 compares fuzzy bits from sector 7 with the ones here (from previous reading) and then replaces them here
    - `BOOLEAN G0070_B_FuzzySectorAnalyzed_CPSF`
    - `BOOLEAN G0078_B_FuzzyBitFound_CPSF`
    - `BOOLEAN G0071_B_FuzzySectorReadingInitiated_CPSF` (Dungeon Master only, not used)
      `int G0073_i_StopFreeingMemory_CPSF` (Chaos Strikes Back only)
  - When the patched function is executed (just a bit later in the process of drawing the dungeon view in `F0128_DUNGEONVIEW_Draw_CPSF`), the hidden code is executed first and then the function is restored to its original form without the patch. The hidden code in graphic #21 will collect all fuzzy bits from the sector 7 stored into `G0643_puc_FloppyDiskReadBuffer_CPSDF` and store them in `G0081_ai_FuzzyBits_CPSF`. It will then set `G0070_B_FuzzySectorAnalyzed_CPSF` to `C00136_TRUE`. If at least one fuzzy bit was detected then `G0078_B_FuzzyBitFound_CPSF` is not affected else it is set to 0.
  - BUG0_01 Coding error without consequence. If graphic #21 is called with G0643_puc_FloppyDiskReadBuffer_CPSDF as second parameter and it does not detect any fuzzy bit then it writes 0 as a long (4 bytes) to `G0078_B_FuzzyBitFound_CPSF` but this variable is an int (2 bytes) which causes `G0077_B_DoNotDrawFluxcagesDuringEndgame` (not related to the copy protection) to be overwritten too as a side effect as it is the next variable in memory. No consequence because the value of this variable is always 0 until the very end of the game.
  - In Dungeon Master the fourth pointer to `G0071_B_FuzzySectorReadingInitiated_CPSF` is not used by graphic #21. In Chaos Strikes Back, this fourth pointer is replaced by `G0073_i_StopFreeingMemory_CPSF` and graphic #21 sets this value to `C00512_FALSE`.
  - Add 5 to `G0069_ui_State_CPSF`, which causes CPSF to proceed to the next state: if current state is State 2 then `G0069_ui_State_CPSF` is set to 10 (State 3), if current state is State 5 then `G0069_ui_State_CPSF` is set to 11 (State 6)

### State 3: `G0069_ui_State_CPSF = 10`: 'Proceed to second reading'

In `F0115_DUNGEONVIEW_DrawObjectsCreaturesProjectilesExplosions_CPSEF`, if `G0070_B_FuzzySectorAnalyzed_CPSF` then the value of `G0069_ui_State_CPSF` is adjusted: if current state is State 3 then `G0069_ui_State_CPSF` is set to 16 (State 4), if current state is State 6 then `G0069_ui_State_CPSF` is set to 13 (State 7)

### State 4: `G0069_ui_State_CPSF = 16`: 'Second sector 7 reading'

In this state, CPSF has the same behavior as state 1, but for a second cycle.

### State 5: `G0069_ui_State_CPSF = 6`: 'Analyze second sector 7 reading'

In this state, CPSF has the same behavior as state 2, but for a second cycle.

### State 6: `G0069_ui_State_CPSF = 11`: ''

In this state, CPSF has the same behavior as state 3, but for a second cycle.

### State 7: `G0069_ui_State_CPSF = 13`: 'End of CPSF'

In this state, nothing happens anymore with CPSF. In version 1.1 and above, CPSF is reset to state 1 when restarting the game after the party has died.

### Consequences of CPSF

In `F0107_DUNGEONVIEW_IsDrawnWallOrnamentAnAlcove_CPSF`

- Calls `F0106_DUNGEONVIEW_TestResetToStep1_CPSF`.
  - If the number of retries in `G0072_ui_GraceReadRetryCount_CPSF < 5` and no fuzzy bit was found in the last sector 7 reading (`G0078_B_FuzzyBitFound_CPSF = C0_FALSE`) then
    - If no copy protected sector is being read and sector 7 was read without failure then set failure flag in `G0315_i_SectorsReadFailure_CPSDF` because no fuzzy bit was found and increment `G0072_ui_GraceReadRetryCount_CPSF`.
    - Reset CPSF to state 1 by setting `G0069_ui_State_CPSF` to 15
- If no fuzzy bit was found in the last sector 7 reading (`G0078_B_FuzzyBitFound_CPSF = C0_FALSE`) then Altars of Vi cannot be used anymore.

In `F0128_DUNGEONVIEW_Draw_CPSF`

- In Chaos Strikes Back only, if sector 7 reading was not initiated by graphic #538 yet (`G0071_B_FuzzySectorReadingInitiated_CPSF = C0_FALSE`) and (`G0073_i_StopFreeingMemory_CPSF != C00512_FALSE`) (the value of `G0073_i_StopFreeingMemory_CPSF` is set to `C00512_FALSE` by graphic #21) then do nothing (This will eventually cause the game to run out of memory) else
  - `G0071_B_FuzzySectorReadingInitiated_CPSF = C1_TRUE`. At this step the function patched with graphic #21 has necessarily been executed, but the sector reading may still be in progress.
  - Free the temporary memory allocated for drawing the dungeon view.

In `E0017_MAIN_Exception28Handler_VerticalBlank_CPSDF`

- If no fuzzy bit was found (`G0078_B_FuzzyBitFound_CPSF = C0_FALSE`) then
  - if `G0328_i_TimeBombToKillParty_CPSF` = 0 then
    - Call `F0106_DUNGEONVIEW_TestResetToStep1_CPSF`
    - `G0328_i_TimeBombToKillParty_CPSF += 5`.

In `F0331_CHAMPION_ApplyTimeEffects_CPSF`

- if `G0328_i_TimeBombToKillParty_CPSF` is not 0 then it is decremented and when reaching 1
  - `G0524_B_RestartGameAllowed` is set to `C0_FALSE` and the whole party is killed, without the ability to restart from a saved game. Rebooting from scratch is necessary.
  - `G0328_i_TimeBombToKillParty_CPSF` is reset to 0 in `F0457_START_DrawEnabledMenus_CPSF`.