├── .gitignore ├── .gitmodules ├── CHANGELOG.txt ├── CONTROLS.txt ├── LICENCE.txt ├── Makefile ├── README.md ├── banner.png ├── banner.wav ├── demos ├── README.txt ├── holiday93_demo.zip ├── holiday94_demo.zip ├── ohno_demo.zip ├── original_demo.zip ├── xmas91.zip └── xmas92.zip ├── doc ├── README.txt ├── data │ ├── explode.dat │ ├── explode_readme.txt │ ├── lemmings_adlib_format.txt │ ├── lemmings_dat_file_format.txt │ ├── lemmings_lvl_file_format.txt │ ├── lemmings_main_dat_file_format.txt │ ├── lemmings_vgagrx_dat_groundxo_dat_file_format.txt │ ├── lemmings_vgaspecx_dat_file_format.txt │ └── minimap.txt └── mechanics │ ├── @Lemmings_mechanics001.txt │ ├── @Lemmings_mechanics002.txt │ ├── @Lemmings_mechanics003.txt │ ├── @Lemmings_mechanics004.txt │ ├── @Lemmings_mechanics005.txt │ ├── @Lemmings_mechanics006.txt │ ├── @Lemmings_mechanics007.txt │ ├── @Lemmings_mechanics008.txt │ ├── @Lemmings_mechanics009.txt │ ├── @Lemmings_mechanics010.txt │ ├── @Lemmings_mechanics011.txt │ ├── @Lemmings_mechanics012.txt │ ├── @Lemmings_mechanics013.txt │ ├── @Lemmings_mechanics014.txt │ ├── Lemmings_mechanics001.txt │ ├── Lemmings_mechanics002.txt │ ├── Lemmings_mechanics003.txt │ ├── Lemmings_mechanics004.txt │ ├── Lemmings_mechanics005.txt │ ├── Lemmings_mechanics006.txt │ ├── Lemmings_mechanics007.txt │ ├── Lemmings_mechanics008.txt │ ├── Lemmings_mechanics009.txt │ ├── Lemmings_mechanics010.txt │ ├── Lemmings_mechanics011.txt │ ├── Lemmings_mechanics012.txt │ ├── Lemmings_mechanics013.txt │ └── Lemmings_mechanics014.txt ├── icon.png ├── include ├── 2p_button.h ├── audio.h ├── control.h ├── cursor.h ├── data_cache.h ├── decode.h ├── draw.h ├── gamespecific.h ├── gamespecific_2p.h ├── highperf_font.h ├── import_adlib.h ├── import_ground.h ├── import_level.h ├── import_main.h ├── import_wave.h ├── ingame.h ├── lemming.h ├── lemming_data.h ├── level.h ├── main.h ├── main_data.h ├── menu.h ├── network.h ├── network_game.h ├── network_menu.h ├── network_run_level.h ├── particles.h ├── patch_menu.h ├── savegame.h ├── settings.h └── settings_menu.h ├── lemmings ├── 2p │ ├── ohno │ │ └── README.txt │ └── orig │ │ └── README.txt ├── audio │ ├── README.txt │ ├── audio.bat │ ├── ohno │ │ └── README.txt │ ├── orig │ │ └── README.txt │ └── xmas │ │ └── README.txt ├── holi93 │ └── README.txt ├── holi93_demo │ └── README.txt ├── holi94 │ └── README.txt ├── holi94_demo │ └── README.txt ├── ohno │ └── README.txt ├── ohno_demo │ └── README.txt ├── orig │ └── README.txt ├── orig_demo │ └── README.txt ├── xmas91 │ └── README.txt └── xmas92 │ └── README.txt └── src ├── audio.c ├── control.c ├── data ├── 2p_button.c ├── cursor.c ├── highperf_font.c ├── particles.c └── patch_menu.c ├── data_cache.c ├── draw.c ├── import ├── adlib │ ├── 8086.cpp │ ├── 8086.h │ ├── adlib.cpp │ ├── adlib.h │ ├── dbopl.cpp │ └── dbopl.h ├── decode.c ├── gamespecific.c ├── gamespecific_2p.c ├── import_adlib.cpp ├── import_ground.c ├── import_level.c ├── import_main.c └── import_wave.c ├── ingame.c ├── lemming.c ├── main.c ├── menu.c ├── network ├── network_game.c ├── network_menu.c └── network_run_level.c ├── savegame.c ├── settings.c └── settings_menu.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | output/ 3 | *.3dsx 4 | *.elf 5 | *.smdh 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "buildtools"] 2 | path = buildtools 3 | url = https://github.com/esoteric-programmer/buildtools.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | Version 0.6.3 2 | - Completed ratings are marked with an asterisk in 1p level selection menu 3 | - Play a custom sound when lemming falls out of the level 4 | - Bugfixes 5 | 6 | Version 0.6.2 7 | - Data for CIA version can be loaded from /3ds/lemmings now 8 | - Introduced cache to reduce start-up time 9 | - CIA file uses homebrew logo now 10 | - Bugfix 11 | 12 | Version 0.6.1 13 | - Bugfixes (note: multiplayer mode is not compatible with v0.6) 14 | 15 | Version 0.6 16 | - Added 2 player mode via 3DS local wifi connection 17 | - In-game background color can be switched to dark blue (AMIGA style) 18 | - Direct drop glitch can be disabled in settings menu 19 | 20 | Version 0.5.1 21 | - Fixed lemming start position 22 | 23 | Version 0.5 24 | - Added settings menu 25 | - Bugfix 26 | 27 | Version 0.4 28 | - Added custom sounds and music 29 | - Audio settings are saved now 30 | - Fixed level: Wicked 2 31 | - Corrected fade-in and fade-out time 32 | - Removed sf2d 33 | - Cleaned up source 34 | 35 | Version 0.3.1 36 | - Added sound effects of traps 37 | - Added congratulation messages for finishing all levels of a game 38 | - Bugfix 39 | 40 | Version 0.3 41 | - Added audio 42 | 43 | Version 0.2.3 44 | - Added error screens 45 | - Bugfixes 46 | 47 | Version 0.2.2 48 | - Support demo versions: 49 | - Original Lemmings Demo 50 | - Oh No! More Lemmings Demo 51 | - Holiday Lemmings 1993 Demo 52 | - Holiday Lemmings 1994 Demo 53 | - Cleaned up source 54 | - Source release 55 | - Bugfixes 56 | 57 | Version 0.2.1 58 | - Support Xmas and Holiday Lemmings 59 | - Bugfix 60 | 61 | Version 0.2 62 | - Support "Oh no! More Lemmings" -> introduced folder structure (see README.txt) 63 | - Timer runs 13% slower (previously it was too fast) 64 | - Wrap around during level selection: FUN30 <-> FUN01; TRICKY30 <-> TRICKY01; and so on 65 | - Start-up time reduced 66 | - exit game with L+R (only in main menu) 67 | - added left handed control (see README.txt) 68 | 69 | Version 0.1 70 | - Initial release 71 | -------------------------------------------------------------------------------- /CONTROLS.txt: -------------------------------------------------------------------------------- 1 | Main menu: 2 | 3 | A, START start 1 player game 4 | X, SELECT start 2 player game 5 | Y enter settings menu 6 | UP, DOWN change rating; switch between original Lemmings and "Oh no! More Lemmings" 7 | touch touch at a lemming to trigger one of the actions above 8 | L + R exit 9 | 10 | 11 | Level selection: 12 | 13 | UP, DOWN select previous/next level 14 | LEFT, RIGHT change rating 15 | B cancel selection 16 | A, START start 1 player game with selected level 17 | touch no effect (sorry!) 18 | 19 | 20 | Settings menu: 21 | 22 | UP, DOWN select menu item 23 | A toggle checkbox, select first/last option, or start key assignment 24 | LEFT, RIGHT select previous/next option (if supported by current menu point) 25 | B exit menu without saving 26 | X delete key binding 27 | START save and exit menu 28 | touch cancel key assignment 29 | 30 | 31 | In game: 32 | 33 | C-PAD move cursor 34 | A click at cursor position 35 | B pause/resume (in 2 player mode during level inspection: start game) 36 | X select next skill 37 | Y select previous skill 38 | D UP increase release rate (not in 2 player game) 39 | D DOWN decrease release rate (not in 2 player game) 40 | D RIGHT step one frame while game is paused (not in 2 player game) 41 | L time runs 3 times faster while held down (not in 2 player game) 42 | R + C-PAD scroll horizontal 43 | SELECT highlight/select non-priorized lemming 44 | START press twice to nuke level (give up); does not work while game is paused 45 | in 2 player game, both players must nuke the level at the same time 46 | touch move cursor to touch position and click there 47 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyrighted third-party content: 2 | buildtools 3 | demos/* 4 | doc/* 5 | lemmings/audio/audio.bat 6 | src/adlib/adlib.cpp (GPL) 7 | src/adlib/adlib.h (GPL) 8 | src/adlib/dbopl.cpp (GPL) 9 | src/adlib/dbopl.h (GPL) 10 | src/data/2p_button.c 11 | src/data/cursor.c 12 | src/data/particles.c 13 | src/data/patch_menu.c 14 | banner.png 15 | banner.wav 16 | icon.png 17 | as well as ctrulib (not included in repository, but linked into binary release) 18 | 19 | Note that all parts of the Lemmings series that are imitated by this software, 20 | e.g. game mechanics (e.g. src/lemming.c), 21 | compression method of .DAT files (src/import/decode.c), 22 | colors and game messages (src/import/gamespecific.c), 23 | and so on, may be protected by law, too. 24 | 25 | Licence of OTHER content NOT mentioned above: 26 | 27 | To the extent possible under law, the author has dedicated all copyright 28 | and related and neighboring rights to this software to the public domain 29 | worldwide. 30 | This does NOT concern the third-party content listed above. 31 | This software is distributed without any warranty. 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MKDIR = mkdir -p 2 | 3 | # TARGET # 4 | 5 | TARGET := 3DS 6 | LIBRARY := 0 7 | 8 | ifeq ($(TARGET),3DS) 9 | ifeq ($(strip $(DEVKITPRO)),) 10 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPro") 11 | endif 12 | 13 | ifeq ($(strip $(DEVKITARM)),) 14 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 15 | endif 16 | endif 17 | 18 | # COMMON CONFIGURATION # 19 | 20 | NAME := lemmings 21 | 22 | BUILD_DIR := build 23 | OUTPUT_DIR := output 24 | INCLUDE_DIRS := include 25 | SOURCE_DIRS := src 26 | 27 | EXTRA_OUTPUT_FILES := 28 | 29 | LIBRARY_DIRS := $(DEVKITPRO)/libctru 30 | LIBRARIES := ctru 31 | 32 | BUILD_FLAGS := 33 | RUN_FLAGS := 34 | 35 | VERSION_MAJOR := 0 36 | VERSION_MINOR := 6 37 | VERSION_MICRO := 3 38 | 39 | # 3DS CONFIGURATION # 40 | 41 | TITLE := Lemmings for 3DS 42 | LONGTITLE := Lemmings for 3DS 43 | DESCRIPTION := Clone of classic game 44 | AUTHOR := Matthias 45 | PRODUCT_CODE := CTR-P-CLEM 46 | UNIQUE_ID := 0xF9173 47 | 48 | SYSTEM_MODE := 64MB 49 | SYSTEM_MODE_EXT := Legacy 50 | 51 | ICON_FLAGS := --flags visible,ratingrequired,recordusage --cero 153 --esrb 153 --usk 153 --pegigen 153 --pegiptr 153 --pegibbfc 153 --cob 153 --grb 153 --cgsrr 153 52 | 53 | ROMFS_DIR := romfs 54 | BANNER_AUDIO := banner.wav 55 | BANNER_IMAGE := banner.png 56 | ICON := icon.png 57 | 58 | # INTERNAL # 59 | 60 | include buildtools/make_base 61 | 62 | # ADD lemmings/* AND TEXT FILES 63 | 64 | TEXTFILES := $(wildcard *.txt) $(wildcard *.md) 65 | TEXTFILES_OUTPUT := $(addprefix $(OUTPUT_DIR)/,$(TEXTFILES)) 66 | GAMES := $(wildcard lemmings/*) 67 | GAMES_OUTPUT := $(GAMES:lemmings/%=$(OUTPUT_DIR)/lemmings/%) 68 | 69 | $(OUTPUT_ZIP_FILE): $(GAMES_OUTPUT) $(TEXTFILES_OUTPUT) 70 | 71 | $(OUTPUT_DIR)/lemmings/%: lemmings/% 72 | @mkdir -p $(OUTPUT_DIR)/lemmings/ 73 | @cp -r $< $@ 74 | 75 | $(OUTPUT_DIR)/%.txt: %.txt 76 | @cp $< $@ 77 | 78 | $(OUTPUT_DIR)/%.md: %.md 79 | @cp $< $@ 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lemmings for 3DS 2 | 3 | Setup: 4 | Create a folder called lemmings either on the root of the SD card or in the 3ds folder. 5 | Copy the required* files into the following subfolders of lemmings folder: 6 | orig\_demo, orig, ohno\_demo, ohno, xmas91, xmas92, holi93\_demo, holi93, holi94\_demo and/or holi94. You need to fill at least one folder to play the game. 7 | To avoid duplicates, you may only fill these folders if you own all Lemmings games: 8 | orig, ohno, xmas91, xmas92, holi94 9 | For multiplayer mode, fill the subfolders of the 2p folder at the host system. 10 | 11 | If you modify level files later, you should delete the CACHE_xx.DAT files in the lemmings folder afterwards. 12 | 13 | The game uses the NDSP service to play audio. Thus you need a DSP firm dump to have audio. 14 | You may want to use your own sound and/or music files. 15 | Put your wave files into lemmings/audio folder. 16 | See README.txt in that folder for more details. 17 | 18 | These files are not included (and will never be included), 19 | because they are protected by copyright law. 20 | 21 | For game control, see CONTROL.txt. 22 | 23 | \* See README.txt in these folders. 24 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/banner.png -------------------------------------------------------------------------------- /banner.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/banner.wav -------------------------------------------------------------------------------- /demos/README.txt: -------------------------------------------------------------------------------- 1 | The games in this folder were released as free demo versions. 2 | However, they are protected by copyright law, so they must not be modified, 3 | and the graphics, sound, and levels must not be included in other software. 4 | 5 | Therefore, the games are in this separate folder and not in the 6 | lemmings folder of the main project. 7 | 8 | You can play the demos using DOSBox. 9 | -------------------------------------------------------------------------------- /demos/holiday93_demo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/demos/holiday93_demo.zip -------------------------------------------------------------------------------- /demos/holiday94_demo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/demos/holiday94_demo.zip -------------------------------------------------------------------------------- /demos/ohno_demo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/demos/ohno_demo.zip -------------------------------------------------------------------------------- /demos/original_demo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/demos/original_demo.zip -------------------------------------------------------------------------------- /demos/xmas91.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/demos/xmas91.zip -------------------------------------------------------------------------------- /demos/xmas92.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/demos/xmas92.zip -------------------------------------------------------------------------------- /doc/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains information about the original DOS Lemmings. 2 | Almost all files in this directory are collected from other places in the internet. 3 | For most of these files the licence is unknown. 4 | 5 | data/minimap.txt contains my own observations. 6 | The files in the mechanics directory were found in LemmixPlayer's source code 7 | and MAY have the same licence (GPL) - or not. 8 | -------------------------------------------------------------------------------- /doc/data/explode.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/doc/data/explode.dat -------------------------------------------------------------------------------- /doc/data/explode_readme.txt: -------------------------------------------------------------------------------- 1 | explode.dat contains the raw bytes extracted from the DOS EXE. Each particle 2 | position is represented in the file by two signed bytes. (A signed byte 3 | ranges from -128 to 127, with hex 0x80 being -128 and 0xFF being -1.) The 4 | first of the two bytes is the offset x from the lemming's x position, and the 5 | second is the offset y. 6 | 7 | In the file, the first 160 bytes contains the positions for particles 1 thru 8 | 80, for when the lemming's animation index is 1 (remember that when an 9 | exploding lemming's animation index is 0, we draw the "explosion" graphic 10 | rather than the particles). Then the next 160 bytes contains the positions 11 | for particles 1 thru 80 when the lemming's animation index is 2, and so forth. 12 | One more special thing: whenever a particle's position offsets, as stored in 13 | the file, becomes (-128, -128), this means the particle has vanished and 14 | would no longer be displayed for the current animation frame and hereafter. 15 | 16 | Finally, the colors of the particles are specified in another lookup table in 17 | the EXE. A color value is of course an index to the palette used by the 18 | current level. The color assignment cycles through every 16 particles, as 19 | follows: 20 | 21 | particle 1: 4 22 | particle 2: 15 23 | particle 3: 14 24 | particle 4: 13 25 | particle 5: 12 26 | particle 6: 11 27 | particle 7: 10 28 | particle 8: 9 29 | particle 9: 8 30 | particle 10: 11 31 | particle 11: 10 32 | particle 12: 9 33 | particle 13: 8 34 | particle 14: 7 35 | particle 15: 6 36 | particle 16: 2 37 | ... 38 | 39 | As an example, consider the first 8 bytes (1 - 8) of the file: 40 | 41 | -52, -100, -23, -47, 7, -25, -2, -21, ... 42 | 43 | Now jump ahead and consider bytes 161 - 168: 44 | 45 | -128, -128, -47, -93, 15, -53, -4, -44, ... 46 | 47 | and then bytes 321 - 328: 48 | 49 | -128, -128, -128, -128, 23, -80, -6, -67, ... 50 | 51 | 481 - 488: 52 | 53 | -128, -128, -128, -128, 30, -107, -7, -90, ... 54 | 55 | 641 - 648: 56 | 57 | -128, -128, -128, -128, -128, -128, -9, -112, ... 58 | 59 | The numbers shown above gives you the particle trails for particles #1 thru #4. 60 | Suppose the lemming is at (100, 100). The frame-by-frame positions for those 61 | particles start off as follows: 62 | 63 | particle 1: (48, 0), 64 | particle 2: (77, 53), (53, 7), 65 | particle 3: (107, 75), (115, 47), (123, 20), (130, -7) [not displayed as it's off-screen], 66 | particle 4: (98, 79), (96, 56), (94, 33), (93, 10), (91, -12) [not displayed as it's off-screen], ... 67 | 68 | Notice how (-128, -128) signals the end of a particle. 69 | -------------------------------------------------------------------------------- /doc/data/lemmings_adlib_format.txt: -------------------------------------------------------------------------------- 1 | adlib.dat file format 2 | 3 | To read adlib.dat, you first have to decompress it using the .dat file decompression. 4 | You should end up with just one decompressed section. 5 | 6 | The decompressed adlib.dat section begins with x86 machine code to handle an interrupt. 7 | Therefore, in order to play sound from adlib.dat, DOS Lemmings decompresses adlib.dat 8 | into memory and adds an interrupt handler pointing to position 0x0000 of the 9 | decompressed data. Then the sound is played by calling this interrupt with 10 | parameter in the ax register. 11 | The parameter handling works as follows: 12 | 13 | switch (ah) { 14 | case 0x00: 15 | continue playing current sound; 16 | return; 17 | case 0x01: 18 | initialize adlib; 19 | return; 20 | case 0x02: 21 | stop playing current sound; 22 | return; 23 | case 0x03: 24 | load music title specified in al register; 25 | return; 26 | case 0x04: 27 | load sound specified in al register; 28 | return; 29 | case 0x05: 30 | some unknown action; 31 | return; 32 | default: 33 | return; 34 | } 35 | 36 | Because 16it DOS Lemmings cannot use multitasking, 37 | the action "continue playing current sound" has to be called 38 | constantly while sound or music plays. 39 | This action triggers some adlib hardware interrupts corresponding 40 | to the current position inside the sound or music and returns. 41 | It cannot play the whole song, because the control flow must 42 | return to the game in order to do all the other things like 43 | animating lemmings. 44 | -------------------------------------------------------------------------------- /doc/data/lemmings_lvl_file_format.txt: -------------------------------------------------------------------------------- 1 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2 | LEMMINGS .LVL FILE FORMAT 3 | BY rt 4 | =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 5 | 6 | document revision 0.0 7 | 8 | thanks to TalveSturges for the original alt.lemmings posting which got me 9 | started on decoding the .lvl format 10 | 11 | if you liked lemmings you should give CLONES a try. go to www.tomkorp.com 12 | for more information. 13 | 14 | this document will explain how to interpret the lemmings .lvl level 15 | file for the windows version of lemmings (directly saved levels in LemEdit). 16 | 17 | values preceded by 0x are hex values 18 | 19 | a lemmings .lvl file is 2048 bytes ( 0x0000 to 0x07FF ) 20 | 21 | ==GLOBALS=============================================================== 22 | 23 | BYTES 0x0000 to 0x0001 24 | Release Rate : 0x0000 is slowest, 0x00FA is fastest 25 | 26 | BYTES 0x0002 to 0x0003 27 | Num of lemmings : maximum 0x0072. 0x0010 would be 16 lemmings. 28 | 29 | BYTES 0x0004 to 0x0005 30 | Num to rescue : should be less than or equal to number of lemmings 31 | 32 | BYTES 0x0006 to 0x0007 33 | Time Limit : max 0x00FF, 0x0001 to 0x0009 works best 34 | 35 | BYTES 0x0008 to 0x0017 (2 bytes each, only lower byte is used) 36 | Num of skills : max 0x00FA. order is Climber, Floater, Bomber, 37 | Blocker,Builder, Basher, Miner, Digger 38 | 39 | BYTES 0x0018 to 0x0019 40 | Start screen xpos : 0x0000 to 0x04F0. is rounded to nearest multiple 41 | of 8. 42 | 43 | BYTES 0x001A to 0x001B 44 | Normal Graphic Set: 0x0000 is dirt, 0x0001 is fire, 0x0002 is squasher, 45 | 0x0003 is pillar,0x0004 is crystal, 0x0005 is brick, 46 | 0x0006 is rock, 0x0007 is snow and 0x0008 is bubble. 47 | 48 | BYTES 0x001C to 0x001D 49 | Extended Graphic Set: Apparently ignored in windows version. 50 | 51 | BYTES 0x001E to 0x001F 52 | Something? : doesn't seem to matter what goes here. use 0x0000. 53 | 54 | CUT-OFFS??? 55 | 56 | 57 | ==OBJECTS=============================================================== 58 | BYTES 0x0020 to 0x011F (8 byte blocks) 59 | 60 | x pos : min 0xFFF8, max 0x0638. 0xFFF8 = -24, 0x0000 = -16, 0x0008 = -8 61 | 0x0010 = 0, 0x0018 = 8, ... , 0x0638 = 1576 62 | note: should be multiples of 8 63 | 64 | y pos : min 0xFFD7, max 0x009F. 0xFFD7 = -41, 0xFFF8 = -8, 0xFFFF = -1 65 | 0x0000 = 0, ... , 0x009F = 159. 66 | note: can be any value in the specified range 67 | 68 | obj id : min 0x0000, max 0x000F. the object id is different in each 69 | graphics set, however 0x0000 is always an exit and 0x0001 is 70 | always a start. 71 | note: see appendix a for full object listings 72 | 73 | modifier : first byte can be 80 (do not overwrite existing terrain) or 40 74 | (must have terrain underneath to be visible). 00 specifies always 75 | draw full graphic. 76 | second byte can be 8F (display graphic upside-down) or 0F (display 77 | graphic normally) 78 | 79 | each 8 byte block starting at byte 0x0020 represents an interactive object. there 80 | can be a maximum of 32 objects. write 0x00 to fill bytes up to 0x0120 if 81 | there are less than 32 objects. 82 | 83 | ==TERRAIN=============================================================== 84 | BYTES 0x0120 to 0x075F (4 byte blocks) 85 | 86 | x pos : min 0x0000, max 0x063F. 0x0000 = -16, 0x0008 = -8, 0x0010 = 0, 87 | 0x063f = 1583. 88 | note: the xpos also contains modifiers. the first nibble can be 89 | 8 (do no overwrite existing terrain), 4 (display upside-down), or 90 | 2 (remove terrain instead of add it). you can add them together. 91 | 0 indicates normal. 92 | eg: 0xC011 means draw at xpos=1, do not overwirte, upside-down. 93 | 94 | y pos : 9-bit value. min 0xEF0, max 0x518. 0xEF0 = -38, 0xEF8 = -37, 95 | 0x020 = 0, 0x028 = 1, 0x030 = 2, 0x038 = 3, ... , 0x518 = 159 96 | note: the ypos value bleeds into the next value since it is 9bits. 97 | 98 | terrain id: min 0x00, max 0x3F. not all graphic sets have all 64 graphics. 99 | 100 | each 4 byte block starting at byte 0x0120 represents a terrain object. 101 | there can be a maximum of 400 terrain objects. write 0xFF fill the bytes 102 | up to byte 0x0760 if need be. 103 | 104 | ==STEEL AREAS=============================================================== 105 | BYTES 0x0760 to 0x07DF (4 byte blocks) 106 | 107 | x pos : 9-bit value. min 0x000, max 0xC78. 0x000 = -16, 0x008 = -12, 108 | 0x010 = -8, 0x018 = -4, ... , 0xC78 = 1580. 109 | note: each hex value represents 4 pixels. since it is 9 bit value it 110 | bleeds into the next attribute. 111 | 112 | y pos : min 0x00, max 0x27. 0x00 = 0, 0x01 = 4, 0x02 = 8, ... , 0x27 = 156 113 | note: each hex value represents 4 pixels 114 | 115 | area : min 0x00, max 0xFF. the first nibble is the x-size, from 0 - F. 116 | each value represents 4 pixels. the second nibble is the y-size. 117 | 0x00 = (4,4), 0x11 = (8,8), 0x7F = (32,64), 0x23 = (12,16) 118 | 119 | eg: 00 9F 52 00 = put steel at (-12,124) width = 24, height = 12 120 | 121 | each 4 byte block starting at byte 0x0760 represents a steel area which 122 | the lemmings cannot bash through. the first three bytes are given above, 123 | and the last byte is always 00.. what a waste of space considering how 124 | compact they made the first 3 bytes! write 0x00 to fill each byte up to 125 | 0x07E0 if need be. 126 | 127 | ==LEVEL NAME============================================================== 128 | BYTES 0x07E0 to 0x07FF 129 | 130 | a character string 32 bytes long. write 0x20 (space) to fill up the empty 131 | bytes. 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | ==APPENDIX A============================================================== 143 | available objects for each graphics set 144 | 145 | graphics set 0: 146 | 0x0000 = exit 147 | 0x0001 = start 148 | 0x0002 = waving green flag 149 | 0x0003 = one-way block pointing left 150 | 0x0004 = one-way block pointing right 151 | 0x0005 = water 152 | 0x0006 = bear trap 153 | 0x0007 = exit decoration, flames 154 | 0x0008 = rock squishing trap 155 | 0x0009 = waving blue flag 156 | 0x000A = 10 ton squishing trap 157 | 0x000B - 0x000F = invalid 158 | graphics set 1: 159 | 0x0000 = exit 160 | 0x0001 = start 161 | 0x0002 = waving green flag 162 | 0x0003 = one-way block pointing left 163 | 0x0004 = one-way block pointing right 164 | 0x0005 = red lava 165 | 0x0006 = exit decoration, flames 166 | 0x0007 = fire pit trap 167 | 0x0008 = fire shooter trap from left 168 | 0x0009 = waving blue flag 169 | 0x000A = fire shooter trap from right 170 | 0x000B - 0x000F = invalid 171 | graphics set 2: 172 | 0x0000 = exit 173 | 0x0001 = start 174 | 0x0002 = waving green flag 175 | 0x0003 = one-way block pointing left 176 | 0x0004 = one-way block pointing right 177 | 0x0005 = green liquid 178 | 0x0006 = exit decoration, flames 179 | 0x0007 = waving blue flag 180 | 0x0008 = pillar squishing trap 181 | 0x0009 = spinning death trap 182 | 0x000A - 0x000F = invalid 183 | graphics set 3: 184 | 0x0000 = exit 185 | 0x0001 = start 186 | 0x0002 = waving green flag 187 | 0x0003 = one-way block pointing left 188 | 0x0004 = one-way block pointing right 189 | 0x0005 = water 190 | 0x0006 = exit decoration, flames 191 | 0x0007 = waving blue flag 192 | 0x0008 = spinny rope trap 193 | 0x0009 = spikes from left trap 194 | 0x000A = spikes from right trap 195 | 0x000B - 0x000F = invalid 196 | graphics set 4: 197 | 0x0000 = exit 198 | 0x0001 = start 199 | 0x0002 = waving green flag 200 | 0x0003 = waving blue flag 201 | 0x0004 = one-way block pointing left 202 | 0x0005 = one-way block pointing right 203 | 0x0006 = sparkle water 204 | 0x0007 = slice trap 205 | 0x0008 = exit decoration, flames 206 | 0x0009 = electrode trap 207 | 0x000A = zap trap 208 | 0x000B - 0x000F = invalid 209 | graphics set 5: 210 | 0x0000 = exit 211 | 0x0001 = start 212 | 0x0002 = waving green flag 213 | 0x0003 = one-way block pointing left 214 | 0x0004 = one-way block pointing right 215 | 0x0005 = sandy water 216 | 0x0006 = hydraulic press trap 217 | 0x0007 = flatten wheel trap 218 | 0x0008 = waving blue flag 219 | 0x0009 = exit decoration, candy canes 220 | 0x000A - 0x000F = invalid 221 | graphics set 6: 222 | 0x0000 = exit 223 | 0x0001 = start 224 | 0x0002 = waving green flag 225 | 0x0003 = one-way block pointing left 226 | 0x0004 = one-way block pointing right 227 | 0x0005 = wavy tentacles (water) 228 | 0x0006 = tentacle grab trap 229 | 0x0007 = exit decoration, green thing 230 | 0x0008 = licker from right trap 231 | 0x0009 = exit decoration, green thing 232 | 0x000A = licker from right trap 233 | 0x000B = waving blue flag 234 | 0x000C - 0x000F = invalid 235 | graphics set 7: 236 | 0x0000 = exit 237 | 0x0001 = start 238 | 0x0002 = waving green flag 239 | 0x0003 = one-way block pointing left 240 | 0x0004 = one-way block pointing right 241 | 0x0005 = ice water 242 | 0x0006 = exit decoration, red flag 243 | 0x0007 = waving blue flag 244 | 0x0008 = icicle point trap 245 | 0x0009 = ice blast from left trap 246 | 0x000A - 0x000F = invalid 247 | graphics set 8: 248 | 0x0000 = exit 249 | 0x0001 = start 250 | 0x0002 = waving green flag 251 | 0x0003 = one-way block pointing left 252 | 0x0004 = one-way block pointing right 253 | 0x0005 = bubble water 254 | 0x0006 = exit decoration, red thing 255 | 0x0007 = waving blue flag 256 | 0x0008 = zapper from left trap 257 | 0x0009 = sucker from top trap 258 | 0x000A = gold thing?? 259 | 0x000B - 0x000F = invalid 260 | graphics set 9: 261 | 0x0000 = exit 262 | 0x0001 = start 263 | 0x0002 = gift box 264 | 0x0003 = exit decoration, flames 265 | 0x0004 = bouncing snowman 266 | 0x0005 = twinkling xmas lights 267 | 0x0006 = fireplace - bottom 268 | 0x0007 = fireplace - top 269 | 0x0008 = santa-in-the-box bottom 270 | 0x0009 = santa-in-the-box top 271 | 0x000A- 0x000F = invalid 272 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /doc/data/lemmings_vgaspecx_dat_file_format.txt: -------------------------------------------------------------------------------- 1 | vgaspecX.dat file format 2 | ------------------------ 3 | 4 | To read a vgaspecX.dat file format, you first decompress it using the regular .dat file decompression. You should end up with just a single decompressed data section. 5 | 6 | Then there is a second level of compression that you need to deal with, though a tiny part of the data section is already usable after the first-level decompression. Namely, the first 24 bytes specifies the VGA palette to be used for the graphics. As you know each VGA palette entry takes 3 bytes (I believe I explained palettes when explaining groundXo.dat), so 24 bytes is 8 palette entries. They get assigned to colors 8-15 in the game palette; colors 0-7 (the "fixed" colors) of the game palette are unaffected, though I think the game always duplicates color 8 in color 7 in its palette (test this out yourself to see if I'm right). 7 | 8 | Notice that since this overwrites half of the game palette, it also affects the colors of the interactive objects being used by a special graphics level. 9 | 10 | Following the 8-entry VGA palette is 16 bytes which I believe corresponds to the EGA palettes, but there's a gap in my notes on that, so you'll have to experiment to find out how those 16 bytes are used. It's really of little use since nowadays no one runs the game in EGA mode anyway. 11 | 12 | VGA (preview and ingame) 13 | EGA preview 14 | EGA in_game 15 | 16 | Anyway, after that finally comes the actual bitmap graphics which has this second level of compression. Fortunately, it is much simpler than the first-level compression scheme. It is byte-based rather than bit-based, and compression/decompression process the bytes in forward order rather than in reverse order as with the first-level scheme. 17 | 18 | There are 3 encodings. One encodes a block of raw bytes, another encodes a run (repetition of the same byte), and the last one just marks the end of a second-level decompressed data section. 19 | 20 | The raw chunk encoding consists of a byte in the range of 0x00 - 0x7F, followed by the raw bytes. That first byte indicates how big the chunk is, that is, how many raw bytes follow. 0x00 means 1, 0x01 means 2, ..., and 0x7F means 128. So for example: 21 | 22 | 0x00 0xab decompresses to 0xab 23 | 0x02 0x12 0x34 0x56 decompresses to 0x12 0x34 0x56 24 | 25 | The run-length encoding consists of a byte in the range of 0x81 - 0xFF, followed by a second byte. The first byte indicates the length of the run, and the second byte indicates the byte that is to be repeated. 0x81 means a length of 128, 0x82 a length of 127, ..., 0xFE means a length of 3 and 0xFF means a length of 2. So for example: 26 | 27 | 0xFF 0x45 decompresses to 0x45 0x45, ie. 2 copies of 0x45 28 | 0xC0 0xab decompresses to 65 copies of 0xab 29 | 30 | Finally, the presence of a 0x80 outside of the scope of the first or second encoding indicates the end of a second-level decompressed data section. 31 | 32 | So for example, the following decompresses to 3 second-level decompressed data sections: 33 | 34 | 0x00 0x81 0x05 0x80 0x81 0x80 0x81 0x80 0x81 0x80 0xFF 0x80 0x80 0xFD 0x00 0x80 35 | 36 | 0x00 0x81 -> 0x81 37 | 0x05 0x80 0x81 0x80 0x81 0x80 0x81 -> 0x80 0x81 0x80 0x81 0x80 0x81 38 | 0x80 -> end of first section 39 | 0xFF 0x80 -> 0x80 0x80 40 | 0x80 -> end of second section 41 | 0xFD 0x00 -> 0x00 0x00 0x00 0x00 42 | 0x80 -> end of third section 43 | 44 | ----------------------- 45 | 46 | Pretty easy eh? 47 | 48 | In each of the vgaspecX.dat files, after the second-level decompression you should get 4 data sections. Each section should be of size 14400 bytes, and each corresponds to a quarter of the terrain bitmap. 49 | 50 | The terrain bitmap has a fixed size of 960x160. Data section #1 corresponds to the first 40 scanlines, #2 corresponds to the next 40 scanlines, etc. 51 | 52 | Within each data section the bitmap section is stored as a planar bitmap (so altogether 4 separate planar bitmaps, one for each quarter of the terrain). However, unlike the interactive objects and terrain bitmaps, the planar bitmaps here is 3 bpp, so there are only 3 planes rather than 4. As usual, bitplane 0 comes first, then bitplane 1, then bitplane 2. 53 | 54 | The color numbers you get out of these planar bitmaps are mapped to the game's palette as follows: 55 | 56 | 0 -> 0, 1-7 -> 9-15 57 | 58 | I believe the terrain bitmap is always placed at a fixed location on the level, basically centered. However, it's also possible that maybe its placement is based on the level's starting x-position as specified in the level's LVL data. Experiment to find out, and e-mail me as to which theory is correct. 59 | 60 | I also believe when a level uses special graphics, the terrain specification section of the level's LVL data is ignored (so the terrain comes purely from the vgaspecX.dat file), but you should experiment to verify, and e-mail me if I'm wrong. 61 | 62 | ------------------------ 63 | 64 | Well that completes my knowledge of the .DAT files. 65 | 66 | Oh, in case of mistakes in this doc: try experimenting and see. What I know 100% sure is: 67 | 68 | 1) the second-level decompression exactly as described here 69 | 2) that after the second-level decompression, you get 4 sections each of size 14400 bytes, and that they are 3 bpp planar bitmaps, and the colors map as described 70 | 3) There are some palette information stored in the first dozen or so bytes before the compressed bitmaps. 71 | 72 | It is possible that maybe the doc here is wrong on how many bytes of palette data there are. In this doc I said there will be 40 bytes: first 24 bytes goes into the VGA palette, and the other 16 to EGA. If doing this leaves you with an incorrect number of data sections or incorrectly-sized or malformed data sections for the compressed bitmap, try varying the number of bytes the palette data takes up until you find something that works. Hopefully you won't need to do this, but if you do e-mail me also to alert me of the mistake. 73 | -------------------------------------------------------------------------------- /doc/data/minimap.txt: -------------------------------------------------------------------------------- 1 | Consider the following lines: 2 | 16, 24, 32, ..., 152 3 | 4 | For each line, consider 16 pixels in a row. 5 | Count the number of non-zero pixels. 6 | If at least 9 of these 16 pixels are non-zero, 7 | draw a colored pixel into the minimap. 8 | Otherwise leave it black. 9 | 10 | -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics001.txt: -------------------------------------------------------------------------------- 1 | 2 | ----- Original Message ----- 3 | From: ccexplore 4 | To: Eric en Zwaan HCCNET 5 | Sent: Wednesday, June 28, 2006 10:11 AM 6 | Subject: game mechanics of original Lemmings 7 | 8 | 9 | There are of course a lot involved in handling the movement and behavior of lemmings, so each e-mail will contain the same attached file but with more code added. Each e-mail also have more elaborate explanation of the code that was added to the file. Now for today: 10 | 11 | First, a note on notation: 12 | 13 | Since you aren't 100% familiar with C and I'm not 100% familiar with Delphi, I write the code in a pseudo-language that uses mostly English keywords (as opposed to C's rich set of operators like ! && || and what not). For convenience I will still use == to mean "equals" and != for "not equals". In the condition expression for things like IF and looping statements, sometimes you may see an expression being the value of some variable, like this: 14 | 15 | IF (HandleInteractiveObjects) 16 | 17 | This means HandleInteractiveObjects is a boolean (true/false) variable, and that the condition being checked for is "HandleInteractiveObjects equals TRUE". 18 | 19 | You will also see expressions like lemming.XXXX. I hope Delphi has concept of either structures or classes, if so then this should be familiar. 20 | 21 | ------------------------ 22 | 23 | Today's code is the lemmings update procedure. It is just a loop that goes through all lemmings that have been released, and handle the movement they should make for the current frame. It also assigns exploder to the next lemming if you're in the phase where the player nuked the level and there are still lemmings not being assigned an exploder. It does not handle skill assignments from the user, nor does it handle the entrance of new lemmings (they happen elsewhere, more on that some other day). 24 | 25 | The function is pretty straightforward and indeed the code is practically obvious. Of course for the procedure to work, I still need to define other procedure/functions like "DoCurrentAction" and "CheckForInteractiveObjects". They will be dealt with another day. DoCurrentAction in particular is really a shorthand for calling a set of up to 17 different procedure/functions, depending on what the lemming's currentAction is. 26 | 27 | You will see from the code that a lemming has 3 properties: 28 | 29 | 1) lemming.removed. This is a boolean variable, indicating whether the lemming is dead (TRUE) or alive (FALSE). As the field name implies, dead lemmings mean the lemming is totally gone from the level, basically doesn't exist anymore if you will. 30 | 31 | There are two ways you can choose to handle a dead lemming in your code: 32 | 33 | a) keep a list of alive lemmings, and when a lemming dies, actually remove it from the list 34 | b) use an array. Always add newly out lemmings to end of array, and when a lemming dies, simply mark the lemming as "removed". This is the approached used in the original code. 35 | 36 | Although method a) seems more natural, in terms of programming you need to be a little careful, because when nuking, you need to remember "the next lemming to be assigned exploder" across frames. You may recall that in one of the alpha versions of Oth's Lemmini, there was a bug where not all lemmings get assigned exploders if some lemming died while exploder assignment is in progress. Using method b) would help avoid this problem. (Sorry if the explanation is inadequate.) The code I present will implicit use method b) 37 | 38 | 2) lemming.currentAction 39 | 40 | This indicates the action the lemming should perform in the upcoming frame. It can have one of 18 values: 41 | 42 | WALKING 43 | SPLATTERING (the deadly landing from falling too high w/o floater skill) 44 | EXPLODING (when the lemming actually explodes and becomes confetti) 45 | FALLING 46 | JUMPING 47 | DIGGING 48 | CLIMBING 49 | HOISTING (the transition from climber to walker when climber reaches the top) 50 | BUILDING 51 | BLOCKING 52 | BASHING 53 | FLOATING 54 | MINING 55 | DROWNING 56 | EXITING 57 | FRIED (the death animation when killed by the flameblower, coal pit, etc. object) 58 | OHNOING (when the explosion countdown already reached 0, but before the actual explosion) 59 | SHRUGGING (the shrug when a builder finishes building) 60 | 61 | Again, for programming purposes, there are a number of ways you can represent the current action. Obvious the easiest is with an integer variable from 1 to 18 representing the 18 possible actions. Or an enumeration or "named constants" if your language supports it. Another possibility is bitmasking. This sounds ludicrous at first since the lemming cannot perform two actions at the same time. However, using bitmasks does help simplify certain checks like: 62 | 63 | IF lemming.currentAction not one of {FALLING,FLOATING,DROWNING,SPLATTERING,FRIED} 64 | 65 | Using a bitmask, you can use a single number to represent a set of actions, and use bitwise operators to check whether one of several bits are set or not, simplifying the way you would code, for example, the above condition. 66 | 67 | 3) lemming.explosionTimer 68 | 69 | This is a numeric value that should range from 0 to 79. 0 means either the lemming hasn't been assigned exploder yet, or has and the countdown already reached 0. Nonzero means the lemming is undergoing the explosion countdown (ie. you'd see a number of top of it). When a lemming is assigned an exploder the initial value of lemming.explosionTimer is 79. Note that this is a frame-based timer and not the number you show on top of the lemming's head, although of course the two are related. More on that another day when I get to the "UpdateExplosionTimer" function. 70 | 71 | =================== 72 | 73 | That's it for today. Sorry it isn't much, a day at a time. 74 | 75 | The next e-mail will probably talk about the code for DoCurrentAction when currentAction is WALKING. -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics002.txt: -------------------------------------------------------------------------------- 1 | This e-mail updates the code to handle walking, falling, and splattering. 2 | 3 | Concepts introduced: 4 | 5 | lemming.x, lemming.y, lemming.frameLeftdx, lemming.frameTopdy: 6 | 7 | First of all, note that in PC computer graphics, (x,y) = (0,0) means top-left corner, with increasing x going to the right, and increasing y going down. 8 | 9 | (lemming.x,lemming.y) is the pixel position the game mechanics consider the lemming to be "at". Although a more precise definition is really that it is the pixel of floor that the lemming is standing on. That's right, it's actually outside the graphics of the lemmings. 10 | 11 | lemming.frameLeftdx and lemming.frameTopdy helps you figure out where to actually display the animation graphics for the lemming based on its (lemming.x,lemming.y) position. Namely, the top-left of the animation frame's bounding box should be located at (lemming.x + lemming.frameLeftdx, lemming.y + lemming.frameTopdy). For this reason lemming.frameLeftdx and lemming.frameTopdy are always negative. Values for lemming.frameLeftdx and lemming.frameTopdy obviously depends on the current action, and are reassigned whenever the lemming switches actions. 12 | 13 | ------------ 14 | 15 | lemming.animationGraphics 16 | lemming.animationFrameIndex 17 | 18 | The first field basically points to the set of animation frames to be used for the lemming's current action. lemming.animationFrameIndex ranges from 0 to one less than the total number of frames available in lemming.animationGraphics, and points to the animation frame to use for the current frame-refresh. 19 | 20 | lemming.animationGraphics might seem a little redundant since one would expect a one-to-one correspondence between lemming.currentAction and lemming.animationGraphics, but in original Lemmings, there is a bug that leads to a mismatch situation where the animationGraphics is for shrugging and currentAction is walking. This bug is not present in any later versions of game mechanics (eg. ONML, or even CustLemm). [It will be discuss in a future e-mail when I get to skill assignments.] 21 | 22 | ---------------------------------------- 23 | 24 | lemming.fallDistanceCount 25 | 26 | It was intended to represent how many pixels the lemming has fallen, but as you see in the actual code, it doesn't do so accurately. In any case, the value of this field determines when falling transitions to floating (for floaters), and whether a landing is fatal. 27 | 28 | ---------------------------- 29 | 30 | lemming.dx 31 | 32 | This represents the facing direction of the lemming, with +1being right and -1 being left. In some rare cases the game code sets it to 0 to represent "no facing direction" such as with splattering. 33 | 34 | ------------------------- 35 | 36 | HEAD_MIN_Y = -6 37 | LEMMING_MIN_X = 0 38 | LEMMING_MAX_X = 1647 39 | LEMMING_MAX_Y = 163 40 | MAX_FALLDISTANCECOUNT = 60 41 | 42 | Some constants used throughout the game. The minx, max and maxy should be self-explanatory. The values might not be what you expected (for example with maxy being 163 even though in DOS Lemmings the last row of pixels is at y=159), but I'm simply taking them from the disassembly. I don't remember how far right a level can extend to in DOS Lemmings, but it's definitely not 1647; this is partly why a lemming will turn around at the level's left boundary, but will simply fall off its right boundary, in DOS Lemmings. HEAD_MIN_Y effectively describes the highest the "head" of the lemming can get to before the lemming is turned around for bumping into the level's top boundary (you'll see this in action in the code for DoCurrentAction(walking) for example). 43 | 44 | MAX_FALLDISTANCECOUNT is 60 for most known versions of DOS Lemmings and its variants; 63 for CustLemm and a version of Lemmings that came with Mark Tsai's solution book. 45 | 46 | ----------------------- 47 | 48 | I believe that should be enough for you to understand the attached code. Feel free to e-mail me if you have questions. -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics003.txt: -------------------------------------------------------------------------------- 1 | For this e-mail, I added the code for climbing, jumping and hoisting (transition from climber to walker). 2 | 3 | I also want to modify some code from the previous e-mail. In the previous e-mail's code for walking, we have: 4 | 5 | HEAD_MIN_Y = -6 6 | 7 | and 8 | 9 | if (lemming.y + lemming.frameTopdy <= HEAD_MIN_Y) 10 | lemming.y = HEAD_MIN_Y - 1 - lemming.frameTopdy 11 | lemming.dx = -lemming.dx 12 | call lemming.SetToWalking() 13 | end if 14 | 15 | The "correction" I'm making is: 16 | 17 | HEAD_MIN_Y = -5 18 | 19 | The if...end above in the walking code will instead be a call to a new function, and also slightly modified: 20 | 21 | lemming.CheckForLevelTopBoundary() 22 | if (lemming.y + lemming.frameTopdy < HEAD_MIN_Y) 23 | lemming.y = HEAD_MIN_Y - 2 - lemming.frameTopdy 24 | lemming.dx = -lemming.dx 25 | if (lemming.currentAction == JUMPING) 26 | call lemming.SetToWalking() 27 | end if 28 | end if 29 | end lemming.CheckForLevelTopBoundary 30 | 31 | (This function will also be called in a few other places, including in the code I added for this e-mail.) 32 | 33 | =================== 34 | 35 | One thing I forgot to mention in the previous e-mail is that, all the DoCurrentAction functions for the various lemming actions all returns TRUE/FALSE. If you recall, the UpdateLemmings() function use that boolean return value to determine whether to call CheckForInteractiveObjects(). 36 | 37 | The code for climbing and hoisting might seem a bit weird but it really is the way the actual DOS game handles those actions. 38 | 39 | Again, e-mail me back if you have questions. -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics004.txt: -------------------------------------------------------------------------------- 1 | Ok, next installment of game mechanics code. 2 | 3 | This one covers floating, building and shrugging. 4 | 5 | As mentioned before, the details for floating is captures in a table, floatParametersTable. The table is indexed by lemming.floatParametersTableIndex ranging from 0 to 15, with 16 wrapping back to 8. Recall the function lemming.SetToFloating() starts the floater off on animationFrameIndex 0, in case you're wondering why 0 is missing from the table given in the code. On subsequent frames where lemming.DoCurrentAction(FLOATING) would be called, the table is used to determine how far to fall and which animation frame to use. There are actually a few frames where the fall distance is 0 or even -1, that really is what happens in DOS Lemmings. 6 | 7 | The code for building is somewhat long (due to the number of different conditions that would terminate building before it finishes) but should be straightforward. The lemming.LayBrick() function as defined is accurate, except that I omitted the color for SetPixelAt. In DOS Lemmings it is the color from palette index 7 which is always copied from palette index 8, and the pixel should be drawn with the "no overdrawing terrain" method (so that if the build brick sticks into existing terrain, it goes "behind" the terrain so to speak). 8 | 9 | ----------------------------- 10 | 11 | I'll probably cover the processing of interactive objects next. Or perhaps I'll cover the code for entering new lemmings. 12 | 13 | I also just remembered that a while ago I hacked a version of original DOS Lemmings where time is slowed down (by about a factor of 4 I think). I attached the executable here. You should find this useful for either verifying the accuracy of your cloning of the DOS Lemmings mechanics, and also to deal with some aspect of the game that I haven't disassembled (eg. how many frames per game second, which if I counted correctly is apparently 17). -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics005.txt: -------------------------------------------------------------------------------- 1 | ccexplore wrote: 2 | I'll probably cover the processing of interactive objects next. Or perhaps I'll cover the code for entering new lemmings. 3 | Let's cover entering of new lemmings. 4 | 5 | This occurs in a separate function outside of UpdateLemmings(). EnterNewLemming() is called immediately before UpdateLemmings() and immediately after ProcessSkillAssignment(), in that order in the game's "master loop". 6 | 7 | The code given in the attached text file omits some matter which does not belong directly to EnterNewLemming(), but is nevertheless important in order for the whole thing to function. 8 | 9 | Start off with when the level shows up on the screen and call that frame #0, the first iteration of the master loop where the various functions mentioned above are first called. 10 | 11 | On frame #15, the "let's go" (the sound effect BEFORE the entrances are opened)sound effect is cued. 12 | 13 | On frame #35, the entrance starts to open (with the entrance-opening sound effect cued), and this is also the first frame where the value of the "EntrancesHaveOpened" global variable alluded to in EnterNewLemmings() becomes TRUE. EnterNewLemmings basically does nothing until that variable becomes TRUE. 14 | 15 | Then on frame #55, the background music starts to play. 16 | 17 | EnterNewLemming() has this global variable "nextLemmingCountdown". Once the first lemming came out, it is calculated using the helper function CalculateNextLemmingCountdown() for subsequent lemmings, as seen in the code. What about the very first lemming out? Well nextLemmingCountdown is initialized with 20 when the level starts. 18 | 19 | One important thing to note is that due to probably a bug in DOS Lemmings, the frames #15, #35 and #55 referenced above continues to count even when the game is paused. Since the timing of the first lemming out is indirectly tied to #35, you can gain a slight amount of time in DOS Lemmings by pausing immediately when the level shows up on the screen, and wait until you hear the entrance-open sound effect before unpausing. [Unpausing later than that point doesn't make a difference, since EnterNewLemmings() which does the actual releasing of lemmings, and various other game-mechanics functions, obviously doesn't run while paused.] Since the game clock (which determines whether you're out of time) doesn't run while pausing, this trick/bug lets you gain a small amount of time. It is I believe necessary for geoo89's 9th level on his first CustLemm set. 20 | 21 | As for the helper function CalculateNextLemmingCountdown(), as you can see it is based on the release rate. The "n < 0" line might seem a little strange since officially, release rates range from 1 to 99. But of course, the file formats allows any release rates from 0 to 255 to be stored, and I believe on of geoo89's levels in his second CustLemm set explicitly uses a release rate outside 1-99, so the code for CalculateNextLemmingCountdown() is written to take that into account. The division ("n / 2") used in the code there is integer truncated division, meaning 1 / 2 = 0, 3 / 2 = 1, etc. 22 | 23 | Finally, EnterNewLemming() refers to this "entrance" array of structures. For DOS Lemmings, this is a 4-element array, indexed by my code from 0 to 3. They are initialized by taking up to the first 4 entrances in the level as specified in the level data's interactive objects section. (An entrance is any object whose "obj id" as defined in lemmings_file_format.txt is 1.) If there are fewer than 4 entrances, the game makes copies of some of the entries to fill up all 4 slots in the array; the exact resulting entrance ordering is described later below. The entrance[].lemmingx and entrance[].lemmingy, used to set the newly entered lemming's initial position, is calculated as follows: (entrance.x + 24, entrance.y + 14), where (entrance.x,entrance.y) is the upper-left corner of the entrance object as specified in the level data. 24 | 25 | One thing omittd in EnterNewLemmings() is the definition for function lemming.Initialize(). This is because there are still some field variables I haven't talked about. But basically the function should be intuitive, things like setting lemming.isClimber and lemming.isFloater to FALSE, lemming.explosionTimer to 0, etc. Just be sure to update the function every time I introduce new lemming field variables. 26 | 27 | Finally, and this leads to the post I made today in the forums. Entrance order when there are fewer than 4 entrances: 28 | 29 | 1 entrance: A,A,A,A 30 | 2 entrances: A,B,B,A [only in original lemmings; appears to be A,B,A,B in all subsequent versions, including CustLemm I believe] 31 | 3 entrances: A,B,C,B 32 | 33 | Wow, looks like my e-mail this time is actually longer than the amount of code I added this time! Hope you can understand it all, and feel free to ask questions if you don't. 34 | -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics006.txt: -------------------------------------------------------------------------------- 1 | The new code this time is CheckForInteractiveObjects(). Since exiting, drowning and fried states are triggered from that function, I also included the SetToXXX functions and DocurrentAction functions for those three actions. 2 | 3 | The reason I need to talk about interactive objects is that to continue on with the remaining lemmings mechanics, I need to talk about the object map. So again you'll have an e-mail longer than the code I added. 4 | 5 | This reminds me: it could be useful to add to your level editor a menu option (or some way you can turn on/off) the ability to show each interactive object's trigger area, when in editor mode. I imagine it can be displayed as a dotted red rectangle area, somewhat like how steel areas are shown in LemEdit (when you set LemEdit's options to show steel area). 6 | 7 | ============= 8 | 9 | Object Map 10 | ----------------- 11 | 12 | The object map is a 2D array used by the game to track the following things: 13 | 14 | 1) trigger areas of interactive objects 15 | 2) steel areas 16 | 3) blocker fields 17 | 18 | Although you can think of other ways to keep track of these things, for accurate emulation of DOS Lemmings including some of its more obscure tricks/bugs, it is best to stick with the way DOS Lemmings does this. 19 | 20 | To start with a simplified description, imagine the level is only 6x6 pixels. A possible object map setup would be a 6 by 6 2D array map[x][y]. A rectangular trigger area with upper-left corner being (0,1), lower-right corner being (3,4), and effect value 3, can be represented in the map as follows: 21 | 22 | y\x| 0 1 2 3 4 5 23 | ---+------------ 24 | 0 | 0 0 0 0 0 0 25 | 1 | 3 3 3 3 0 0 26 | 2 | 3 3 3 3 0 0 27 | 3 | 3 3 3 3 0 0 28 | 4 | 3 3 3 3 0 0 29 | 5 | 0 0 0 0 0 0 30 | 31 | Where an effect of value of 0 means "no effect" (ie. no trigger area in that location). With such a map, if the game needs to know what effect any particular location (x,y) has, it can just look it up from the map. 32 | 33 | The object map in DOS Lemmings is similar to what I just described, with the following differences: 34 | 35 | 1) It's obviously much bigger, since the level area is far more than 6x6 36 | 37 | 2) More importantly, probably to save memory, the map only as a resolution of 4 pixels both for x and y. In other words, each element of the map, rather than representing one pixel location, actually corresponds to a 4x4 block of pixels. For example, if the above 6 by 6 map uses a 4-pixel resolution, instead of x being 0,1,2,3,4,5, you would in effect have x being the 6 ranges 0-3,4-7,8-11,12-15,16-19,20-23, and similarly for y. This is the main reason why steel areas have to line up to a multiple of 4 in their x and y coordinates, and that their widths and heights are also multiples of 4. Another interesting effect is that the field of a blocker is not positioned at a fixed offset from the blocker's own position, because the blocker's own position can be at any arbitrary pixel location, but the field, as represented in the map, has to align to multiples of 4 pixels. 38 | 39 | 3) Each element of the map not only has to hold the effect type, it also needs to identify which of the 32 interactive objects of the level data is associated with the trigger area. This is mainly for traps. Traps are different from other objects in that they are not active until activated by some lemming, and then while the trap is active, other lemmings can safely pass through it until the trap finishes animating. So it is necessary to figure out from the map not only that the effect type is "trap", but also which of the up to 32 traps it is. 40 | 41 | I'm not 100% sure of the exact size of the map, but I believe it covers an x range of 42 | -16 to 1647 and a y range of 0 to 159 (possibly higher). The DOS game doesn't really check for trigger areas that are outside the map range which is why the game may behave funny or downright crash/freeze when an out-of-range trigger area is encountered. 43 | 44 | DOS Lemmings uses the following values for effect types (the values for each object type is taken from the groundXo.dat file if you recall) 45 | 46 | 0: no effect (all map elements are initialized to this value before loading and processing the level data) 47 | 1: exit 48 | 2: force left [the "left arm" of a blocker, so to speak] 49 | 3: force right [the "right arm" of a blocker, so to speak] 50 | 4: trap 51 | 5: water (causes lemming to drown) 52 | 6: fire (causes lemming to fry; includes objects like the coal pit, the fire shooter, the spinner, etc.) 53 | 7: one-way, left 54 | 8: one-way, right 55 | 9: steel 56 | 10: blocker [the middle "body" part of a blocker, so to speak] 57 | 58 | The "blocker" effect does not directly affect a lemming's movement. It is used however by the game mechanics to prevent you from assigning blockers whose fields overlap. More on that later. 59 | 60 | There is no "entrance" effect since entrances don't work like that: new lemmings simply come out of the entrance, rather than an existing lemming being affected by the entrance. 61 | 62 | To store the index to the interactive object associated with each trigger area, DOS Lemmings uses a byte-size element with 4 bits reserved to hold the effect type and the other 4 bits for holding the index. This is why only the 0th - 15th objects defined in the level data have any effects in DOS Lemmings. 63 | 64 | This is a rather wasteful way to encode however, especially since traps (effect #4) are the only types of object that actually needs the index. So in Lemmix, I suggest a more efficient encoding: 65 | 66 | * use 0 to 15 to represent effect #4. The value is then the index itself. 67 | * use 16+DOS_effect_number to represent the corresponding effect (so "no effect" is 16, "exit" is 17, etc.) 68 | 69 | Or if you like to keep 0 to mean "no effect", you can make 1 to 16 instead be used to represent effect #4 with object index 0 thru 15. The point is that you can reserve a range of numbers to represent the effect #4 + index combination, and then use other numbers outside the range to represent the other effects that don't need the index. Notice that this representation, suitably adjusted, does not suffer from the limitation that only objects 0th-15th can be handled by the map, even when sticking to using a byte-size value for map elements. 70 | 71 | Another thing to note is that when trigger areas are kept track of through a map in the above manner, you cannot represent trigger areas that overlap, since each element in the map can only represent one effect. This is why in DOS Lemmings, when the trigger areas of two interactive objects overlap, the later object's effect is used in the overlapping area. Basically what happens is that when processing the level data, the game goes through the objects one by one in the order given in the level data, and so if object #1 and #2 are overlapping, first object #1 writes its trigger area to the map, and then object #2 writes its trigger area (and thereby overwriting part of #1's). The game happens to process steel area before interactive objects when processing level data, so object trigger areas always overwrite steel areas. 72 | 73 | This is also why blockers (or more precisely, blocker fields) are not allowed to overlap. In fact, when a blocker sets up its field, it essentially has to ovewrite the existing effect values in the map, so that the field itself can be represented in the map. This is why you can for example use a blocker's field to "disable" the effects of steel nearby, a glitch that for example allows Tricky 9 to be solved by digging thru the steel floor. The game is smart enough that when a lemming becomes a blocker, it records within the lemming object the existing effect values from the map before overwriting, so that later when the blocker is freed, it is able to restore the map back to the way it was before the blocker. This save-and-restore scheme will no longer work though if blockers can overlap, since unless you free the blockers in the reverse order in which they were assigned, the map will not be properly restored. So the game disallows overlapping blocker fields. 74 | 75 | ReadObjectMap() and WriteObjectMap() functions 76 | ---------------------------------------------- 77 | When writing code for these functions, remember that sometimes the (x,y) passed in to those functions might be out of range. Rather than imitating the DOS Lemming behavior of reading/writing whatever value happens to be outside the array (pretty much impossible to match exactly), just return the "no effect" effect type when reading, and don't write anything when writing. 78 | 79 | The (x,y) passed into those functions may be arbitrary pixel positions. Due to the 4-pixel resolution though, this means for example ReadObjectMap(1,3) is always equivalent to ReadObjectMap(0,0), and that WriteObjectMap will write to the same map element given those two locations. 80 | 81 | Trigger areas and lemmings 82 | -------------------------- 83 | lemming.CheckForInteractiveObjects() checks for the effect at (lemming.x,lemming.y). But recall that this location basically corresponds to the ground that the lemming is walking on (ie. underneath the lemming's feet!), so in effect, it is really checking what trigger area the lemming is standing on. For example, the trigger area for exits, when the exit is properly located, the trigger area is actually buried just under the ground, rather than above ground. 84 | 85 | The first two lines in the code given for lemming.CheckForInteractiveObjects() are 86 | 87 | lemming.objectBelow = ReadObjectMap(lemming.x,lemming.y) 88 | lemming.objectInFront = ReadObjectMap(lemming.x + 8*lemming.dx, lemming.y - 8) 89 | 90 | You might wonder about lemming.objectInFront, since the value isn't even used inside the function, or why lemming.objectBelow needs to be stored with the lemming since it is already acted upon in lemming.CheckForInteractiveObjects. 91 | 92 | The reason is that there are other parts of lemmings mechanics which uses these stored values, rather than reading from the object map again. Skills assignment for example. For more accurate emulation with DOS Lemmings you would need to do the same thing as in the given code. 93 | -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics007.txt: -------------------------------------------------------------------------------- 1 | This installment adds code for blocking. 2 | 3 | It is very straightforward and you should have no problem understanding it, especially since I already explained the jist of how blocker fields are set up in the object map, and how blockers save the map and restore it back when the blocker is freed. 4 | 5 | As can be seen in the code, the blocker field is a 3 by 3 area in the object map, which due to the 4-pixel resolution means a 12x12 pixels area: 6 | 7 | x range is lemming.x - 4, lemming.x, lemming.x + 4 8 | y range is lemming.y - 6, lemming.y - 2, lemming.y + 2 9 | 10 | Given that (lemming.x,lemming.y) is a position that at the horizontal center and vertical bottom of the lemming, the numbers given basically means the blocker field is centered around the blocker itself, ignoring the alignment aspect imposed by the object map. 11 | 12 | So there are 9 object map locations to be saved and restored. I named these lemming fields based on the offset coordinates, so that the map location at (lemming.x - 4, lemming.y - 6) is saved to and restored from lemming.savedMap_xm4ym6 (standing for "x minus 4 y minus 6") for example. 13 | 14 | DoCurrentAction(BLOCKING) only needs lemming.RestoreMap(), but since it's mostly cut-and-paste, the code added this time also includes lemming.SaveMap(), lemming.SetBlockerField(), and lemming.CheckForOverlappingField(). And also lemming.SetToBlocking(). Obviously those functions will get used when we get to ProcessSkillAssignments in the case of assigning a lemming a blocker. 15 | 16 | Also note that I introduce here lemming.isBlocking. The reason I need that is because the blocker field is active not only when the lemming is BLOCKING, but if the blocker is assigned an exploder, it would go through OHNOING and the field is removed only after reaching EXPLODING. So in order to remember that the lemming has set up a blocker field, you can't rely on lemming.currentAction, so I introduced boolean field variable lemming.isBlocking. It should be initialized to FALSE, and I have it set to TRUE inside SetToBlocking, and it is set back to FALSE anytime you want to call lemming.RestoreMap() to remove the field [indeed, I probably could've put "lemming.isBlocking = FALSE" inside lemming.RestoreMap()]. -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics008.txt: -------------------------------------------------------------------------------- 1 | This installment adds code for basically handling explosion-related matters, which also means nuking can now be properly handled. 2 | 3 | It has taken this long to get here because I need to first introduce the code for restoring the map, since the blocker field can also be removed when the lemming is EXPLODING, in addition to the blocker being freed in the manner handled by DoCurrentAction(BLOCKING). 4 | 5 | The code is pretty straightforward and should require little to no explanation. 6 | 7 | The function ApplyExplosionMask is not given code but there should be enough information to handle it. The mask is from main.dat, of dimensions 16x22, and when drawn the upper left corner is at (lemming.x - 8, lemming.y - 14) as specified in the code given. 8 | 9 | One other thing to note is that although DoCurrentAction(EXPLODING) increments lemming.animationFrameIndex like almost all the other actions, it's clear that in the actual game there is just one animation frame, the explosion graphic (not the confetti particles part), and that graphics is displayed exactly for one frame only. I believe it is displayed exactly only when lemming.animationFrameIndex = 0, which happens on the frame where lemming.SetToExploding() is called. This means as soon as you call lemming.DoCurrentAction(EXPLODING) for a lemming (which observe from the code can never be called on the same frame where lemming.SetToExploding() is called), the lemming should become invisible and you should start the confetti particles. These aspects are not shown in the code given. 10 | 11 | DoCurrentAction(EXPLODING) waits for lemming.animationFrameIndex to reach 52 post-increment before the lemming is removed, despite as explained above that the lemming is invisible after the explosion started. The reason of course is to stall the game from fading out of the level too soon when nuking. It means for example that even after the last lemming explodes, there are 52 frames before the number of lemmings remaining finally drop to 0, giving time for the confetti display to continue for that long. 12 | -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics009.txt: -------------------------------------------------------------------------------- 1 | The new code introduced this time should be pretty straightforward. 2 | 3 | Note that although the digger's animation frame in DOS Lemmings is 16x14, in SetToDigging lemming.frameTopdy is set to -12 rather than -14. If you look at the actual graphics for digging you'll find -12 to be correct since the lemming's feet is not at the bottom of the bounding box, unlike other animation graphics. 4 | 5 | There is a boolean field variable lemming.isNewDigger, assigned TRUE in SetToDigging. This is used so that on the very first call to DoCurrentAction(DIGGING) for the newly assigned digger, it goes through a few more calls to remove terrain pixels, as seen in the code. Just following the DOS behavior. -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics010.txt: -------------------------------------------------------------------------------- 1 | After this, just one more skill to go! 2 | 3 | The code omitted definition for lemming.ApplyBashingMask(). The mask graphics in DOS Lemmings have the same dimensions (16x10) as the animation frames for bashing, and there are 4 masks, with left/right versions. 4 | 5 | So: 6 | 7 | 1) upper-left of the 16x10 mask graphics should be located at (lemming.x + lemming.frameLeftdx, lemming.y + lemming.frameTopdy) 8 | 9 | 2) if we index the 4 masks as 0 - 3, use the mask indexed lemming.animationFrameIndex - 2 10 | 11 | 3) don't forget to check lemming.dx to pick the left/right version -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics011.txt: -------------------------------------------------------------------------------- 1 | Minor but important correction (with modified code attached). 2 | 3 | You'd notice from the code for DoCurrentAction(BASHING) that while lemming.animationFrameIndex ranges from 0 to 31, two bash strokes actually occur during that cycle, once from 0 to 15 and again from 16-31. 4 | 5 | With that in mind, I mentioned in the previous e-mail to index the bashing mask via lemming.animationFrameIndex - 2. This isn't correct however since it doesn't work when lemming.animationFrameIndex in the 16-31 range. 6 | 7 | So I modified the code to pass to lemming.ApplyBashingMask() the correctly computed index. 8 | 9 | I also want to point out that in DoCurrentAction(BASHING), there's a line that checks 10 | 11 | if (lemming.animationFrameIndex == 5) 12 | 13 | This is *correct* (or rather, it is what DOS Lemmings do); it checks the real animationFrameIndex rather than the "normalized" local variable "index". The implication of this is that the game only checks for "is there anything more in front to bash?" every other bash stroke. This is why in DOS Lemmings (and Amiga, SNES, Genesis/Megadrive, though not the Mac), a basher whose bashing is not terminated prematurely by steel, falling etc., will always make an odd number of bash strokes. 14 | 15 | ccexplore wrote: 16 | After this, just one more skill to go! 17 | 18 | The code omitted definition for lemming.ApplyBashingMask(). The mask graphics in DOS Lemmings have the same dimensions (16x10) as the animation frames for bashing, and there are 4 masks, with left/right versions. 19 | 20 | So: 21 | 22 | 1) upper-left of the 16x10 mask graphics should be located at (lemming.x + lemming.frameLeftdx, lemming.y + lemming.frameTopdy) 23 | 24 | 2) if we index the 4 masks as 0 - 3, use the mask indexed lemming.animationFrameIndex - 2 25 | 26 | 3) don't forget to check lemming.dx to pick the left/right version -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics012.txt: -------------------------------------------------------------------------------- 1 | This e-mail adds the code for the final lemmings action, mining. 2 | 3 | I didn't include code for lemming.ApplyMinerMask(x,y), but it's similar in style to bashing. There are 2 16x13 mining masks and separate left/right versions, so be sure to check lemming.dx within lemming.ApplyMinerMask(). If we index the 2 masks as 0 and 1, then the mask to use would be indexed by lemming.animationFrameIndex - 1. The (x,y) parameters passed into the function specifies the upper left corner location for the 16x13 mask. 4 | 5 | The check for steel and one-way walls, you see that the (belowObj == ONE_WAY_RIGHT) seems to be missing a check for the lemming's facing direction. That's correct as is, and it is simply emulating the same bug in DOS Lemmings, which results in a one-way wall pointing right being unminable in either direction. 6 | 7 | Well, we're close to done. The final thing to cover is ProcessSkillAssignments, next e-mail. -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics013.txt: -------------------------------------------------------------------------------- 1 | This is the final installment of code, for handling skill assignment the way it is done in DOS Lemmings. 2 | 3 | The code added this time consists of function ProcessSkillAssignment() and its 8 helper functions, an AssignSkill() function for each of 8 possible skills. 4 | 5 | ProcessSkillAssignment() references the global variables mousex and mousey. Unfortunately, it's really tough at this point from the disassembly to tell which pixel of the cursor it is considering "mousex" and "mousey" to be located at, and in fact in the actual disassembly mousex and mousey reads more like "var1 + var2 - 4" etc. 6 | 7 | So instead, I'm just using mousex and mousey in the code, but also attached in this e-mail a picture of the blocker animation frame, and the mouse being at the upper-left-most and lower-right-most positions that still causes the blocker to be highlighted. With those pictures and my given code, I worked out the pixel location of the cursor that corresponds to (mousex, mousey) and mark it red in the picture. 8 | 9 | AssignSkill has some code that checks if lemming.currentAction is SHRUGGING and if so, changes it to WALKING. This is to emulate a bug in original DOS Lemmings (which only holds for original DOS Lemmings and not any later versions). Note that it only changes the currentAction, but *not* the animation frame graphics or the animationFrameIndex. The net effect is that the lemming acts in "WALKING" mode but the graphics stills show him shrugging, until the game mechanics transitions him out of WALKING into something else (eg. FALLING, JUMPING, etc.). 10 | 11 | ------------------------ 12 | 13 | Phew! That's it in terms of coding for now. However, there will probably be one last e-mail after this, on miscellaneous stuff that isn't quite code. Things like: 14 | 15 | 1) other glitches that needs emulating, such as the "nuke glitch" causing percentage saved to be incorrectly calculated 16 | 17 | 2) what I believe the frame rate is, and how many frames per one game second 18 | 19 | 3) comparison of game mechanics across different versions of DOS lemming games 20 | 21 | etc. 22 | 23 | How's your vacation? -------------------------------------------------------------------------------- /doc/mechanics/@Lemmings_mechanics014.txt: -------------------------------------------------------------------------------- 1 | Sadly, another problem with game mechanics in Lemmix: 2 | I was trying out the "across the ceiling" solution for Mayhem 12 ("The Far Side"). 3 | Turns out this is my fault as well. Essentially, I never bothered to disassemble DOS Lemming's "HasPixelAt" function since I figured it is obvious from the way the function is used what it does. 4 | 5 | But seeing this discrepency and after some testing with DOS Lemmings, I decided to disassemble the respective functions in DOS Lemmings. As it turns out, there is a minor thing tugged into that function which has an effect on levels like this. 6 | 7 | The full story is unfortunately somewhat complicated so bear with me...... 8 | 9 | DOS Lemmings actually have two versions of "HasPixelAt", and they behave differently with respect to this minor thing. 10 | 11 | 1) Call this version "HasPixelAtA(x,y)". This version really does check whether there's a pixel at (x,y) no matter what x and y are. Due to clipping of graphics, this means for pixels outside the visible level area, HasPixelAtA(x,y) should always return FALSE. (Although, this reminds me, I guess I should disassemble further to firmly establish DOS Lemming's clipping boundaries for graphics......) 12 | 13 | 2) Call the second version "HasPixelAtB(x,y)". This version is similar to the A version, except it checks the value of y. If y is less than 0 then it proceeds pretending y equals 0. In other words, this B version is effectively equivalent to the following: 14 | 15 | if (y < 0) 16 | return call HasPixelAtA(x,0) 17 | else 18 | return call HasPixelAtA(x,y) 19 | end if 20 | 21 | that is, y is "clipped" to be 0 or above. 22 | 23 | ccexplorecode so far pretends every call is HasPixelAtA, but this is incorrect. 24 | 25 | ------------------ 26 | 27 | It gets worse. In the actual DOS Lemmings game mechanics, the HasPixelAt functions ultimately returns pointers (memory addresses) to video memory, and it is outside the call where the game does the actual checking of pixels through the pointer. One effect is that because it is just pointers, sometimes the game will just add or subtract to the pointer after a HasPixelAt call, in order to read the pixel information at a related location without calling HasPixelAt again. But notice that only the real call to HasPixelAt is clipped in y. The pointer calculations afterwards basically does no further clipping. 28 | 29 | So for example, say the program did something along the lines of: 30 | 31 | pointerXY = GetPixelAtB(x,y) 32 | check pointerXY - 9 * pixelsperline 33 | 34 | In ccexplorecode I had been simplifying stuff like this to "call HasPixelAtB(x,y - 9)". However, given that we now know GetPixelAtB(x,y) "clips" y to 0, the two lines of code above is really equivalent to another version of HasPixelAtB where instead of clipping y values to be >= 0, we are effectively clipping to >= -9: the first call does clip y to >= 0, but then pointer calculation means we are ultimately checking a video memory location whose y may be as low as 0 - 9 = -9. 35 | 36 | So, to be perfectly compatible taking account of all this, this is how I will modify ccexplorecode: 37 | 38 | 1) HasPixelAt_NoClipY is now the name of the "raw", no-clipping version of HasPixelAt. (That is, "HasPixelAtA" in the above discussion, and effectively the version of HasPixelAt we have in the current version of Lemmix.) 39 | 40 | 2) I add a new function, HasPixelAt_ClipY(x,y,miny), defined as follows: 41 | 42 | if (y < miny) 43 | return call HasPixelAt_NoClipY(x, miny) 44 | else 45 | return call HasPixelAt_NoClipY(x, y) 46 | end if 47 | 48 | So for example, HasPixelAt_ClipY(x,y,0) is equivalent to the "HasPixelAtB" in the above discussion. 49 | 50 | I then change all occurrences of HasPixelAt in ccexplorecode to one of the two versions, with different values for miny, to match the behavior to the actual game. The result is attached. 51 | 52 | Actually, since I still want to go through your DelphiCode, I'm thinking that maybe I will do the changes for you as I go. So for now please feel free to work on other areas of Lemmix. 53 | -------------------------------------------------------------------------------- /doc/mechanics/Lemmings_mechanics001.txt: -------------------------------------------------------------------------------- 1 | UpdateLemmings() 2 | 3 | for each lemming 4 | 5 | if NOT lemming.removed then 6 | if (lemming.explosionTimer != 0) 7 | CountdownReached0 = call lemming.UpdateExplosionTimer() 8 | if (CountdownReached0) 9 | skip to next lemming 10 | end if 11 | end if 12 | 13 | HandleInteractiveObjects = call lemming.DoCurrentAction() 14 | 15 | if (HandleInteractiveObjects) 16 | call lemming.CheckForInteractiveObjects() 17 | end if 18 | end if 19 | 20 | next lemming 21 | 22 | if (UserSetNuking AND ExploderAssignInProgress) 23 | while (Index_LemmingToBeNuked <= NumberOfLemmingsOut AND 24 | lemming[Index_LemmingToBeNuked].removed) 25 | Index_LemmingToBeNuked = Index_LemmingToBeNuked + 1 26 | end while 27 | 28 | if (Index_LemmingToBeNuked > NumberOfLemmingsOut) 29 | ExploderAssignInProgress = FALSE 30 | else 31 | if (lemming[Index_LemmingToBeNuked].explosionTimer == 0 AND 32 | lemming[Index_LemmingToBeNuked].currentAction != SPLATTERING AND 33 | lemming[Index_LemmingToBeNuked].currentAction != EXPLODING) 34 | lemming[Index_LemmingToBeNuked].explosionTimer = 79 35 | end if 36 | end if 37 | end if 38 | 39 | end UpdateLemmings 40 | 41 | -------------------------------------------------------------------------------- /doc/mechanics/Lemmings_mechanics002.txt: -------------------------------------------------------------------------------- 1 | UpdateLemmings() 2 | 3 | for each lemming 4 | 5 | if NOT lemming.removed then 6 | if (lemming.explosionTimer != 0) 7 | CountdownReached0 = call lemming.UpdateExplosionTimer() 8 | if (CountdownReached0) 9 | skip to next lemming 10 | end if 11 | end if 12 | 13 | HandleInteractiveObjects = call lemming.DoCurrentAction() 14 | 15 | if (HandleInteractiveObjects) 16 | call lemming.CheckForInteractiveObjects() 17 | end if 18 | end if 19 | 20 | next lemming 21 | 22 | if (UserSetNuking AND ExploderAssignInProgress) 23 | while (Index_LemmingToBeNuked <= NumberOfLemmingsOut AND 24 | lemming[Index_LemmingToBeNuked].removed) 25 | Index_LemmingToBeNuked = Index_LemmingToBeNuked + 1 26 | end while 27 | 28 | if (Index_LemmingToBeNuked > NumberOfLemmingsOut) 29 | ExploderAssignInProgress = FALSE 30 | else 31 | if (lemming[Index_LemmingToBeNuked].explosionTimer == 0 AND 32 | lemming[Index_LemmingToBeNuked].currentAction != SPLATTERING AND 33 | lemming[Index_LemmingToBeNuked].currentAction != EXPLODING) 34 | lemming[Index_LemmingToBeNuked].explosionTimer = 79 35 | end if 36 | end if 37 | end if 38 | 39 | end UpdateLemmings 40 | 41 | HEAD_MIN_Y = -6 42 | LEMMING_MIN_X = 0 43 | LEMMING_MAX_X = 1647 44 | LEMMING_MAX_Y = 163 45 | 46 | MAX_FALLDISTANCECOUNT = 60 47 | 48 | lemming.DoCurrentAction(when action=WALKING) 49 | 50 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 51 | if (lemming.animationFrameIndex >= 8) 52 | lemming.animationFrameIndex = lemming.animationFrameIndex - 8 53 | end if 54 | 55 | lemming.x = lemming.x + lemming.dx 56 | if (lemming.x >= LEMMING_MIN_X AND lemming.x <= LEMMING_MAX_X) 57 | if (call HasPixelAt(lemming.x,lemming.y) == TRUE) 58 | // walk, jump, climb, or turn around 59 | dy = 0 60 | newy = lemming.y 61 | while (dy <= 6 and call HasPixelAt(lemming.x,newy - 1) == TRUE) 62 | dy = dy + 1 63 | newy = newy - 1 64 | end while 65 | 66 | if (dy > 6) 67 | if (lemming.isClimber) 68 | call lemming.SetToClimbing() 69 | else 70 | lemming.dx = -lemming.dx 71 | end if 72 | return TRUE 73 | else 74 | if (dy >= 3) 75 | call lemming.SetToJumping() 76 | newy = lemming.y - 2 77 | end if 78 | 79 | lemming.y = newy 80 | if (lemming.y + lemming.frameTopdy <= HEAD_MIN_Y) 81 | lemming.y = HEAD_MIN_Y - 1 - lemming.frameTopdy 82 | lemming.dx = -lemming.dx 83 | call lemming.SetToWalking() 84 | end if 85 | return TRUE 86 | end if 87 | 88 | else 89 | // walk or fall downwards 90 | dy = 1 91 | while (dy <= 3) 92 | lemming.y = lemming.y + 1 93 | if (call HasPixelAt(lemming.x,lemming.y) == TRUE) 94 | exit while loop 95 | end if 96 | dy = dy + 1 97 | end while 98 | 99 | if (dy > 3) 100 | // in this case, lemming becomes a faller 101 | lemming.y = lemming.y + 1 102 | call lemming.SetToFalling() 103 | end if 104 | 105 | if (lemming.y > LEMMING_MAX_Y) 106 | lemming.removed = TRUE 107 | return false 108 | else 109 | return TRUE 110 | end if 111 | end if 112 | else 113 | lemming.dx = -lemming.dx 114 | return TRUE 115 | end if 116 | 117 | end lemming.DoCurrentAction(WALKING) 118 | 119 | 120 | lemming.DoCurrentAction(when action=FALLING) 121 | 122 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 123 | if (lemming.animationFrameIndex >= 4) 124 | lemming.animationFrameIndex = lemming.animationFrameIndex - 4 125 | end if 126 | 127 | if (lemming.fallDistanceCount > 16 AND lemming.isFloater) 128 | call lemming.SetToFloating() 129 | return TRUE 130 | else 131 | dy = 0 132 | while (dy < 3 AND call HasPixelAt(lemming.x,lemming.y) == FALSE) 133 | dy = dy + 1 134 | lemming.y = lemming.y + 1 135 | if (lemming.y > LEMMING_MAX_Y) 136 | lemming.removed = TRUE 137 | return false 138 | end if 139 | end while 140 | 141 | if (dy == 3) 142 | lemming.fallDistanceCount = lemming.fallDistanceCount + 3 143 | return true 144 | else 145 | if (lemming.fallDistanceCount <= MAX_FALLDISTANCECOUNT) 146 | call lemming.SetToSplattering() 147 | return true 148 | else 149 | call lemming.SetToWalking() 150 | return true 151 | end if 152 | end if 153 | end 154 | 155 | end lemming.DoCurrentAction(FALLING) 156 | 157 | 158 | lemming.DoCurrentAction(when action=SPLATTERING) 159 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 160 | if (lemming.animationFrameIndex == 16) 161 | lemming.removed = TRUE 162 | end if 163 | return false 164 | end lemming.DoCurrentAction(SPLATTERING) 165 | 166 | 167 | lemming.SetToWalking() 168 | lemming.currentAction = WALKING 169 | lemming.animationGraphics = 170 | lemming.animationFrameIndex = 0 171 | lemming.frameLeftdx = -8 172 | lemming.frameTopdy = -10 173 | end lemming.SetToWalking 174 | 175 | lemming.SetToFalling() 176 | lemming.currentAction = FALLING 177 | lemming.animationGraphics = 178 | lemming.animationFrameIndex = 0 179 | lemming.frameLeftdx = -8 180 | lemming.frameTopdy = -10 181 | lemming.fallDistanceCount = 3 182 | end lemming.SetToFalling 183 | 184 | lemming.SetToClimbing() 185 | lemming.currentAction = CLIMBING 186 | lemming.animationGraphics = 187 | lemming.animationFrameIndex = 0 188 | lemming.frameLeftdx = -8 189 | lemming.frameTopdy = -12 190 | end lemming.SetToClimbing 191 | 192 | lemming.SetToJumping() 193 | lemming.currentAction = JUMPING 194 | lemming.animationGraphics = 195 | lemming.animationFrameIndex = 0 196 | lemming.frameLeftdx = -8 197 | lemming.frameTopdy = -10 198 | end lemming.SetToJumping 199 | 200 | lemming.SetToFloating() 201 | lemming.currentAction = FLOATING 202 | lemming.animationGraphics = 203 | lemming.animationFrameIndex = 0 204 | lemming.floatParametersTableIndex = 0 205 | lemming.frameLeftdx = -8 206 | lemming.frameTopdy = -16 207 | end lemming.SetToFloating 208 | 209 | lemming.SetToSplattering() 210 | lemming.currentAction = SPLATTERING 211 | lemming.animationGraphics = 212 | lemming.animationFrameIndex = 0 213 | lemming.frameLeftdx = -8 214 | lemming.frameTopdy = -10 215 | 216 | lemming.dx = 0 217 | lemming.explosionTimer = 0 218 | 219 | call CueSoundEffect(SFX_SPLAT) 220 | end lemming.SetToSplattering 221 | 222 | -------------------------------------------------------------------------------- /doc/mechanics/Lemmings_mechanics003.txt: -------------------------------------------------------------------------------- 1 | UpdateLemmings() 2 | 3 | for each lemming 4 | 5 | if NOT lemming.removed then 6 | if (lemming.explosionTimer != 0) 7 | CountdownReached0 = call lemming.UpdateExplosionTimer() 8 | if (CountdownReached0) 9 | skip to next lemming 10 | end if 11 | end if 12 | 13 | HandleInteractiveObjects = call lemming.DoCurrentAction() 14 | 15 | if (HandleInteractiveObjects) 16 | call lemming.CheckForInteractiveObjects() 17 | end if 18 | end if 19 | 20 | next lemming 21 | 22 | if (UserSetNuking AND ExploderAssignInProgress) 23 | while (Index_LemmingToBeNuked <= NumberOfLemmingsOut AND 24 | lemming[Index_LemmingToBeNuked].removed) 25 | Index_LemmingToBeNuked = Index_LemmingToBeNuked + 1 26 | end while 27 | 28 | if (Index_LemmingToBeNuked > NumberOfLemmingsOut) 29 | ExploderAssignInProgress = FALSE 30 | else 31 | if (lemming[Index_LemmingToBeNuked].explosionTimer == 0 AND 32 | lemming[Index_LemmingToBeNuked].currentAction != SPLATTERING AND 33 | lemming[Index_LemmingToBeNuked].currentAction != EXPLODING) 34 | lemming[Index_LemmingToBeNuked].explosionTimer = 79 35 | end if 36 | end if 37 | end if 38 | 39 | end UpdateLemmings 40 | 41 | HEAD_MIN_Y = -5 42 | LEMMING_MIN_X = 0 43 | LEMMING_MAX_X = 1647 44 | LEMMING_MAX_Y = 163 45 | 46 | MAX_FALLDISTANCECOUNT = 60 47 | 48 | lemming.DoCurrentAction(when action=WALKING) 49 | 50 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 51 | if (lemming.animationFrameIndex >= 8) 52 | lemming.animationFrameIndex = lemming.animationFrameIndex - 8 53 | end if 54 | 55 | lemming.x = lemming.x + lemming.dx 56 | if (lemming.x >= LEMMING_MIN_X AND lemming.x <= LEMMING_MAX_X) 57 | if (call HasPixelAt(lemming.x,lemming.y) == TRUE) 58 | // walk, jump, climb, or turn around 59 | dy = 0 60 | newy = lemming.y 61 | while (dy <= 6 and call HasPixelAt(lemming.x,newy - 1) == TRUE) 62 | dy = dy + 1 63 | newy = newy - 1 64 | end while 65 | 66 | if (dy > 6) 67 | if (lemming.isClimber) 68 | call lemming.SetToClimbing() 69 | else 70 | lemming.dx = -lemming.dx 71 | end if 72 | return TRUE 73 | else 74 | if (dy >= 3) 75 | call lemming.SetToJumping() 76 | newy = lemming.y - 2 77 | end if 78 | 79 | lemming.y = newy 80 | call lemming.CheckForLevelTopBoundary() 81 | return TRUE 82 | end if 83 | 84 | else 85 | // walk or fall downwards 86 | dy = 1 87 | while (dy <= 3) 88 | lemming.y = lemming.y + 1 89 | if (call HasPixelAt(lemming.x,lemming.y) == TRUE) 90 | exit while loop 91 | end if 92 | dy = dy + 1 93 | end while 94 | 95 | if (dy > 3) 96 | // in this case, lemming becomes a faller 97 | lemming.y = lemming.y + 1 98 | call lemming.SetToFalling() 99 | end if 100 | 101 | if (lemming.y > LEMMING_MAX_Y) 102 | lemming.removed = TRUE 103 | return false 104 | else 105 | return TRUE 106 | end if 107 | end if 108 | else 109 | lemming.dx = -lemming.dx 110 | return TRUE 111 | end if 112 | 113 | end lemming.DoCurrentAction(WALKING) 114 | 115 | 116 | lemming.DoCurrentAction(when action=FALLING) 117 | 118 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 119 | if (lemming.animationFrameIndex >= 4) 120 | lemming.animationFrameIndex = lemming.animationFrameIndex - 4 121 | end if 122 | 123 | if (lemming.fallDistanceCount > 16 AND lemming.isFloater) 124 | call lemming.SetToFloating() 125 | return TRUE 126 | else 127 | dy = 0 128 | while (dy < 3 AND call HasPixelAt(lemming.x,lemming.y) == FALSE) 129 | dy = dy + 1 130 | lemming.y = lemming.y + 1 131 | if (lemming.y > LEMMING_MAX_Y) 132 | lemming.removed = TRUE 133 | return false 134 | end if 135 | end while 136 | 137 | if (dy == 3) 138 | lemming.fallDistanceCount = lemming.fallDistanceCount + 3 139 | return true 140 | else 141 | if (lemming.fallDistanceCount <= MAX_FALLDISTANCECOUNT) 142 | call lemming.SetToSplattering() 143 | return true 144 | else 145 | call lemming.SetToWalking() 146 | return true 147 | end if 148 | end if 149 | end 150 | 151 | end lemming.DoCurrentAction(FALLING) 152 | 153 | 154 | lemming.DoCurrentAction(when action=SPLATTERING) 155 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 156 | if (lemming.animationFrameIndex == 16) 157 | lemming.removed = TRUE 158 | end if 159 | return false 160 | end lemming.DoCurrentAction(SPLATTERING) 161 | 162 | 163 | lemming.SetToWalking() 164 | lemming.currentAction = WALKING 165 | lemming.animationGraphics = 166 | lemming.animationFrameIndex = 0 167 | lemming.frameLeftdx = -8 168 | lemming.frameTopdy = -10 169 | end lemming.SetToWalking 170 | 171 | lemming.SetToFalling() 172 | lemming.currentAction = FALLING 173 | lemming.animationGraphics = 174 | lemming.animationFrameIndex = 0 175 | lemming.frameLeftdx = -8 176 | lemming.frameTopdy = -10 177 | lemming.fallDistanceCount = 3 178 | end lemming.SetToFalling 179 | 180 | lemming.SetToClimbing() 181 | lemming.currentAction = CLIMBING 182 | lemming.animationGraphics = 183 | lemming.animationFrameIndex = 0 184 | lemming.frameLeftdx = -8 185 | lemming.frameTopdy = -12 186 | end lemming.SetToClimbing 187 | 188 | lemming.SetToJumping() 189 | lemming.currentAction = JUMPING 190 | lemming.animationGraphics = 191 | lemming.animationFrameIndex = 0 192 | lemming.frameLeftdx = -8 193 | lemming.frameTopdy = -10 194 | end lemming.SetToJumping 195 | 196 | lemming.SetToFloating() 197 | lemming.currentAction = FLOATING 198 | lemming.animationGraphics = 199 | lemming.animationFrameIndex = 0 200 | lemming.floatParametersTableIndex = 0 201 | lemming.frameLeftdx = -8 202 | lemming.frameTopdy = -16 203 | end lemming.SetToFloating 204 | 205 | lemming.SetToSplattering() 206 | lemming.currentAction = SPLATTERING 207 | lemming.animationGraphics = 208 | lemming.animationFrameIndex = 0 209 | lemming.frameLeftdx = -8 210 | lemming.frameTopdy = -10 211 | 212 | lemming.dx = 0 213 | lemming.explosionTimer = 0 214 | 215 | call CueSoundEffect(SFX_SPLAT) 216 | end lemming.SetToSplattering 217 | 218 | 219 | lemming.CheckForLevelTopBoundary() 220 | if (lemming.y + lemming.frameTopdy < HEAD_MIN_Y) 221 | lemming.y = HEAD_MIN_Y - 2 - lemming.frameTopdy 222 | lemming.dx = -lemming.dx 223 | if (lemming.currentAction == JUMPING) 224 | call lemming.SetToWalking() 225 | end if 226 | end if 227 | end lemming.CheckForLevelTopBoundary 228 | 229 | lemming.DoCurrentAction(when action=JUMPING) 230 | dy = 0 231 | while (dy < 2 AND call HasPixelAt(lemming.x, lemming.y - 1) == TRUE) 232 | dy = dy + 1 233 | lemming.y = lemming.y - 1 234 | end while 235 | 236 | if (dy < 2) 237 | call lemming.SetToWalking() 238 | end if 239 | 240 | call lemming.CheckForLevelTopBoundary() 241 | return TRUE 242 | end lemming.DoCurrentAction(JUMPING) 243 | 244 | lemming.DoCurrentAction(when action=CLIMBING) 245 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 246 | if (lemming.animationFrameIndex >= 8) 247 | lemming.animationFrameIndex = lemming.animationFrameIndex - 8 248 | end if 249 | 250 | if (lemming.animationFrameIndex <= 3) 251 | // check if we approached the top 252 | if (call HasPixelAt(lemming.x, lemming.y - 7 - lemming.animationFrameIndex) == FALSE) 253 | lemming.y = lemming.y - lemming.animationFrameIndex + 2 254 | call lemming.SetToHoisting() 255 | call lemming.CheckForLevelTopBoundary() 256 | end if 257 | return TRUE 258 | else 259 | lemming.y = lemming.y - 1 260 | // check for overhang or level top boundary 261 | if (lemming.y + lemming.frameTopdy < HEAD_MIN_Y 262 | OR call HasPixelAt(lemming.x - lemming.dx, lemming.y - 8) == TRUE) 263 | call lemming.SetToFalling() 264 | lemming.dx = -lemming.dx 265 | lemming.x = lemming.x + lemming.dx + lemming.dx 266 | end if 267 | return TRUE 268 | end if 269 | end lemming.DoCurrentAction(CLIMBING) 270 | 271 | lemming.DoCurrentAction(when action=HOISTING) 272 | lemming.animationFrameIndex = lemming.animationFrameIndex + 1 273 | if (lemming.animationFrameIndex <= 4) 274 | lemming.y = lemming.y - 2 275 | call lemming.CheckForLevelTopBoundary() 276 | return TRUE 277 | else if (lemming.animationFrameIndex == 8) 278 | call lemming.SetToWalking() 279 | call lemming.CheckForLevelTopBoundary() 280 | return TRUE 281 | else 282 | return FALSE 283 | end if 284 | end lemming.DoCurrentAction(HOISTING) 285 | 286 | lemming.SetToHoisting() 287 | lemming.currentAction = HOISTING 288 | lemming.animationGraphics = 289 | lemming.animationFrameIndex = 0 290 | lemming.frameLeftdx = -8 291 | lemming.frameTopdy = -12 292 | end lemming.SetToHoisting 293 | 294 | 295 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esoteric-programmer/lemmings_3ds/7625315ef42d28d5d584eed9012a2cebe5695474/icon.png -------------------------------------------------------------------------------- /include/2p_button.h: -------------------------------------------------------------------------------- 1 | #ifndef BUTTON2P_H 2 | #define BUTTON2P_H 3 | #include <3ds.h> 4 | // main menu button 2 player that replaces "new game" button (orig and ohno) 5 | // 93 x 27 6 | extern const u8 main_menu_button_2p[]; 7 | extern const u8 main_menu_xmas_button_2p[]; 8 | #endif 9 | -------------------------------------------------------------------------------- /include/audio.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_H 2 | #define AUDIO_H 3 | #include <3ds.h> 4 | 5 | void init_audio(); 6 | void update_volume(); 7 | int audio_error(); 8 | int import_audio(u8 game); 9 | int is_custom_sound(u8 sound); 10 | void next_music(); 11 | void prepare_music(u8 game, u8 lvl); 12 | void play_music(); 13 | void stop_audio(); 14 | void play_sound(u8 sound); 15 | void update_audio(); 16 | void deinit_audio(); 17 | #endif 18 | -------------------------------------------------------------------------------- /include/control.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROL_H 2 | #define CONTROL_H 3 | #include <3ds.h> 4 | #include "level.h" 5 | 6 | #define ACTION_MOVE_CURSOR_RIGHT (1<< 0) 7 | #define ACTION_MOVE_CURSOR_LEFT (1<< 1) 8 | #define ACTION_MOVE_CURSOR_UP (1<< 2) 9 | #define ACTION_MOVE_CURSOR_DOWN (1<< 3) 10 | #define ACTION_CURSOR_CLICK (1<< 4) 11 | #define ACTION_CURSOR_HOLD (1<< 5) 12 | #define ACTION_PAUSE (1<< 6) 13 | #define ACTION_NEXT_SKILL (1<< 7) 14 | #define ACTION_PREV_SKILL (1<< 8) 15 | #define ACTION_INC_RATE (1<< 9) 16 | #define ACTION_DEC_RATE (1<<10) 17 | #define ACTION_SPEED_UP (1<<11) 18 | #define ACTION_SCROLL_RIGHT (1<<12) 19 | #define ACTION_SCROLL_LEFT (1<<13) 20 | #define ACTION_NONPRIORIZED_LEMMING (1<<14) 21 | #define ACTION_NUKE (1<<15) 22 | #define ACTION_NUKE_IMMEDIATELY (1<<16) 23 | #define ACTION_QUIT (1<<17) 24 | #define ACTION_SELECT_SKILL_CIMBER (1<<18) 25 | #define ACTION_SELECT_SKILL_FLOATER (1<<19) 26 | #define ACTION_SELECT_SKILL_BOMBER (1<<20) 27 | #define ACTION_SELECT_SKILL_BLOCKER (1<<21) 28 | #define ACTION_SELECT_SKILL_BUILDER (1<<22) 29 | #define ACTION_SELECT_SKILL_BASHER (1<<23) 30 | #define ACTION_SELECT_SKILL_MINER (1<<24) 31 | #define ACTION_SELECT_SKILL_DIGGER (1<<25) 32 | #define ACTION_MOVE_CURSOR_PARAM (1<<26) // mirror UD (can be combined with mirror LD) 33 | #define ACTION_SCROLL_PARAM (1<<27) // mirror LR (can be combined with mirror UD) 34 | #define ACTION_QUIT_GAME (1<<28) 35 | #define ACTION_STEP_FRAME (1<<29) 36 | 37 | #define BOTTOM_SCREEN_Y_OFFSET 32 38 | 39 | #define MAX_ACTION_QUEUE_SIZE 3 40 | struct ActionQueue { 41 | enum Action { 42 | ACTIONQUEUE_NOP, // no action 43 | ACTIONQUEUE_NUKE, // 0 = nuke; 1 = exit immediately 44 | ACTIONQUEUE_ASSIGN_SKILL, // to Lemming with id stored in param (lem1) or param2 (lem2); skill stored in param3 45 | ACTIONQUEUE_TOGGLE_PAUSE, 46 | ACTIONQUEUE_FRAME_FORWARD, // (s8)param: number of frames (handling of negative values not implemented yet) 47 | ACTIONQUEUE_CHANGE_RATE // (s8)param = changing 48 | } action; 49 | u8 param; 50 | u8 param2; 51 | u8 param3; 52 | }; 53 | 54 | struct InputState { 55 | // number of (input-)frames since user pressed a button 56 | // to change the release rate of lemmings 57 | u8 change_rate_hold; 58 | // time elapsed since user pressed the nuke button the last time. 59 | // 0 if this is long ago. 60 | u8 time_since_nuke_pressed; 61 | u8 speed_up; 62 | u8 nonprio_lem; 63 | u8 skill; 64 | struct {s16 x; s16 y;} cursor; 65 | u16 x_pos; 66 | u8 num_actions; // number of actions in queue 67 | struct ActionQueue action_queue[MAX_ACTION_QUEUE_SIZE]; 68 | }; 69 | 70 | u64 get_action(u32 kDown, u32 kHeld, circlePosition cpad, circlePosition* params); 71 | void init_io_state(struct InputState* io_state, u16 x_pos); 72 | int add_action(struct InputState* io_state, enum Action action, u8 param, u8 param2, u8 param3); 73 | int read_io(struct Level* level, struct InputState* io_state, u8 player); 74 | int process_action_queue( 75 | // actions to perform (invalid actions will be replaced by ACTIONQUEUE_NOP) 76 | struct ActionQueue* action_queue, 77 | u8 num_actions, 78 | struct Level* level, 79 | u8 player_id, 80 | // if multiplayer is set, some actions will be disabled 81 | u8 multiplayer); 82 | #endif 83 | -------------------------------------------------------------------------------- /include/cursor.h: -------------------------------------------------------------------------------- 1 | #ifndef CURSOR_H 2 | #define CURSOR_H 3 | #include <3ds.h> 4 | // 14x14 palette images 5 | extern const u8 cursor_data[]; 6 | extern const u8 cursor_active_data[]; 7 | #endif 8 | -------------------------------------------------------------------------------- /include/data_cache.h: -------------------------------------------------------------------------------- 1 | #ifndef DAtA_CACHE_H 2 | #define DAtA_CACHE_H 3 | #define CACHE_FILE_VERSION 0 4 | #include <3ds.h> 5 | 6 | void update_data_cache_old(u8 game, u8 multiplayer, const char* level_names); 7 | u8 read_data_cache_old(u8 game, u8 multiplayer, char* level_names); 8 | u8 read_data_cache(u8* games, char* level_names, u16 overall_num_of_levels); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/decode.h: -------------------------------------------------------------------------------- 1 | #ifndef DECODE_H 2 | #define DECODE_H 3 | #include 4 | #include <3ds.h> 5 | 6 | struct CompressionHeader { 7 | s8 start_bits; 8 | s8 checksum; 9 | u16 size_dec; 10 | u16 size_enc; 11 | }; 12 | 13 | 14 | struct Data { 15 | u16 size; 16 | s8 data[0]; 17 | }; 18 | 19 | int goto_section(FILE* input, u16 section); 20 | struct Data* decompress_cur_section(FILE* input); 21 | int planar_image_to_pixmap( 22 | u8* pixmap_image, 23 | void* planar_image, 24 | u16 num_of_pixels, 25 | u16 mask_offset); 26 | #endif 27 | -------------------------------------------------------------------------------- /include/draw.h: -------------------------------------------------------------------------------- 1 | #ifndef DRAW_H 2 | #define DRAW_H 3 | #include <3ds.h> 4 | #include "settings.h" 5 | #include "main_data.h" 6 | #include "level.h" 7 | #include "lemming_data.h" 8 | #include "control.h" 9 | 10 | typedef enum 11 | { 12 | TOP_SCREEN = 0, 13 | BOTTOM_SCREEN = 1, 14 | TOP_SCREEN_BACK = 2, 15 | BOTTOM_SCREEN_BACK = 3 16 | } ScreenBuffer; 17 | 18 | void init_drawing(); 19 | 20 | void begin_frame(); 21 | void end_frame(); 22 | 23 | // apply fading to palette (not its alpha value, since gfx seems to ignore alpha value) 24 | void fade_palette(u32 palette[16], float fading); 25 | 26 | // scale with linear interpolation 27 | int draw_scaled( 28 | ScreenBuffer screen, 29 | s16 x, 30 | s16 y, 31 | const u8* img, 32 | u16 w, 33 | u16 h, 34 | u32 palette[16], 35 | float scaling); 36 | 37 | int clear(ScreenBuffer screen); 38 | int clear_rectangle(ScreenBuffer screen, u16 x, u16 y, u16 w, u16 h); 39 | int color_rectangle(ScreenBuffer screen, u16 x, u16 y, u16 w, u16 h, u32 color); 40 | void copy_from_backbuffer(ScreenBuffer screen); 41 | 42 | // draw palette image into RGB image at specific position 43 | int draw(ScreenBuffer screen, s16 x, s16 y, const u8* img, u16 w, u16 h, u32 palette[16]); 44 | 45 | // draw menu background into im_bottom (tiled) 46 | void tile_menu_background(ScreenBuffer screen, struct MainMenuData* menu_data); 47 | 48 | // draw level view at top of RGB image while (without info text, toolbar, and lemmings) 49 | int draw_level( 50 | ScreenBuffer screen, 51 | s16 x, 52 | s16 y, 53 | u16 w, 54 | u16 h, 55 | s16 x_offset, 56 | struct Level* level, 57 | struct MainInGameData* main_data, 58 | u32* palette, 59 | u32* lemmings_palette); 60 | 61 | 62 | // draw a string using highperf-font into an RGB image 63 | void draw_highperf_text( 64 | ScreenBuffer screen, 65 | s16 x, 66 | s16 y, 67 | struct MainInGameData* data, 68 | const char* text, 69 | u32* highperf_palette); 70 | 71 | // draw a string using menu-font into an RGB image 72 | void draw_menu_text( 73 | ScreenBuffer screen, 74 | struct MainMenuData* data, 75 | s16 x_offset, 76 | s16 y_offset, 77 | const char* text, 78 | u32* palette, 79 | float scaling); 80 | 81 | // draw toolbar at bottom of RGB image 82 | int draw_toolbar( 83 | struct MainInGameData* data, 84 | struct Level* level, 85 | struct InputState* io_state, 86 | const char* text, 87 | u32* highperf_palette, 88 | u8 player); 89 | 90 | // draw lemmings of all players (only 2 players supported) 91 | void draw_lemmings( 92 | ScreenBuffer screen, 93 | s16 x, 94 | s16 y, 95 | struct Level* level, 96 | struct Image* lemmings_anim[337], 97 | struct Image* masks[23], 98 | u32 palette[16], 99 | s16 x_offset, 100 | s16 y_offset); 101 | 102 | int update_topscreen(struct MainMenuData* menu); 103 | #endif 104 | -------------------------------------------------------------------------------- /include/gamespecific.h: -------------------------------------------------------------------------------- 1 | #ifndef GAMESPECIFIC_H 2 | #define GAMESPECIFIC_H 3 | #include <3ds.h> 4 | 5 | #define LEMMING_GAMES 10 // number of currently supported games... 6 | 7 | // game identifiers 8 | #define LEMMINGS_DEMO 0 9 | #define ORIGINAL_LEMMINGS 1 10 | #define OH_NO_DEMO 2 11 | #define OH_NO_MORE_LEMMINGS 3 12 | #define HOLIDAY_LEMMINGS_91 4 13 | #define HOLIDAY_LEMMINGS_92 5 14 | #define HOLIDAY_93_DEMO 6 15 | #define HOLIDAY_LEMMINGS_93 7 16 | #define HOLIDAY_94_DEMO 8 17 | #define HOLIDAY_LEMMINGS_94 9 18 | 19 | #define PATH_DATA_DEMO "orig_demo" 20 | #define PATH_DATA_ORIGINAL "orig" 21 | #define PATH_DATA_OHNODEMO "ohno_demo" 22 | #define PATH_DATA_OHNOMORE "ohno" 23 | #define PATH_DATA_XMAS91 "xmas91" 24 | #define PATH_DATA_XMAS92 "xmas92" 25 | #define PATH_DATA_HOLI93DEMO "holi93_demo" 26 | #define PATH_DATA_HOLIDAY93 "holi93" 27 | #define PATH_DATA_HOLI94DEMO "holi94_demo" 28 | #define PATH_DATA_HOLIDAY94 "holi94" 29 | 30 | struct GameSpecific { 31 | const u8 ABBA_order; 32 | const u8 entrance_x_offset; 33 | const u8 num_of_difficulties; 34 | const u8 num_of_level_per_difficulty; 35 | const char* const path; 36 | const char* const custom_audio_path; 37 | const char* const level_dat_prefix; 38 | const char* const* const difficulties; 39 | const unsigned char* const level_position; 40 | const char* const* const messages; 41 | const unsigned char num_of_difficulty_graphics; 42 | const u32* const main_palette; 43 | const u32* const ingame_palette; 44 | const u32* const highperf_palette; 45 | const u8 num_of_songs; 46 | const u8* const song_ids; 47 | const u8 num_special_songs; 48 | const struct SpecialSongs{ 49 | const u8 level; 50 | const u8 song; 51 | }* const special_songs; 52 | const u8 num_of_special_messages; 53 | const struct SpecialMessages{ 54 | const u8 level; 55 | const u8 lines_of_message; 56 | const char* const message; 57 | }* const special_messages; 58 | }; 59 | extern const struct GameSpecific import[LEMMING_GAMES]; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /include/gamespecific_2p.h: -------------------------------------------------------------------------------- 1 | #ifndef GAMESPECIFIC_2P_H 2 | #define GAMESPECIFIC_2P_H 3 | #include <3ds.h> 4 | #include "gamespecific.h" 5 | 6 | struct GameSpecific2P { 7 | const char* const level_path; 8 | const char* const ressource_path; 9 | const u32* const ingame_palette; 10 | const u8 num_levels; 11 | const u8* const swap_exit; 12 | }; 13 | extern const struct GameSpecific2P import_2p[2]; 14 | #endif 15 | -------------------------------------------------------------------------------- /include/highperf_font.h: -------------------------------------------------------------------------------- 1 | #ifndef HIGHPERFFONT_H 2 | #define HIGHPERFFONT_H 3 | #include <3ds.h> 4 | // the ingame fonts (high performance as well as normal) do not 5 | // include all ASCII characters (see /doc/data/lemmings_main_dat_file_format.txt). 6 | // to display level names, we need additional symbols for the font. 7 | // these symbols are defined here. 8 | 9 | // note that some printable ASCII characters still are not covered. e.g. { / 10 | // so, in order to allow user levels (with arbitrary level names) these charactes have 11 | // to be added here (and in draw.c file). 12 | 13 | // 8x16 palette images 14 | extern const u8 highperf_font_dot[]; 15 | extern const u8 highperf_font_comma[]; 16 | extern const u8 highperf_font_questionmark[]; 17 | extern const u8 highperf_font_exclamationmark[]; 18 | extern const u8 highperf_font_colon[]; 19 | extern const u8 highperf_font_bracket_l[]; 20 | extern const u8 highperf_font_bracket_r[]; 21 | extern const u8 highperf_font_apostrophe[]; 22 | extern const u8 highperf_font_quote[]; 23 | #endif 24 | -------------------------------------------------------------------------------- /include/import_adlib.h: -------------------------------------------------------------------------------- 1 | #ifndef ADLIB_DAT_H 2 | #define ADLIB_DAT_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "decode.h" 9 | void add_sample(unsigned long length, s32* data, unsigned long param); 10 | 11 | void OPL_Init(unsigned long sample_rate); 12 | int OPL_LoadAdlibData(struct Data* decoded_adlib_dat); 13 | int OPL_CallAdlib(u16 ax, int module); 14 | void OPL_FreeAdlibData(); 15 | void OPL_QuerySamples(unsigned long samples, int module); 16 | void OPL_ShutDown(); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /include/import_ground.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORT_GROUND_H 2 | #define IMPORT_GROUND_H 3 | #include <3ds.h> 4 | #include 5 | 6 | struct ObjectInfo { 7 | u16 animation_flags; 8 | u8 start_animation_frame_index; 9 | u8 end_animation_frame_index; 10 | u8 width; 11 | u8 height; 12 | u16 animation_frame_data_size; 13 | u16 mask_offset_from_image; 14 | u16 unknown1; 15 | u16 unknown2; 16 | u16 trigger_left; 17 | u16 trigger_top; 18 | u8 trigger_width; 19 | u8 trigger_height; 20 | u8 trigger_effect_id; 21 | u16 animation_frames_base_loc; 22 | u16 preview_image_index; 23 | u16 unknown3; 24 | u8 trap_sound_effect_id; 25 | }; 26 | 27 | struct TerrainInfo { 28 | u8 width; 29 | u8 height; 30 | u16 image_loc; 31 | u16 mask_loc; 32 | u16 unknown1; 33 | }; 34 | 35 | struct LevelPalette { 36 | u8 ega_custom[8]; 37 | u8 ega_standard[8]; 38 | u8 ega_preview[8]; 39 | u32 vga_custom[8]; 40 | u32 vga_standard[8]; 41 | u32 vga_preview[8]; 42 | }; 43 | 44 | struct GroundInfo { 45 | struct ObjectInfo object_info[16]; 46 | struct TerrainInfo terrain_info[64]; 47 | struct LevelPalette palette; 48 | }; 49 | 50 | int read_ground_data(struct GroundInfo* ret, void* ground_data); 51 | #endif 52 | -------------------------------------------------------------------------------- /include/import_level.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORT_LEVEL_H 2 | #define IMPORT_LEVEL_H 3 | #include <3ds.h> 4 | #include "decode.h" 5 | #include "level.h" 6 | 7 | int read_level_names(u8 game, char* names); 8 | // count number of available custom files in specific folder 9 | u8 count_custom_levels(const char* path, u8 offset); 10 | // read at most num_of_level many names; on success: set num_of_levels to the number of names that have been read 11 | int read_level_names_from_path(const char* levelpath, u8* num_of_levels, char* names); 12 | 13 | int read_level( 14 | u8 game, 15 | u8 id, 16 | void* level, 17 | void* ground_data, 18 | struct Data** vgagr_s0, 19 | struct Data** vgagr_s1, 20 | struct Data** vgaspec); 21 | // import custom level (from uncompressed lvl file) 22 | int read_level_file( 23 | const char* filename, 24 | const char* ressource_path, 25 | void* level, 26 | void* ground_data, 27 | struct Data** vgagr_s0, 28 | struct Data** vgagr_s1, 29 | struct Data** vgaspec); 30 | 31 | int parse_level( 32 | void* level, 33 | void* ground_data, 34 | struct Data* vgagr_s0, 35 | struct Data* vgagr_s1, 36 | struct Data* vgaspec, 37 | const u32* ingame_palette, 38 | u8 ABBA_order, 39 | u8 entrance_x_offset, 40 | u8 players, 41 | struct Level* output); 42 | 43 | struct Level* init_level_from_dat(u8 game, u8 lvl, char* level_id); 44 | 45 | void free_objects(struct ObjectType* objects[16]); 46 | #endif 47 | -------------------------------------------------------------------------------- /include/import_main.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORT_MAIN_H 2 | #define IMPORT_MAIN_H 3 | #include <3ds.h> 4 | #include "main_data.h" 5 | 6 | int read_main_ingame(u8 game, struct MainInGameData* data); 7 | void free_ingame_data_arrays(struct MainInGameData* data); // free memory pointed to by lemmings_anim and masks 8 | 9 | int read_main_menu(u8 game, struct MainMenuData* data); 10 | void free_menu_data_arrays(struct MainMenuData* data); // free memory pointed to by static_pictures 11 | 12 | int read_gamespecific_data(u8 game, struct MainMenuData* menu, struct MainInGameData* ingame); 13 | void clean_gamedata(struct MainMenuData* menu_data, struct MainInGameData* main_data); 14 | #endif 15 | -------------------------------------------------------------------------------- /include/import_wave.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORT_WAVE_H 2 | #define IMPORT_WAVE_H 3 | #include 4 | #include <3ds.h> 5 | 6 | struct WaveFile { 7 | FILE* file; 8 | u8 channels; 9 | u8 bitdepth; 10 | u32 frequency; 11 | 12 | u32 data_offset; 13 | u32 current_data_position; 14 | u32 data_size; 15 | }; 16 | 17 | struct WaveSound { 18 | u8 channels; 19 | u8 bitdepth; 20 | u32 frequency; 21 | u32 samples; 22 | u16 size; 23 | void* data; // linearAlloc 24 | }; 25 | 26 | // return: success (true) or failure (false) 27 | // supported format: PCM wave, mono or stereo, 8 or 16 bit 28 | int wave_open_file(struct WaveFile* file, const char* filename); 29 | 30 | // return: number of samples read 31 | u32 wave_get_next_samples(void* buffer, u32 num_samples, struct WaveFile* file); 32 | 33 | void wave_rewind(struct WaveFile* file); 34 | 35 | void wave_close_file(struct WaveFile* file); 36 | 37 | // return: success (true) or failure (false) 38 | int import_wave_sound(struct WaveSound* dest, const char* filename); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/ingame.h: -------------------------------------------------------------------------------- 1 | #ifndef INGAME_H 2 | #define INGAME_H 3 | #include <3ds.h> 4 | #include "level.h" 5 | #include "control.h" 6 | #include "main_data.h" 7 | #include "draw.h" 8 | 9 | #define LEVEL_ERROR 0 10 | #define LEVEL_TIMEOUT 1 11 | #define LEVEL_NO_LEMMINGS_LEFT 2 12 | #define LEVEL_EXIT_GAME 127 13 | 14 | 15 | struct LevelResult { 16 | u8 lvl; 17 | u8 percentage_rescued; 18 | u8 percentage_needed; 19 | u8 exit_reason; 20 | }; 21 | 22 | int level_step( 23 | struct MainInGameData* main_data, 24 | struct Level* level, 25 | // *lemming_inout = 1, iff a any lemming enters or exits the level (without dying) 26 | u8* lemming_inout); 27 | 28 | void render_level_frame( 29 | const char* level_id, // e.g. FUN 14 30 | struct MainInGameData* main_data, 31 | struct Level* level, 32 | struct InputState* io_state, 33 | u8 player); 34 | 35 | // returns: percentage saved 36 | struct LevelResult run_level( 37 | struct Level* level, 38 | const char* level_id, 39 | struct MainMenuData* menu_data, 40 | struct MainInGameData* main_data); 41 | 42 | int show_result( 43 | u8 game, 44 | struct LevelResult result, 45 | struct MainMenuData* menu_data); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /include/lemming.h: -------------------------------------------------------------------------------- 1 | #ifndef LEMMING_H 2 | #define LEMMING_H 3 | #include "level.h" 4 | #include "draw.h" 5 | #include "main_data.h" 6 | #include "settings.h" 7 | #include "lemming_data.h" 8 | 9 | extern const int action_image_offsets[18][2]; 10 | 11 | void init_lemmings(struct Level* level); 12 | int add_lemming(struct Level* level); 13 | u8 lemmings_left(struct LevelPlayer* player_state); // get number of lemmings that did not have entered the level yet. 14 | void nuke(struct LevelPlayer* player); 15 | 16 | void update_lemmings( 17 | struct Level*, 18 | struct Image* masks[23]); 19 | 20 | // x,y: mouse position 21 | // returns number of lemmings under cursor 22 | u8 select_lemming(struct Lemming[MAX_NUM_OF_LEMMINGS], s16 x, s16 y, u8 right_mouse_btn, s16* lem1_idx, s16* lem2_idx); 23 | 24 | // return 0 on failure, 1 on success 25 | // skills: CLIMBING=0, FLOATING=1, etc. (ordering like at the skill panel) 26 | u8 assign_skill(u8 skill, struct Lemming* lem1, struct Lemming* lem2, struct Level*); 27 | 28 | const char* get_lemming_description(struct Lemming*); // returns ptr to pre-initialized string (no heap is reserved, do not free the pointer) 29 | 30 | // get number of lemmings alive 31 | int count_lemmings(struct Lemming lemmings[MAX_NUM_OF_LEMMINGS]); 32 | #endif 33 | -------------------------------------------------------------------------------- /include/lemming_data.h: -------------------------------------------------------------------------------- 1 | #ifndef LEMMING_DATA_H 2 | #define LEMMING_DATA_H 3 | #include <3ds.h> 4 | 5 | #define LEMACTION_WALK 0 6 | #define LEMACTION_SPLATTER 1 // after falling down from too high 7 | #define LEMACTION_EXPLODE 2 // fire ball and explosion particles 8 | #define LEMACTION_FALL 3 9 | #define LEMACTION_JUMP 4 10 | #define LEMACTION_DIG 5 11 | #define LEMACTION_CLIMB 6 12 | #define LEMACTION_HOIST 7 // end of climbing 13 | #define LEMACTION_BUILD 8 14 | #define LEMACTION_BLOCK 9 15 | #define LEMACTION_BASH 10 16 | #define LEMACTION_FLOAT 11 17 | #define LEMACTION_MINE 12 18 | #define LEMACTION_DROWN 13 // in water 19 | #define LEMACTION_EXIT 14 20 | #define LEMACTION_FRY 15 // killed by flameblower etc. 21 | #define LEMACTION_OHNO 16 22 | #define LEMACTION_SHRUG 17 // builder finished buildung 23 | 24 | #define LEMABILITY_CLIMB 1 25 | #define LEMABILITY_FLOAT 2 26 | 27 | #define LEM_MIN_Y -5 // HEAD_MIN_Y 28 | #define LEM_MAX_Y 163 // LEMMING_MAX_Y 29 | #define LEM_MAX_FALLING 60 // MAX_FALLDISTANCECOUNT 30 | 31 | struct Lemming { 32 | u8 removed; 33 | u8 current_action; 34 | u8 timer; // start with 79 (frame based) 35 | s16 x; 36 | s16 y; 37 | s8 x_draw_offset; 38 | s8 y_draw_offset; 39 | u8 draw_action; // used to copy the shrugging-bug; draw current_action when bug is disabled 40 | u8 frame_offset; 41 | u16 fall_distance; 42 | u8 look_right; 43 | u8 abilities; // mask: LEMABILITY_CLIMB; LEMABILITY_FLOAT 44 | u8 float_index; 45 | u8 bricks_left; 46 | u8 blocking; 47 | u8 start_digging; 48 | u8 object_below; 49 | u8 object_in_front; 50 | u8 saved_object_map[9]; 51 | u8 exit_counts_for; // player id the exiting lemming counts for 52 | u8 player; // owner of this lemming 53 | }; 54 | #endif 55 | -------------------------------------------------------------------------------- /include/level.h: -------------------------------------------------------------------------------- 1 | #ifndef LEVEL_H 2 | #define LEVEL_H 3 | #include <3ds.h> 4 | #include "settings.h" 5 | #include "lemming_data.h" 6 | 7 | // object effects 8 | #define OBJECT_EXIT 1 9 | #define OBJECT_FORCE_LEFT 2 10 | #define OBJECT_FORCE_RIGHT 3 11 | #define OBJECT_TRAP 4 12 | #define OBJECT_WATER 5 13 | #define OBJECT_FIRE 6 14 | #define OBJECT_ONEWAY_LEFT 7 15 | #define OBJECT_ONEWAY_RIGHT 8 16 | #define OBJECT_STEEL 9 17 | #define OBJECT_BLOCKER 10 18 | 19 | struct ObjectType { 20 | u16 flags; 21 | u16 width; 22 | u16 height; 23 | u8 start_frame; // used? 24 | u8 end_frame; // or: frames 25 | s16 trigger_x; 26 | s16 trigger_y; 27 | u8 trigger_width; 28 | u8 trigger_height; 29 | u8 trigger; // effect 30 | u8 sound; 31 | u8 preview_frame; 32 | u8 data[0]; 33 | }; 34 | 35 | #define OBJECT_USED 1 // is object slot used / object present? 36 | #define OBJECT_DONT_OVERWRITE 2 // dont overwrite terrain, but overwrite other objects? 37 | #define OBJECT_REQUIRE_TERRAIN 4 // dont accept other objects? 38 | #define OBJECT_UPSIDE_DOWN 8 // draw object upside down (TODO: flip trigger area as well?) 39 | struct ObjectInstance { 40 | s16 x; 41 | s16 y; 42 | u8 type; 43 | u8 modifier; 44 | u8 current_frame; // STATE; 45 | }; 46 | 47 | struct LevelPlayer { 48 | u8 max_lemmings; 49 | u8 skills[8]; 50 | u16 x_pos; 51 | u8 nuking; 52 | u8 timer_assign; 53 | u8 next_lemming_id; 54 | // player one's lemmings and player two's lemmings that used the exit 55 | // of that player this LevelPlayer struct corresponds to. 56 | u8 rescued[2]; 57 | u16 request_common_nuke; // multiplayer only. set when player wants to nuke. count down each frame, so the other player must request nuking in the same time slot 58 | u8 ready_to_start; // multiplayer only. set when player finished inspection of the level. to start the game, all players must have set this value (if level inspection is enabled). 59 | struct Lemming lemmings[MAX_NUM_OF_LEMMINGS]; 60 | }; 61 | 62 | struct Level { 63 | u8 terrain[1584*160]; 64 | struct ObjectType* object_types[16]; 65 | struct ObjectInstance object_instances[32]; 66 | u8 object_map[1584/4*160/4]; 67 | u32 palette[16]; 68 | struct {s16 x; s16 y;} entrances[4]; 69 | u8 rate; 70 | u8 percentage_needed; 71 | u8 speed_up; 72 | u8 num_players; 73 | 74 | u8 fade_in; 75 | u8 fade_out; 76 | u8 opening_counter; // count frames until entrances open 77 | u8 entrances_open; 78 | u16 frames_left; // until time is up 79 | 80 | u8 paused; 81 | u8 inspect; 82 | u8 frame_step_forward; 83 | 84 | u8 next_lemming_countdown; 85 | // which player got the last lemming? 86 | // in multiplayer mode, the next lemming should belong to the other player 87 | u8 lemming_last_player; 88 | u8 cur_rate; 89 | 90 | struct LevelPlayer player[2]; 91 | 92 | char name[33]; 93 | }; 94 | #endif 95 | -------------------------------------------------------------------------------- /include/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H 2 | #define MAIN_H 3 | #define SUCCESS(res) (R_SUCCEEDED(res) && \ 4 | (R_LEVEL(res) == RL_SUCCESS \ 5 | || R_SUMMARY(res) == RS_SUCCESS \ 6 | || R_DESCRIPTION(res) == RD_SUCCESS)) 7 | 8 | #define FAILED(res) (!SUCCESS(res)) 9 | 10 | int was_suspended(); 11 | #endif 12 | -------------------------------------------------------------------------------- /include/main_data.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_DATA_H 2 | #define MAIN_DATA_H 3 | #include <3ds.h> 4 | 5 | struct Image { 6 | u16 width; 7 | u16 height; 8 | u8 data[0]; 9 | }; 10 | 11 | struct MainInGameData { 12 | struct Image* lemmings_anim[337]; 13 | struct Image* masks[23]; 14 | u32 high_perf_palette[16]; 15 | u8 high_perf_toolbar[320*40]; 16 | u8 skill_numbers[20*8*8]; 17 | u8 high_perf_font[38*8*16]; 18 | u32 level_base_palette[7]; 19 | }; 20 | 21 | struct MainMenuData { 22 | u32 palette[16]; 23 | struct Image* static_pictures[16]; 24 | u8 blinking_eyes[7*8*32*12]; 25 | u8 scroller[2*16*48*16]; 26 | u8 menu_font[94*16*16]; 27 | }; 28 | #endif 29 | -------------------------------------------------------------------------------- /include/menu.h: -------------------------------------------------------------------------------- 1 | #ifndef MENU_H 2 | #define MENU_H 3 | #include <3ds.h> 4 | #include "draw.h" 5 | #include "import_main.h" 6 | #include "savegame.h" 7 | 8 | #define MENU_ERROR 0 9 | #define MENU_ACTION_EXIT 1 10 | #define MENU_ACTION_START_SINGLE_PLAYER 2 11 | #define MENU_ACTION_SELECT_LEVEL_SINGLE_PLAYER 3 12 | #define MENU_ACTION_SETTINGS 4 13 | #define MENU_ACTION_LEVEL_SELECTED 5 14 | #define RESULT_ACTION_NEXT 6 15 | #define RESULT_ACTION_CANCEL 7 16 | #define MENU_ACTION_START_MULTI_PLAYER 8 17 | #define MENU_HOST_REJECT_CLIENT 9 18 | #define MENU_CLIENT_QUIT 10 19 | #define MENU_EXIT_NETWORK 11 20 | #define MENU_EXIT_GAME 127 21 | 22 | int main_menu( 23 | u8 games[], 24 | u8* game, 25 | u8* lvl, 26 | struct MainMenuData* menu_data, 27 | struct MainInGameData* main_data, 28 | struct SaveGame* savegame); 29 | 30 | int level_select_menu( 31 | u8 games[], 32 | u8* game, 33 | u8* lvl, 34 | u8* progress, 35 | const char* level_names, 36 | struct MainMenuData* menu_data, 37 | struct MainInGameData* main_data); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /include/network.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_H 2 | #define NETWORK_H 3 | #include <3ds.h> 4 | #include "control.h" 5 | 6 | #define WLAN_ID 0x40F91730 7 | #define WLAN_PASS "Lemmings for 3DS rulez" 8 | #define NETWORK_PROTOCOL_VERSION 2 9 | #define NETWORK_MIN_PROTOCOL_VERSION 2 10 | 11 | // network messages 12 | #define NW_ERROR 0 // communication error (maybe just cancel connection...; implement handling later) 13 | #define NW_PROTOCOL_VERSION 42 14 | #define NW_INITIALIZE 1 // confirm that the server WILL start the game; params: num of (active) players, num of lemmings per player, player id of receiver 15 | #define NW_LEVEL_INFO 2 // receive level data (first packet: Info. then: Chunks) 16 | #define NW_LEVEL_DATA_CHUNK 3 // send back (with zero length) to confirm receivement 17 | #define NW_LEVEL_SENT 4 // entire level has been sent 18 | #define NW_RUN_FRAME 5 // step a given number of frames without action (info from server) 19 | #define NW_USER_INPUT 6 // sent from server to client: screen positions and skill assignments of all players (to inform them); 20 | #define NW_CLIENT_ACTION 7 // client sends actions to server to ask him to apply them (use NW_User_Input struct, maybe do not use all fields) 21 | #define NW_LVLRESULT 8 // indicate end of level and send results (lemmings saved by each player) 22 | #define NW_READY_TO_PROCEED 9 // client informs server that he is ready to start the next level (user has read the result message) 23 | 24 | #define NETWORK_SUCCESS 0 25 | #define NETWORK_ERROR_WLAN_LOST 1 26 | #define NETWORK_ERROR_CONNECTION_LOST 2 27 | #define NETWORK_ERROR_PROTOCOL_ERROR 3 28 | #define NETWORK_ERROR_READ_LEVEL_ERROR 4 29 | #define NETWORK_ERROR_PARSE_LEVEL_ERROR 5 30 | #define NETWORK_ERROR_OUT_OF_MEM 6 31 | #define NETWORK_ERROR_ASYNCHRONOUS 7 32 | #define NETWORK_ERROR_NO_2P_LEVELS 8 33 | #define NETWORK_ERROR_OTHER 9 34 | 35 | // sent by client on connect (until server confirms receivement) 36 | struct NW_ProtocolVersion { 37 | u8 msg_type; 38 | u8 version_major; 39 | u8 version_minor; 40 | }; 41 | 42 | struct NW_GameInit { 43 | u8 msg_type; 44 | u8 num_players; 45 | u8 lemmings_per_player[2]; 46 | u8 receiver_id; 47 | u8 lvl_id; 48 | u8 game_id; 49 | u8 glitch_direct_drop; 50 | u8 glitch_shrugger; 51 | u8 timeout; // 2p time limit settings 52 | u8 inspect_level; // inspect level before it starts? 53 | }; 54 | 55 | struct NW_LevelData_Info { 56 | u8 msg_type; 57 | u8 swap_exits; 58 | u32 vgagr_s0_size; 59 | u32 vgagr_s1_size; 60 | u32 vgaspec_size; 61 | u32 ingame_basis_palette[7]; 62 | u8 ground_data[1056]; 63 | }; 64 | 65 | struct NW_LevelData_Chunk { 66 | u8 msg_type; 67 | u8 type; // 0: level; 1: vgagr_s0; 2: vgagr_s1; 3: vgaspec 68 | u32 offset; 69 | u16 length; 70 | u8 data[0]; 71 | }; 72 | 73 | struct NW_User_Input { 74 | u8 msg_type; 75 | u8 player_id; 76 | u16 x_pos; 77 | u32 frame_id; // frame id the user made the input (only set when sent by server) 78 | u32 input_id; // id of this operation; starting with 1; no id must be missed 79 | u8 num_actions; 80 | struct ActionQueue action_queue[MAX_ACTION_QUEUE_SIZE]; 81 | }; 82 | 83 | struct NW_Run_Frame { 84 | u8 msg_type; 85 | u32 frame_id; // current frame id 86 | u32 required_input_id; // this frame must only be rendered if the required_input_id has been received already 87 | }; 88 | 89 | struct NW_Level_Result { 90 | u8 msg_type; 91 | u8 lemmings_saved[2]; 92 | u16 won[2]; 93 | }; 94 | 95 | int connection_alive(); 96 | #endif 97 | -------------------------------------------------------------------------------- /include/network_game.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_GAME_H 2 | #define NETWORK_GAME_H 3 | #include <3ds.h> 4 | #include "menu.h" 5 | #include "savegame.h" 6 | 7 | int host_game(struct SaveGame* savegame, u8 num_levels[2], char* level_names, struct MainMenuData* menu_data, struct MainInGameData* main_data); 8 | int connect_to_network(const udsNetworkScanInfo* scan_info, struct MainMenuData* menu_data, struct MainInGameData* main_data); 9 | #endif 10 | -------------------------------------------------------------------------------- /include/network_menu.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_MENU_H 2 | #define NETWORK_MENU_H 3 | #include <3ds.h> 4 | #include "import_main.h" 5 | #include "savegame.h" 6 | 7 | int get_aligned_username(char username[41+2*6], const udsNodeInfo* nodeinfo); 8 | int network_menu(struct SaveGame* savegame, u8 num_levels[2], char* level_names, struct MainMenuData* menu_data, struct MainInGameData* main_data); 9 | int show_network_error(u8 error, struct MainMenuData* menu_data); 10 | #endif 11 | -------------------------------------------------------------------------------- /include/network_run_level.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_RUN_LEVEL_H 2 | #define NETWORK_RUN_LEVEL_H 3 | #include <3ds.h> 4 | #include "level.h" 5 | #include "main_data.h" 6 | 7 | int server_prepare_level( 8 | udsBindContext* bindctx, 9 | const u8* lemmings, // number of lemmings the players start with 10 | u8 game_id, 11 | u8 level_id, 12 | struct Level* output); 13 | 14 | // important: overwrites settings.glitch_direct_drop. 15 | // therefore the local value has to be stored before this function is called 16 | int client_prepare_level( 17 | udsBindContext* bindctx, 18 | const u8* lemmings, // number of lemmings the players start with 19 | u8* lvl_id, 20 | u8 game_id, 21 | struct Level* output); 22 | 23 | int server_run_level( 24 | udsBindContext* bindctx, 25 | struct Level* level, 26 | const char* level_id, 27 | u8* lemmings, // number of lemmings the players have rescued 28 | struct MainMenuData* menu_data, 29 | struct MainInGameData* main_data); 30 | int client_run_level( 31 | udsBindContext* bindctx, 32 | struct Level* level, 33 | const char* level_id, 34 | u8* lemmings, 35 | u16* won, 36 | struct MainMenuData* menu_data, 37 | struct MainInGameData* main_data); 38 | 39 | int server_send_result( 40 | udsBindContext* bindctx, 41 | u8 lemmings[2], 42 | u16 won[2]); 43 | 44 | #define CHUNK_SIZE (UDS_DATAFRAME_MAXSIZE - sizeof(struct NW_LevelData_Chunk)) 45 | #endif 46 | -------------------------------------------------------------------------------- /include/particles.h: -------------------------------------------------------------------------------- 1 | #ifndef PARTICLES_H 2 | #define PARTICLES_H 3 | #include <3ds.h> 4 | // particles of exploding lemming 5 | extern const s8 particles[]; 6 | #endif 7 | -------------------------------------------------------------------------------- /include/patch_menu.h: -------------------------------------------------------------------------------- 1 | #ifndef PATCH_MENU_H 2 | #define PATCH_MENU_H 3 | #include <3ds.h> 4 | // 21 x 12 5 | extern const u8 orig_mainmenu_no_dos_1p[]; 6 | // 21 x 12 7 | extern const u8 orig_mainmenu_no_dos_2p[]; 8 | // 21 x 12 9 | extern const u8 orig_mainmenu_no_dos_settings[]; 10 | // 17 x 9 11 | extern const u8 xmas_mainmenu_no_dos_1p[]; 12 | // 15 x 7 13 | extern const u8 xmas_mainmenu_no_dos_2p[]; 14 | // 17 x 9 15 | extern const u8 holi_mainmenu_no_dos_1p[]; 16 | #endif 17 | -------------------------------------------------------------------------------- /include/savegame.h: -------------------------------------------------------------------------------- 1 | #ifndef SAVEGAME_H 2 | #define SAVEGAME_H 3 | #define SAVEGAME_VERSION 7 4 | #include <3ds.h> 5 | 6 | struct SaveGame { 7 | // u8 array with one entry for each difficulty 8 | // of each supported lemmings game 9 | u8* progress; 10 | u8 multiplayer_progress[2]; 11 | u8 last_game; 12 | u8 last_level; 13 | u8 last_multiplayer_level; 14 | u8 last_multiplayer_game; 15 | }; 16 | 17 | void read_savegame(struct SaveGame* savegame); 18 | void write_savegame(struct SaveGame* savegame); 19 | #endif 20 | -------------------------------------------------------------------------------- /include/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | #include <3ds.h> 4 | 5 | extern const char* PATH_ROOT; 6 | 7 | #define MAX_NUM_OF_LEMMINGS 80 8 | #define FPS 17 // dosframes per second (for time counter in level) 9 | #define SCREEN_WIDTH 320 10 | #define MS_PER_FRAME 71 // duration of frame in milliseconds 11 | #define MS_PER_FRAME_SUPERLEM 28 // frame duration in "introducing superlemming" 12 | #define INPUT_SAMPLING_MILLIS 25 // affects cursor speed and so on... 13 | #define MS_PER_FRAME_SPEED_UP 20 14 | #define FADE_IN_DOSFRAMES 18 15 | #define FADE_OUT_DOSFRAMES 32 16 | #define AMIGA_BACKGROUND 0x000030FF 17 | 18 | #define COMMON_NUKE_FRAME_INTERVAL (FPS * 8 - 1) // player must respond to nuke request within 15 seconds 19 | 20 | #define AUDIO_ORDER_PREFER_CUSTOM 0 21 | #define AUDIO_ORDER_PREFER_ADLIB 1 22 | #define AUDIO_ORDER_ONLY_CUSTOM 2 23 | #define AUDIO_ORDER_ONLY_ADLIB 3 24 | 25 | #define TIMEOUT_2P_NEVER 0 26 | #define TIMEOUT_2P_INACTIVITY 1 27 | #define TIMEOUT_2P_COUNTDOWN 2 28 | 29 | extern struct Settings { 30 | u8 glitch_nuke; 31 | u8 glitch_entrance_pausing; 32 | u8 glitch_mining_right_oneway; 33 | u8 glitch_shrugger; 34 | u8 glitch_mayhem12; // not implemented yet 35 | u8 glitch_direct_drop; 36 | u8 speedup_millis_per_frame; 37 | u8 music_volume; // 0 = off; 100 = max 38 | u8 sfx_volume; // 0 = off; 100 = max 39 | u8 audio_order; // 0 = prefer custom sound; 1 = prefer ADLIB; 2 = only custom; 3 = only ADLIB 40 | u8 dlbclick_nuke; // not implemented yet 41 | u8 dblclick_exit; // not implemented yet 42 | u8 skip_unavailable_skills; // not implemented yet 43 | u8 zoom_mode_active; // not implemented yet 44 | u8 amiga_background; // 0 = no (black); 1 = yes (dark blue) 45 | u8 two_player_always_equal; // 0 = yes, always start with 40 lemmings; 1 = no, start wth 40 + rescued lemmings 46 | u8 two_player_timeout; 47 | u8 two_player_inspect_level; 48 | u8 reserved_1; // maybe 1p recording settings (for playback) 49 | u8 reserved_2; // maybe 2p recording settings (for playback) 50 | struct KeyBindings { 51 | u32 modifier; 52 | u32 click; 53 | u32 inc_rate; 54 | u32 dec_rate; 55 | u32 next_skill; 56 | u32 prev_skill; 57 | u32 pause; 58 | u32 nuke; 59 | u32 exit; 60 | u32 speed_up; 61 | u32 non_prio; 62 | u32 step_one_frame; 63 | u32 step_backwards; // not implemented yet 64 | u32 play_backwards; // not implemented yet 65 | u32 toggle_zoom_mode; // not implemented yet 66 | u32 cursor_up; // d-pad, xyab, c-pad, c-stick 67 | u32 cursor_down; // d-pad, xyab, c-pad, c-stick 68 | u32 cursor_left; // d-pad, xyab, c-pad, c-stick 69 | u32 cursor_right; // d-pad, xyab, c-pad, c-stick 70 | u32 scroll_up; // d-pad, xyab, c-pad, c-stick (for zoom-mode, not implemented yet) 71 | u32 scroll_down; // d-pad, xyab, c-pad, c-stick (for zoom-mode, not implemented yet) 72 | u32 scroll_left; // d-pad, xyab, c-pad, c-stick 73 | u32 scroll_right; // d-pad, xyab, c-pad, c-stick 74 | } key_bindings[2]; 75 | } settings; 76 | 77 | extern u8 settings_icon[]; 78 | #endif 79 | -------------------------------------------------------------------------------- /include/settings_menu.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_MENU_H 2 | #define SETTINGS_MENU_H 3 | #include <3ds.h> 4 | #include "menu.h" 5 | #include "savegame.h" 6 | 7 | int settings_menu(struct SaveGame* savegame, struct MainMenuData* menu_data); 8 | #endif 9 | -------------------------------------------------------------------------------- /lemmings/2p/ohno/README.txt: -------------------------------------------------------------------------------- 1 | Place multiplayer level from Oh No! More Lemmings game here. 2 | You may find these files in an old version of Lix (before 2015) - or somewhere else. 3 | File names must be 01.lvl to 10.lvl. 4 | Note that the order must not be changed, because otherwise 5 | the green goals may count for the blue player and vice versa! 6 | -------------------------------------------------------------------------------- /lemmings/2p/orig/README.txt: -------------------------------------------------------------------------------- 1 | Place multiplayer level from original Lemmings game here. 2 | You may find these files in an old version of Lix (before 2015) - or somewhere else. 3 | File names must be 01.lvl to 20.lvl. 4 | Note that the order must not be changed, because otherwise 5 | the green goals may count for the blue player and vice versa! 6 | -------------------------------------------------------------------------------- /lemmings/audio/README.txt: -------------------------------------------------------------------------------- 1 | You may place custom sound files here. Whenever a valid specific 2 | sound file exists in this folder, it is used instead of 3 | corresponding ADLIB.DAT sound. All other sounds and tunes are 4 | still played by ADLIB.DAT. 5 | 6 | On Windows machines, you may run audio.bat to fill this folder 7 | and its subfolders. However, to do so, you need .Net 4.5 framework 8 | and PlayerSource_V29.zip from neolemmix website and 9 | (optionally) lem_wavs.zip from lemmings universe. 10 | 11 | Format: 12 | Only PCM RIFF WAVE, only 8 or 16 bit, only 1 channel (mono). 13 | Stereo might work, but has not been tested. 14 | 15 | File names and corresponding sound effect: 16 | SFX01.WAV - Skill select 17 | SFX02.WAV - Entrance opening 18 | SFX03.WAV - "Let's go" (Level begin) 19 | SFX04.WAV - Skill assign to lemming 20 | SFX05.WAV - "Oh no!" (Before lemming explosion) 21 | SFX06.WAV - Electrode trap 22 | SFX07.WAV - Squishing trap / spikes trap 23 | SFX08.WAV - "Aaargh!" (splattering after falling down) 24 | SFX09.WAV - Rope trap / slicer trap 25 | SFX10.WAV - Hit steel 26 | SFX11.WAV - (unknown / not used?) 27 | SFX12.WAV - Lemming explosion 28 | SFX13.WAV - Lemming gets fried (fire traps) 29 | SFX14.WAV - 10-ton trap 30 | SFX15.WAV - Bear trap 31 | SFX16.WAV - "Yippieh" (Lemming exiting) 32 | SFX17.WAV - Drowning (in water) 33 | SFX18.WAV - Last 3 bricks of builder 34 | SFX19.WAV - Fall out of level 35 | 36 | File names compared to wave sounds of Windows Lemmings 37 | SFX01.WAV = changeop.wav 38 | SFX02.WAV = door.wav 39 | SFX03.WAV = letsgo.wav 40 | SFX04.WAV = mousepre.wav 41 | SFX05.WAV = ohno.wav 42 | SFX06.WAV = electric.wav 43 | SFX07.WAV = thud.wav 44 | SFX08.WAV = splat.wav 45 | SFX09.WAV = chain.wav 46 | SFX10.WAV = chink.wav 47 | SFX11.WAV = ??? 48 | SFX12.WAV = explode.wav 49 | SFX13.WAV = fire.wav 50 | SFX14.WAV = tenton.wav 51 | SFX15.WAV = thunk.wav 52 | SFX16.WAV = yippee.wav 53 | SFX17.WAV = glug.wav 54 | SFX18.WAV = ting.wav 55 | SFX19.WAV = die.wav 56 | -------------------------------------------------------------------------------- /lemmings/audio/audio.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | mode con: cols=113 lines=30 3 | echo. 4 | echo. 5 | echo This is a windows batch file created by TarkinMX. This batch file uses VLC and 7-Zip to extract audio files 6 | echo from PlayerSource_V29.zip and lem_wavs.zip for use with bayleef's Lemmings for 3DS. If VLC and/or 7-Zip aren't 7 | echo present, then this batch file will use .Net 4.5 to download their portable versions. PlayerSource_V29.zip will 8 | echo be required for music and sound files, lem_wavs.zip is only needed if you'd prefer those sound files. Therefore, 9 | echo the requirements to run this batch file are PlayerSource_V29.zip and then .Net 4.5 framework -OR- VLC and 7-Zip. 10 | echo If you do not meet these requirements then there will be errors. 11 | echo. 12 | timeout /t 30 13 | 14 | if exist "PlayerSource_V29.zip" ( 15 | echo. 16 | echo PlayerSource_V29.zip found 17 | ) else ( 18 | cls 19 | echo. 20 | echo. 21 | echo PlayerSource_V29.zip not found. Please make sure PlayerSource_V29.zip is in this folder. 22 | echo. 23 | echo Press any key to quit. 24 | pause >nul 25 | exit 26 | ) 27 | echo. 28 | echo getting paths for VLC and 7-Zip 29 | 30 | if exist "%ProgramFiles%\7-Zip\7z.exe" ( 31 | set zippath="%ProgramFiles%\7-Zip" 32 | goto VLCsetup 33 | ) else if exist "%ProgramFiles(x86)%\7-Zip\7z.exe" ( 34 | set zippath="%ProgramFiles(x86)%\7-Zip" 35 | goto VLCsetup 36 | ) else if exist "7za.exe" ( goto 7zset ) else if exist "7z.exe" ( goto 7zset ) 37 | 38 | echo. 39 | echo No paths for 7-Zip were found, attempting to download 7-zip command line version using .Net 4.5 40 | powershell.exe (new-object System.Net.WebClient).DownloadFile('http://www.7-zip.org/a/7za920.zip','.\7za920.zip') 41 | echo. 42 | if exist "7za920.zip" ( 43 | echo attempting to extract 7-Zip 44 | ) 45 | powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem';[IO.Compression.ZipFile]::ExtractToDirectory('7za920.zip', '.'); }" 46 | 47 | :7zset 48 | set zippath="7-Zip command line version" 49 | if exist "7za.exe" ( 50 | ren 7za.exe 7z.exe 51 | if exist 7za920.zip del 7za920.zip 52 | if exist 7-zip.chm del 7-zip.chm 53 | if exist license.txt del license.txt 54 | echo done 55 | ) else ( 56 | cls 57 | echo. 58 | echo. 59 | echo No paths for 7-Zip were found and downloading the command line version was unsuccessful. Make sure there is 60 | echo a working "7za.exe" in this folder. 61 | echo. 62 | echo Press any key to quit. 63 | pause >nul 64 | exit 65 | ) 66 | 67 | :VLCsetup 68 | if exist "%ProgramFiles%\VideoLAN\VLC\vlc.exe" ( 69 | set VLCpath=%ProgramFiles%\VideoLAN\VLC" 70 | goto main 71 | ) else if exist "%ProgramFiles(x86)%\VideoLAN\VLC\vlc.exe" ( 72 | set VLCpath="%ProgramFiles(x86)%\VideoLAN\VLC" 73 | goto main 74 | ) else if exist "vlc.exe" ( goto VLCset ) 75 | 76 | echo. 77 | echo No paths for VLC were found, attempting to download VLC portable version using .Net 4.5, please be patient as 78 | echo this will take a while. 79 | powershell.exe (new-object System.Net.WebClient).DownloadFile('http://get.videolan.org/vlc/2.2.4/win32/vlc-2.2.4-win32.zip','.\vlc-2.2.4-win32.zip') 80 | echo. 81 | echo attempting to extract VLC 82 | set PATH=%PATH%;%zippath% 83 | 7z e vlc-2.2.4-win32.zip vlc-2.2.4\vlc.exe 84 | 7z e vlc-2.2.4-win32.zip vlc-2.2.4\libvlc.dll 85 | 7z e vlc-2.2.4-win32.zip vlc-2.2.4\libvlccore.dll 86 | 7z x vlc-2.2.4-win32.zip vlc-2.2.4\plugins 87 | move vlc-2.2.4\plugins 88 | rd vlc-2.2.4 89 | del vlc-2.2.4-win32.zip 90 | 91 | :VLCset 92 | if exist "vlc.exe" ( 93 | set VLCpath="VLC portable version" 94 | ) else ( 95 | cls 96 | echo. 97 | echo. 98 | echo No paths for VLC were found and downloading the portable version was unsuccessful. Make sure there is a working 99 | echo "vlc-2.2.4-win32.zip" in this folder. 100 | echo. 101 | echo Press any key to quit. 102 | pause >nul 103 | exit 104 | ) 105 | 106 | :main 107 | echo. 108 | echo %VLCpath% found 109 | echo. 110 | echo %zippath% found 111 | echo. 112 | echo setting paths for VLC and 7-Zip 113 | set PATH=%PATH%;%VLCpath%;%zippath% 114 | 115 | if exist SFX*.WAV ( del SFX*.WAV ) 116 | 117 | if exist "lem_wavs.zip" ( 118 | echo. 119 | echo "lem_wavs.zip found 120 | echo. 121 | echo processing sound files 122 | 7z e lem_wavs.zip changeop.wav 123 | ren changeop.wav SFX01.WAV 124 | 7z e lem_wavs.zip door.wav 125 | ren door.wav SFX02.WAV 126 | 7z e lem_wavs.zip letsgo.wav 127 | ren letsgo.wav SFX03.WAV 128 | 7z e lem_wavs.zip mousepre.wav 129 | ren mousepre.wav SFX04.WAV 130 | 7z e lem_wavs.zip ohno.wav 131 | ren ohno.wav SFX05.WAV 132 | 7z e lem_wavs.zip electric.wav 133 | ren electric.wav SFX06.WAV 134 | 7z e lem_wavs.zip thud.wav 135 | ren thud.wav SFX07.WAV 136 | 7z e lem_wavs.zip splat.wav 137 | ren splat.wav SFX08.WAV 138 | 7z e lem_wavs.zip chain.wav 139 | ren chain.wav SFX09.WAV 140 | 7z e lem_wavs.zip chink.wav 141 | ren chink.wav SFX10.WAV 142 | 7z e lem_wavs.zip explode.wav 143 | ren explode.wav SFX12.WAV 144 | 7z e lem_wavs.zip fire.wav 145 | ren fire.wav SFX13.WAV 146 | 7z e lem_wavs.zip tenton.wav 147 | ren tenton.wav SFX14.WAV 148 | 7z e lem_wavs.zip thunk.wav 149 | ren thunk.wav SFX15.WAV 150 | 7z e lem_wavs.zip yippee.wav 151 | ren yippee.wav SFX16.WAV 152 | 7z e lem_wavs.zip glug.wav 153 | ren glug.wav SFX17.WAV 154 | 7z e lem_wavs.zip ting.wav 155 | ren ting.wav SFX18.WAV 156 | 7z e lem_wavs.zip die.wav 157 | ren die.wav SFX19.WAV 158 | ) else ( 159 | echo lem_wavs.zip not found, sound files will be processed from PlayerSource_V29.zip 160 | echo. 161 | echo processing sound files 162 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.0.wav 163 | ren BasicFX.0.wav SFX01.WAV 164 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.1.wav 165 | ren BasicFX.1.wav SFX02.WAV 166 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.2.wav 167 | ren BasicFX.2.wav SFX03.WAV 168 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.3.wav 169 | ren BasicFX.3.wav SFX04.WAV 170 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.4.wav 171 | ren BasicFX.4.wav SFX05.WAV 172 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\noises.3.wav 173 | ren noises.3.wav SFX06.WAV 174 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\noises.7.wav 175 | ren noises.7.wav SFX07.WAV 176 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.5.wav 177 | ren BasicFX.5.wav SFX08.WAV 178 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\noises.1.wav 179 | ren noises.1.wav SFX09.WAV 180 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.6.wav 181 | ren BasicFX.6.wav SFX10.WAV 182 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.9.wav 183 | ren BasicFX.9.wav SFX12.WAV 184 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\noises.2.wav 185 | ren noises.2.wav SFX13.WAV 186 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\FullFX.1.wav 187 | ren FullFX.1.wav SFX14.WAV 188 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\FullFX.2.wav 189 | ren FullFX.2.wav SFX15.WAV 190 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.7.wav 191 | ren BasicFX.7.wav SFX16.WAV 192 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\noises.4.wav 193 | ren noises.4.wav SFX17.WAV 194 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.8.wav 195 | ren BasicFX.8.wav SFX18.WAV 196 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Sounds\amiga\BasicFX.10.wav 197 | ren BasicFX.8.wav SFX19.WAV 198 | ) 199 | 200 | echo. 201 | echo processing music files 202 | md ohno 203 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Music\ohno\*.it 204 | for %%a in (*.it) do vlc -I dummy -vvv %%a --sout=#transcode{acodec=s16l,channels=2,samplerate=44100}:std{access=file,mux=wav,dst=ohno\%%a} vlc://quit 205 | cd ohno 206 | ren track_01.it TUNE01.WAV 207 | ren track_02.it TUNE02.WAV 208 | ren track_03.it TUNE03.WAV 209 | ren track_04.it TUNE04.WAV 210 | ren track_05.it TUNE05.WAV 211 | ren track_06.it TUNE06.WAV 212 | cd.. 213 | attrib *.it -r 214 | del *.it 215 | 216 | md orig 217 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Music\orig\*.it 218 | for %%a in (*.it) do vlc -I dummy -vvv %%a --sout=#transcode{acodec=s16l,channels=2,samplerate=44100}:std{access=file,mux=wav,dst=orig\%%a} vlc://quit 219 | %a} vlc://quit 220 | cd orig 221 | ren track_20.it TUNE01.WAV 222 | ren track_18.it TUNE02.WAV 223 | ren track_21.it TUNE03.WAV 224 | ren track_01.it TUNE04.WAV 225 | ren track_08.it TUNE05.WAV 226 | ren track_02.it TUNE06.WAV 227 | ren track_04.it TUNE07.WAV 228 | ren track_10.it TUNE08.WAV 229 | ren track_19.it TUNE09.WAV 230 | ren track_17.it TUNE10.WAV 231 | ren track_16.it TUNE11.WAV 232 | ren track_13.it TUNE12.WAV 233 | ren track_03.it TUNE13.WAV 234 | ren track_06.it TUNE14.WAV 235 | ren track_15.it TUNE15.WAV 236 | ren track_07.it TUNE16.WAV 237 | ren track_09.it TUNE17.WAV 238 | ren track_11.it TUNE18.WAV 239 | ren track_05.it TUNE19.WAV 240 | ren track_12.it TUNE20.WAV 241 | ren track_14.it TUNE21.WAV 242 | cd.. 243 | attrib *.it -r 244 | del *.it 245 | 246 | md xmas 247 | 7z e PlayerSource_V29.zip PlayerSourceTrad\Music\h94\*.it 248 | for %%a in (*.it) do vlc -I dummy -vvv %%a --sout=#transcode{acodec=s16l,channels=2,samplerate=44100}:std{access=file,mux=wav,dst=xmas\%%a} vlc://quit 249 | cd xmas 250 | ren track_01.it TUNE01.WAV 251 | ren track_02.it TUNE02.WAV 252 | ren track_03.it TUNE03.WAV 253 | cd.. 254 | attrib *.it -r 255 | del *.it 256 | 257 | :choice 258 | echo. 259 | set /P c=" Delete all the .zip files and any other loose files from this folder? (Y/N) " 260 | if /I "%c%" EQU "Y" ( 261 | echo. 262 | set /P c=" Are you sure? (Y/N) " 263 | if /I "%c%" EQU "Y" goto :yes 264 | if /I "%c%" EQU "N" goto :no 265 | ) 266 | if /I "%c%" EQU "N" goto :no 267 | echo. 268 | echo Invalid Option, must be (Y) or (N) 269 | goto choice 270 | 271 | :yes 272 | echo. 273 | echo OK, cleaning up zip files and loose files. 274 | if exist 7z.exe ( del 7z.exe ) 275 | if exist vlc.exe ( del vlc.exe ) 276 | if exist libvlc.dll ( del libvlc.dll ) 277 | if exist libvlccore.dll ( del libvlccore.dll ) 278 | if exist plugins ( rd plugins /s /q ) 279 | 280 | :no 281 | echo. 282 | echo Finished. Press any key to quit. 283 | pause >nul 284 | -------------------------------------------------------------------------------- /lemmings/audio/ohno/README.txt: -------------------------------------------------------------------------------- 1 | You may place custom sound files here. Whenever a valid specific 2 | sound file exists in this folder, it is used instead of 3 | corresponding ADLIB.DAT tune. All other sounds and tunes are 4 | still played by ADLIB.DAT. 5 | 6 | Format: 7 | Only PCM RIFF WAVE, only 8 or 16 bit, only 1 channel (mono). 8 | Stereo might work, but has not been tested. 9 | 10 | Below you find the file name and a youtube link to the corresponding amiga music. 11 | TUNE01.WAV https://www.youtube.com/watch?v=ojrtWBEFMHk 12 | TUNE02.WAV https://www.youtube.com/watch?v=6EkZYqb0Stc 13 | TUNE03.WAV https://www.youtube.com/watch?v=4eauv0M96zI 14 | TUNE04.WAV https://www.youtube.com/watch?v=zRVw8Mp5tfI 15 | TUNE05.WAV https://www.youtube.com/watch?v=0RPzTySHlcs 16 | TUNE06.WAV https://www.youtube.com/watch?v=P7ldSABWG6A 17 | -------------------------------------------------------------------------------- /lemmings/audio/orig/README.txt: -------------------------------------------------------------------------------- 1 | You may place custom sound files here. Whenever a valid specific 2 | sound file exists in this folder, it is used instead of 3 | corresponding ADLIB.DAT tune. All other sounds and tunes are 4 | still played by ADLIB.DAT. 5 | 6 | Format: 7 | Only PCM RIFF WAVE, only 8 or 16 bit, only 1 channel (mono). 8 | Stereo might work, but has not been tested. 9 | 10 | Below you find the file name and a youtube link to the corresponding amiga music. 11 | TUNE01.WAV https://www.youtube.com/watch?v=oUuFp646yFo 12 | TUNE02.WAV https://www.youtube.com/watch?v=UBOAhd2BWL4 13 | TUNE03.WAV https://www.youtube.com/watch?v=4XUS5X0kP6Y 14 | TUNE04.WAV https://www.youtube.com/watch?v=WbPg24abDAE 15 | TUNE05.WAV https://www.youtube.com/watch?v=Q4-kmCgHPb8 16 | TUNE06.WAV https://www.youtube.com/watch?v=IVeE4oVjAdY 17 | TUNE07.WAV https://www.youtube.com/watch?v=YKSgJS5e9s4 18 | TUNE08.WAV https://www.youtube.com/watch?v=vdL1O2Xw0oY 19 | TUNE09.WAV https://www.youtube.com/watch?v=mLZYJCBAoNM 20 | TUNE10.WAV https://www.youtube.com/watch?v=OJI9XD9Hhho 21 | TUNE11.WAV https://www.youtube.com/watch?v=-b8EzCd9w54 22 | TUNE12.WAV https://www.youtube.com/watch?v=UFkxGnILTMg 23 | TUNE13.WAV https://www.youtube.com/watch?v=WRXoZVCoPM8 24 | TUNE14.WAV https://www.youtube.com/watch?v=pmRraUfhTjs 25 | TUNE15.WAV https://www.youtube.com/watch?v=q5jYvzAX3jQ 26 | TUNE16.WAV https://www.youtube.com/watch?v=GumiSlmm4nU 27 | TUNE17.WAV https://www.youtube.com/watch?v=ovwhJTwp688 28 | TUNE18.WAV https://www.youtube.com/watch?v=jlJpl1nHHEQ 29 | TUNE19.WAV https://www.youtube.com/watch?v=fILi4wR04EA 30 | TUNE20.WAV https://www.youtube.com/watch?v=rif2ipHuLfc 31 | TUNE21.WAV https://www.youtube.com/watch?v=1XeuZgtSlMo 32 | -------------------------------------------------------------------------------- /lemmings/audio/xmas/README.txt: -------------------------------------------------------------------------------- 1 | You may place custom sound files here. Whenever a valid specific 2 | sound file exists in this folder, it is used instead of 3 | corresponding ADLIB.DAT tune. All other sounds and tunes are 4 | still played by ADLIB.DAT. 5 | 6 | Format: 7 | Only PCM RIFF WAVE, only 8 or 16 bit, only 1 channel (mono). 8 | Stereo might work, but has not been tested. 9 | 10 | Below you find the file name and a youtube link to the corresponding amiga music. 11 | TUNE01.WAV https://www.youtube.com/watch?v=8qFClKNFy28 12 | TUNE02.WAV https://www.youtube.com/watch?v=CQDQ7iG1yNM 13 | TUNE03.WAV https://www.youtube.com/watch?v=9p0qgjSJWqY 14 | -------------------------------------------------------------------------------- /lemmings/holi93/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Holiday Lemmings 1993 here: 2 | ADLIB.DAT 3 | GROUND1O.DAT 4 | GROUND2O.DAT 5 | LEVEL000.DAT 6 | LEVEL001.DAT 7 | LEVEL002.DAT 8 | LEVEL003.DAT 9 | MAIN.DAT 10 | VGAGR1.DAT 11 | VGAGR2.DAT 12 | -------------------------------------------------------------------------------- /lemmings/holi93_demo/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Holiday Lemmings 1993 Demo here: 2 | ADLIB.DAT 3 | GROUND2O.DAT 4 | LEVEL000.DAT 5 | MAIN.DAT 6 | VGAGR2.DAT 7 | -------------------------------------------------------------------------------- /lemmings/holi94/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Holiday Lemmings 1994 here: 2 | ADLIB.DAT 3 | GROUND1O.DAT 4 | GROUND2O.DAT 5 | LEVEL000.DAT 6 | LEVEL001.DAT 7 | LEVEL002.DAT 8 | LEVEL003.DAT 9 | LEVEL004.DAT 10 | LEVEL005.DAT 11 | LEVEL006.DAT 12 | LEVEL007.DAT 13 | MAIN.DAT 14 | VGAGR1.DAT 15 | VGAGR2.DAT 16 | -------------------------------------------------------------------------------- /lemmings/holi94_demo/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Holiday Lemmings 1994 Demo here: 2 | ADLIB.DAT 3 | GROUND2O.DAT 4 | LEVEL000.DAT 5 | MAIN.DAT 6 | VGAGR2.DAT 7 | -------------------------------------------------------------------------------- /lemmings/ohno/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Oh no! More Lemmings for DOS here: 2 | ADLIB.DAT 3 | DLVEL000.DAT 4 | DLVEL001.DAT 5 | DLVEL002.DAT 6 | DLVEL003.DAT 7 | DLVEL004.DAT 8 | DLVEL005.DAT 9 | DLVEL006.DAT 10 | DLVEL007.DAT 11 | DLVEL008.DAT 12 | DLVEL009.DAT 13 | DLVEL010.DAT 14 | DLVEL011.DAT 15 | DLVEL012.DAT 16 | GROUND0O.DAT 17 | GROUND1O.DAT 18 | GROUND2O.DAT 19 | GROUND3O.DAT 20 | MAIN.DAT 21 | VGAGR0.DAT 22 | VGAGR1.DAT 23 | VGAGR2.DAT 24 | VGAGR3.DAT 25 | -------------------------------------------------------------------------------- /lemmings/ohno_demo/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Oh no! More Lemmings Demo for DOS here: 2 | ADLIB.DAT 3 | DLVEL000.DAT 4 | GROUND0O.DAT 5 | GROUND1O.DAT 6 | MAIN.DAT 7 | VGAGR0.DAT 8 | VGAGR1.DAT 9 | -------------------------------------------------------------------------------- /lemmings/orig/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from original DOS Lemmings here: 2 | ADLIB.DAT 3 | GROUND0O.DAT 4 | GROUND1O.DAT 5 | GROUND2O.DAT 6 | GROUND3O.DAT 7 | GROUND4O.DAT 8 | LEVEL000.DAT 9 | LEVEL001.DAT 10 | LEVEL002.DAT 11 | LEVEL003.DAT 12 | LEVEL004.DAT 13 | LEVEL005.DAT 14 | LEVEL006.DAT 15 | LEVEL007.DAT 16 | LEVEL008.DAT 17 | LEVEL009.DAT 18 | MAIN.DAT 19 | ODDTABLE.DAT 20 | VGAGR0.DAT 21 | VGAGR1.DAT 22 | VGAGR2.DAT 23 | VGAGR3.DAT 24 | VGAGR4.DAT 25 | VGASPEC0.DAT 26 | VGASPEC1.DAT 27 | VGASPEC2.DAT 28 | VGASPEC3.DAT 29 | -------------------------------------------------------------------------------- /lemmings/orig_demo/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from original DOS Lemmings Demo here: 2 | ADLIB.DAT 3 | GROUND0O.DAT 4 | GROUND3O.DAT 5 | LEVEL000.DAT 6 | MAIN.DAT 7 | ODDTABLE.DAT 8 | VGAGR0.DAT 9 | VGAGR3.DAT 10 | -------------------------------------------------------------------------------- /lemmings/xmas91/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Xmas Lemmings 1991 here: 2 | ADLIB.DAT 3 | GROUND0O.DAT 4 | GROUND2O.DAT 5 | LEVEL000.DAT 6 | MAIN.DAT 7 | VGAGR0.DAT 8 | VGAGR2.DAT 9 | -------------------------------------------------------------------------------- /lemmings/xmas92/README.txt: -------------------------------------------------------------------------------- 1 | Copy these files from Xmas Lemmings 1992 here: 2 | ADLIB.DAT 3 | GROUND2O.DAT 4 | LEVEL000.DAT 5 | MAIN.DAT 6 | VGAGR2.DAT 7 | -------------------------------------------------------------------------------- /src/data/cursor.c: -------------------------------------------------------------------------------- 1 | #include "cursor.h" 2 | 3 | const u8 cursor_data[] = { 4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | 0xF2, 0x00, 0xF2, 0x00, 0xF2, 0x00, 0xF1, 0xF1, 0x00, 0xF3, 0x00, 0xF3, 0x00, 0xF3, 11 | 0xF3, 0x00, 0xF3, 0x00, 0xF3, 0x00, 0xF1, 0xF1, 0x00, 0xF2, 0x00, 0xF2, 0x00, 0xF2, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0 18 | }; 19 | 20 | const u8 cursor_active_data[] = { 21 | 0xF2, 0xF2, 0xF3, 0xF3, 0x00, 0x00, 0xF1, 0xF1, 0x00, 0x00, 0xF3, 0xF3, 0xF2, 0xF2, 22 | 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 23 | 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 24 | 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1, 28 | 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 32 | 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 33 | 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 34 | 0xF2, 0xF2, 0xF3, 0xF3, 0x00, 0x00, 0xF1, 0xF1, 0x00, 0x00, 0xF3, 0xF3, 0xF2, 2 35 | }; 36 | -------------------------------------------------------------------------------- /src/data/highperf_font.c: -------------------------------------------------------------------------------- 1 | #include "highperf_font.h" 2 | 3 | const u8 highperf_font_dot[] = { 4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 20 | }; 21 | 22 | const u8 highperf_font_comma[] = { 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 37 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 38 | 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 0x00 39 | }; 40 | 41 | const u8 highperf_font_questionmark[] = { 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0xF2, 0xF9, 0xF9, 0xF3, 0xF3, 0x00, 0x00, 47 | 0x00, 0xF2, 0xF2, 0xF2, 0xF2, 0xF9, 0xF3, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 49 | 0x00, 0x00, 0xF2, 0xF9, 0xF9, 0xF9, 0x00, 0x00, 50 | 0x00, 0xF2, 0xF9, 0xF2, 0xF2, 0xF9, 0x00, 0x00, 51 | 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0xF2, 0xF9, 0xF3, 0xF9, 0xF9, 0xF3, 0x00, 53 | 0x00, 0x00, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 56 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 58 | }; 59 | 60 | const u8 highperf_font_exclamationmark[] = { 61 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0xF3, 0xF3, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 77 | }; 78 | 79 | const u8 highperf_font_colon[] = { 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 92 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 94 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 95 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 96 | }; 97 | 98 | const u8 highperf_font_bracket_l[] = { 99 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 102 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 103 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0xF3, 0x00, 104 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF2, 0xF9, 0x00, 105 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 106 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 107 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 108 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 109 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 110 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 111 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF3, 0xF9, 0x00, 112 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF9, 0xF9, 0x00, 113 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xF2, 0x00, 114 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 115 | }; 116 | 117 | const u8 highperf_font_bracket_r[] = { 118 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 121 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 122 | 0x00, 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 0x00, 123 | 0x00, 0x00, 0xF2, 0xF9, 0xF3, 0x00, 0x00, 0x00, 124 | 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 125 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x00, 0xF2, 0xF3, 0x00, 0x00, 0x00, 127 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 128 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 129 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 130 | 0x00, 0x00, 0xF3, 0xF2, 0xF9, 0x00, 0x00, 0x00, 131 | 0x00, 0x00, 0xF9, 0xF9, 0xF2, 0x00, 0x00, 0x00, 132 | 0x00, 0x00, 0xF2, 0xF2, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 134 | }; 135 | 136 | const u8 highperf_font_apostrophe[] = { 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 139 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 140 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 141 | 0x00, 0x00, 0x00, 0xF9, 0xF3, 0x00, 0x00, 0x00, 142 | 0x00, 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 143 | 0x00, 0x00, 0xF2, 0xF9, 0x00, 0x00, 0x00, 0x00, 144 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 145 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 146 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 147 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 148 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 149 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 150 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 151 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 153 | }; 154 | 155 | const u8 highperf_font_quote[] = { 156 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 157 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 159 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 160 | 0x00, 0x00, 0xF9, 0xF3, 0x00, 0xF9, 0xF3, 0x00, 161 | 0x00, 0x00, 0xF2, 0xF9, 0x00, 0xF2, 0xF9, 0x00, 162 | 0x00, 0xF2, 0xF9, 0x00, 0xF2, 0xF9, 0x00, 0x00, 163 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 164 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 165 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 166 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 167 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 168 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 169 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 172 | }; 173 | -------------------------------------------------------------------------------- /src/data/patch_menu.c: -------------------------------------------------------------------------------- 1 | #include "patch_menu.h" 2 | 3 | // 21 x 12 4 | const u8 orig_mainmenu_no_dos_1p[] = { 5 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 6 | 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 8 | 0xF0, 0xF0, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 9 | 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 10 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 11 | 0xF9, 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF9, 12 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 13 | 0xF9, 0xF9, 0xF9, 0xF9, 0xFF, 0xFF, 0xF0, 0xF0, 0xF9, 0xF9, 14 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 15 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF0, 0xF9, 0xF9, 0xF9, 0xF9, 16 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 17 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 18 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 19 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 20 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 21 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 22 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 23 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 24 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 25 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 26 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 27 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 28 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 29 | 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 30 | 0xF9, 0xF9 31 | }; 32 | 33 | // 21 x 12 34 | const u8 orig_mainmenu_no_dos_2p[] = { 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0xFC, 0xF0, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 38 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 39 | 0xFE, 0xFE, 0xF0, 0xF0, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 40 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 41 | 0xFE, 0xFE, 0xFE, 0xF0, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 42 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 43 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 44 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 45 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 46 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 47 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 48 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 49 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 50 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 51 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 52 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 53 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 54 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 55 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 56 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 57 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 58 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 59 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 60 | 0xFE, 0xFE 61 | }; 62 | 63 | // 21 x 12 64 | const u8 orig_mainmenu_no_dos_settings[] = { 65 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 66 | 0xF0, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 67 | 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFD, 68 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 69 | 0xFD, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFD, 70 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 71 | 0xFD, 0xFD, 0xFD, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xFD, 72 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 73 | 0xFD, 0xFD, 0xFD, 0xFD, 0xF0, 0xF0, 0xFD, 0xFD, 0xFD, 0xFD, 74 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 75 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 76 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 77 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 78 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 79 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 80 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 81 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 82 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 83 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 84 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 85 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 86 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 87 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 88 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 89 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 90 | 0xFD, 0xFD 91 | }; 92 | 93 | // 17 x 9 94 | const u8 xmas_mainmenu_no_dos_1p[] = { 95 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF9, 0xF9, 96 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xFF, 0xFF, 0xFF, 97 | 0xFF, 0xF0, 0xF0, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 98 | 0xF9, 0xF9, 0xF9, 0xF9, 0xFF, 0xFF, 0xF0, 0xF0, 0xF9, 0xF9, 99 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 100 | 0xF9, 0xF0, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 101 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 102 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 103 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 104 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 105 | 0xF0, 0xF0, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF0, 106 | 0xF0, 0xF0, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xF9, 107 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF0, 0xF0, 0xFA, 0xFA, 0xFA, 0xFA, 108 | 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xF9, 0xF9, 0xF9, 0xF9, 109 | 0xF0, 0xFA, 0xFA, 0xFA, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 110 | 0xFB, 0xFB, 0xFB 111 | }; 112 | 113 | // 15 x 7 114 | const u8 xmas_mainmenu_no_dos_2p[] = { 115 | 0xF3, 0xFE, 0xFE, 0xFE, 0xF3, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 116 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xF3, 0xF3, 0xF3, 0xF3, 117 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 118 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 119 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 120 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 121 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 122 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 123 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 124 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 125 | 0xFD, 0xFD, 0xFD, 0xFD, 0xFD 126 | }; 127 | 128 | // 17 x 9 129 | const u8 holi_mainmenu_no_dos_1p[] = { 130 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF6, 0xF6, 131 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xFF, 0xFF, 0xFF, 132 | 0xFF, 0xF0, 0xF0, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 133 | 0xF6, 0xF6, 0xF6, 0xF6, 0xFF, 0xFF, 0xF0, 0xF0, 0xF6, 0xF6, 134 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 135 | 0xF6, 0xF0, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 136 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 137 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 138 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 139 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 140 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 141 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 142 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 143 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 144 | 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 145 | 0xF6, 0xF6, 0xF6 146 | }; 147 | -------------------------------------------------------------------------------- /src/data_cache.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "data_cache.h" 5 | #include "gamespecific.h" 6 | #include "gamespecific_2p.h" 7 | #include "import_level.h" 8 | #include "settings.h" 9 | 10 | void update_data_cache_old(u8 game, u8 multiplayer, const char* level_names) { 11 | u8 version = CACHE_FILE_VERSION; 12 | char cachefilename[64]; 13 | u16 num_of_levels = 0; 14 | if (!level_names) { 15 | return; 16 | } 17 | if (!multiplayer) { 18 | num_of_levels = (u16)import[game].num_of_difficulties 19 | * (u16)import[game].num_of_level_per_difficulty; 20 | }else{ 21 | num_of_levels = multiplayer; 22 | } 23 | sprintf(cachefilename, "%s/CACHE_%02X.DAT",PATH_ROOT, game + (multiplayer?0x80:0)); 24 | FILE* cachefile = fopen(cachefilename, "wb"); 25 | if (!cachefile) { 26 | return; 27 | } 28 | fwrite(&version, 1, 1, cachefile); 29 | if (multiplayer) { 30 | fwrite(&multiplayer, 1, 1, cachefile); 31 | } 32 | fwrite(level_names, 1, 33*num_of_levels, cachefile); 33 | fclose(cachefile); 34 | } 35 | 36 | u8 read_data_cache_old(u8 game, u8 multiplayer, char* level_names) { 37 | u8 version = 0; 38 | char cachefilename[64]; 39 | u16 num_of_levels = 0; 40 | if (!level_names) { 41 | return 0; 42 | } 43 | if (!multiplayer) { 44 | num_of_levels = (u16)import[game].num_of_difficulties 45 | * (u16)import[game].num_of_level_per_difficulty; 46 | }else{ 47 | return 0; 48 | } 49 | sprintf(cachefilename, "%s/CACHE_%02X.DAT",PATH_ROOT, game + (multiplayer?0x80:0)); 50 | FILE* cachefile = fopen(cachefilename, "rb"); 51 | if (!cachefile) { 52 | return 0; 53 | } 54 | fread(&version, 1, 1, cachefile); 55 | if (version > CACHE_FILE_VERSION) { 56 | return 0; 57 | } 58 | if (multiplayer) { 59 | fread(&num_of_levels, 1, 1, cachefile); 60 | if (num_of_levels > import_2p[game].num_levels) { 61 | num_of_levels = import_2p[game].num_levels; 62 | } 63 | } 64 | u16 size = fread(level_names, 1, 33*num_of_levels, cachefile); 65 | fclose(cachefile); 66 | if (size != 33*num_of_levels) { 67 | return 0; 68 | } 69 | return (multiplayer?num_of_levels:1); 70 | } 71 | 72 | void update_data_cache(const u8* games, const char* level_names, u16 overall_num_of_levels) { 73 | char cachefilename[64]; 74 | sprintf(cachefilename, "%s/CACHE.DAT",PATH_ROOT); 75 | FILE* cachefile = fopen(cachefilename, "wb"); 76 | if (!cachefile) { 77 | return; 78 | } 79 | fwrite(games, 1, LEMMING_GAMES, cachefile); 80 | fwrite(level_names, 1, 33*overall_num_of_levels, cachefile); 81 | fclose(cachefile); 82 | 83 | int i = 0; 84 | for (i=0;i 4 | #include "decode.h" 5 | #include "adlib.h" 6 | 7 | #define FLAG_O (1<<11) 8 | #define FLAG_D (1<<10) 9 | #define FLAG_I (1<< 9) 10 | #define FLAG_T (1<< 8) 11 | #define FLAG_S (1<< 7) 12 | #define FLAG_Z (1<< 6) 13 | #define FLAG_A (1<< 4) 14 | #define FLAG_P (1<< 2) 15 | #define FLAG_C (1<< 1) 16 | #define STACK_SIZE 0x100 17 | 18 | class Emu8086 { 19 | private: 20 | static const u8 parity[]; 21 | struct Registers { 22 | u16 ax; 23 | u16 cx; 24 | u16 dx; 25 | u16 bx; 26 | 27 | u16 sp; // stack pointer 28 | u16 bp; // base pointer 29 | u16 si; // source index 30 | u16 di; // destination index 31 | 32 | 33 | u16 cs; // code segment 34 | u16 ds; // data segment 35 | u16 ss; // stack segment 36 | u16 es; // extra segment 37 | 38 | u16 ip; // instruction pointer 39 | u16 sr; // status register (flags) 40 | } registers; 41 | u8* memory; 42 | u16 stack[STACK_SIZE]; 43 | Adlib::Module* module; 44 | 45 | int x86_step(); 46 | int in_out(u8 direction, u8 width); 47 | inline void parse_mod_rm(void** other, u8 mod_rm, u8 width); 48 | inline void parse_mod_reg_rm(void** reg, void** other, u8 width); 49 | inline int pop(u16* val); 50 | inline int push(u16 val); 51 | inline void add(void* dest, void* source, int width); 52 | inline void _and(void* dest, void* source, int width); 53 | inline void _xor(void* dest, void* source, int width); 54 | inline void _or(void* dest, void* source, int width); 55 | inline void sub(void* dest, void* source, int width); 56 | inline void dec(void* dest, int width); 57 | inline void inc(void* dest, int width); 58 | inline void cmp(void* dest, void* source, int width); 59 | int and1(u8 direction, u8 width); 60 | int xor1(u8 direction, u8 width); 61 | int xor2(u8 direction, u8 width); 62 | int and2(u8 direction, u8 width); 63 | int or1(u8 direction, u8 width); 64 | int cmp1(u8 direction, u8 width); 65 | int add1(u8 direction, u8 width); 66 | int cmp2(u8 direction, u8 width); 67 | int mov1(u8 direction, u8 width); 68 | int sbb2(u8 direction, u8 width); 69 | int add2(u8 direction, u8 width); 70 | int sub2(u8 direction, u8 width); 71 | int or2(u8 direction, u8 width); 72 | int jz(u8 direction, u8 width); 73 | int jo(u8 direction, u8 width); 74 | int js(u8 direction, u8 width); 75 | int jl(u8 direction, u8 width); 76 | int jmp(u8 direction, u8 width); 77 | int push1(u8 direction, u8 width); 78 | int push2(u8 direction, u8 width); 79 | int pop1(u8 direction, u8 width); 80 | int pop2(u8 direction, u8 width); 81 | int ret(u8 direction, u8 width); 82 | int iret(u8 direction, u8 width); 83 | int mov_al(u8 direction, u8 width); 84 | int mov_l(u8 direction, u8 width); 85 | int les(u8 direction, u8 width); 86 | int grp1(u8 direction, u8 width); 87 | int grp2(u8 direction, u8 width); 88 | int grp4(u8 direction, u8 width); 89 | int mov_general_purpose(u8 direction, u8 width); 90 | int xchg1(u8 direction, u8 width); 91 | int mov_special_purpose(u8 direction, u8 width); 92 | int loop(u8 direction, u8 width); 93 | int inc1(u8 direction, u8 width); 94 | int inc2(u8 direction, u8 width); 95 | int lods(u8 direction, u8 width); 96 | int cmc(u8 direction, u8 width); 97 | int lea(u8 direction, u8 width); 98 | static int (Emu8086::*execute_opcode[64])(u8 direction, u8 width); 99 | public: 100 | Emu8086(unsigned long sample_rate); 101 | int load_adlib_data(struct Data* decoded_adlib_dat); 102 | int call_adlib(u16 ax); 103 | void query_opl_samples(unsigned long samples, int module); 104 | void free_adlib_data(); 105 | ~Emu8086(); 106 | }; 107 | #endif 108 | -------------------------------------------------------------------------------- /src/import/adlib/adlib.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * OPL implementation from DOSBox. Slightly modified for Lemmings for 3DS. 3 | */ 4 | 5 | /* 6 | * Copyright (C) 2002-2010 The DOSBox Team 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 | */ 22 | 23 | /* $Id: adlib.cpp,v 1.42 2009-11-03 20:17:42 qbix79 Exp $ */ 24 | 25 | #include 26 | #include 27 | //#include 28 | #include "adlib.h" 29 | #include "import_adlib.h" 30 | 31 | //#include "setup.h" 32 | //#include "mapper.h" 33 | //#include "mem.h" 34 | #include "dbopl.h" 35 | 36 | #define RAW_SIZE 1024 37 | 38 | 39 | /* 40 | Main Adlib implementation 41 | 42 | */ 43 | 44 | namespace Adlib { 45 | 46 | /* 47 | Chip 48 | */ 49 | 50 | bool Chip::Write( u32 reg, u8 val ) { 51 | switch ( reg ) { 52 | case 0x02: 53 | timer[0].counter = val; 54 | return true; 55 | case 0x03: 56 | timer[1].counter = val; 57 | return true; 58 | case 0x04: 59 | double time; 60 | time = 0; 61 | if ( val & 0x80 ) { 62 | timer[0].Reset( time ); 63 | timer[1].Reset( time ); 64 | } else { 65 | timer[0].Update( time ); 66 | timer[1].Update( time ); 67 | if ( val & 0x1 ) { 68 | timer[0].Start( time, 80 ); 69 | } else { 70 | timer[0].Stop( ); 71 | } 72 | timer[0].masked = (val & 0x40) > 0; 73 | if ( timer[0].masked ) 74 | timer[0].overflow = false; 75 | if ( val & 0x2 ) { 76 | timer[1].Start( time, 320 ); 77 | } else { 78 | timer[1].Stop( ); 79 | } 80 | timer[1].masked = (val & 0x20) > 0; 81 | if ( timer[1].masked ) 82 | timer[1].overflow = false; 83 | 84 | } 85 | return true; 86 | } 87 | return false; 88 | } 89 | 90 | 91 | u8 Chip::Read( ) { 92 | double time( 0.0 ); 93 | timer[0].Update( time ); 94 | timer[1].Update( time ); 95 | u8 ret = 0; 96 | //Overflow won't be set if a channel is masked 97 | if ( timer[0].overflow ) { 98 | ret |= 0x40; 99 | ret |= 0x80; 100 | } 101 | if ( timer[1].overflow ) { 102 | ret |= 0x20; 103 | ret |= 0x80; 104 | } 105 | return ret; 106 | 107 | } 108 | 109 | void Module::CacheWrite( u32 reg, u8 val ) { 110 | //Store it into the cache 111 | cache[ reg ] = val; 112 | } 113 | 114 | void Module::DualWrite( u8 index, u8 reg, u8 val ) { 115 | //Make sure you don't use opl3 features 116 | //Don't allow write to disable opl3 117 | if ( reg == 5 ) { 118 | return; 119 | } 120 | //Only allow 4 waveforms 121 | if ( reg >= 0xE0 ) { 122 | val &= 3; 123 | } 124 | //Write to the timer? 125 | if ( chip[index].Write( reg, val ) ) 126 | return; 127 | //Enabling panning 128 | if ( reg >= 0xc0 && reg <=0xc8 ) { 129 | val &= 0x0f; 130 | val |= index ? 0xA0 : 0x50; 131 | } 132 | u32 fullReg = reg + (index ? 0x100 : 0); 133 | handler->WriteReg( fullReg, val ); 134 | CacheWrite( fullReg, val ); 135 | } 136 | 137 | 138 | void Module::PortWrite( unsigned long port, unsigned long val, unsigned long iolen ) { 139 | //Maybe only enable with a keyon? 140 | if ( port&1 ) { 141 | if ( !chip[0].Write( reg.normal, val ) ) { 142 | handler->WriteReg( reg.normal, val ); 143 | CacheWrite( reg.normal, val ); 144 | } 145 | } else { 146 | //Ask the handler to write the address 147 | //Make sure to clip them in the right range 148 | reg.normal = handler->WriteAddr( port, val ) & 0xff; 149 | } 150 | } 151 | 152 | 153 | unsigned long Module::PortRead( unsigned long port, unsigned long iolen ) { 154 | //We allocated 4 ports, so just return -1 for the higher ones 155 | if ( !(port & 3 ) ) { 156 | //Make sure the low bits are 6 on opl2 157 | return chip[0].Read() | 0x6; 158 | } else { 159 | return 0xff; 160 | } 161 | return 0; 162 | } 163 | 164 | 165 | void Module::Init() { 166 | } 167 | 168 | }; //namespace 169 | 170 | namespace Adlib { 171 | 172 | Module::Module( unsigned long sample_rate ) { //Section* configuration ) : Module_base(configuration) { 173 | reg.dual[0] = 0; 174 | reg.dual[1] = 0; 175 | reg.normal = 0; 176 | handler = 0; 177 | 178 | //Section_prop * section=static_cast(configuration); 179 | unsigned long rate = sample_rate; //section->Get_int("oplrate"); 180 | //Make sure we can't select lower than 8000 to prevent fixed point issues 181 | if ( rate < 8000 ) 182 | rate = 8000; 183 | //std::string oplemu( section->Get_string( "oplemu" ) ); 184 | 185 | handler = new DBOPL::Handler(); 186 | handler->Init( rate ); 187 | Init(); 188 | } 189 | 190 | Module::~Module() { 191 | if ( handler ) { 192 | delete handler; 193 | } 194 | } 195 | 196 | }; //Adlib Namespace 197 | -------------------------------------------------------------------------------- /src/import/adlib/adlib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * OPL implementation from DOSBox. Slightly modified for Lemmings for 3DS. 3 | */ 4 | 5 | /* 6 | * Copyright (C) 2002-2010 The DOSBox Team 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 | */ 22 | 23 | /* $Id: adlib.h,v 1.5 2009-04-28 21:45:43 c2woody Exp $ */ 24 | 25 | #ifndef DOSBOX_ADLIB_H 26 | #define DOSBOX_ADLIB_H 27 | #include 28 | #include <3ds.h> 29 | 30 | namespace Adlib { 31 | 32 | struct Timer { 33 | double start; 34 | double delay; 35 | bool enabled, overflow, masked; 36 | u8 counter; 37 | Timer() { 38 | masked = false; 39 | overflow = false; 40 | enabled = false; 41 | counter = 0; 42 | delay = 0; 43 | } 44 | //Call update before making any further changes 45 | void Update( double time ) { 46 | if ( !enabled || !delay ) 47 | return; 48 | double deltaStart = time - start; 49 | //Only set the overflow flag when not masked 50 | if ( deltaStart >= 0 && !masked ) { 51 | overflow = 1; 52 | } 53 | } 54 | //On a reset make sure the start is in sync with the next cycle 55 | void Reset(const double& time ) { 56 | overflow = false; 57 | if ( !delay || !enabled ) 58 | return; 59 | double delta = (time - start); 60 | double rem = fmod( delta, delay ); 61 | double next = delay - rem; 62 | start = time + next; 63 | } 64 | void Stop( ) { 65 | enabled = false; 66 | } 67 | void Start( const double& time, signed long scale ) { 68 | //Don't enable again 69 | if ( enabled ) { 70 | return; 71 | } 72 | enabled = true; 73 | delay = 0.001 * (256 - counter ) * scale; 74 | start = time + delay; 75 | } 76 | 77 | }; 78 | 79 | struct Chip { 80 | //Last selected register 81 | Timer timer[2]; 82 | //Check for it being a write to the timer 83 | bool Write( u32 addr, u8 val ); 84 | //Read the current timer state, will use current double 85 | u8 Read( ); 86 | }; 87 | 88 | class Handler { 89 | public: 90 | //Write an address to a chip, returns the address the chip sets 91 | virtual u32 WriteAddr( u32 port, u8 val ) = 0; 92 | //Write to a specific register in the chip 93 | virtual void WriteReg( u32 addr, u8 val ) = 0; 94 | //Generate a certain amount of samples 95 | virtual void Generate( unsigned long samples, unsigned long param ) = 0; 96 | //Initialize at a specific sample rate and mode 97 | virtual void Init( unsigned long rate ) = 0; 98 | virtual ~Handler() { 99 | } 100 | }; 101 | 102 | //The cache for 2 chips or an opl3 103 | typedef u8 RegisterCache[512]; 104 | 105 | class Module { 106 | // IO_ReadHandleObject ReadHandler[3]; 107 | // IO_WriteHandleObject WriteHandler[3]; 108 | 109 | //Last selected address in the chip for the different modes 110 | union { 111 | u32 normal; 112 | u8 dual[2]; 113 | } reg; 114 | void CacheWrite( u32 reg, u8 val ); 115 | void DualWrite( u8 index, u8 reg, u8 val ); 116 | public: 117 | 118 | Handler* handler; //Handler that will generate the sound 119 | RegisterCache cache; 120 | Chip chip[2]; 121 | 122 | //Handle port writes 123 | void PortWrite( unsigned long port, unsigned long val, unsigned long iolen ); 124 | unsigned long PortRead( unsigned long port, unsigned long iolen ); 125 | void Init(); 126 | 127 | Module(unsigned long sample_rate); // Section* configuration); 128 | ~Module(); 129 | }; 130 | 131 | 132 | } //Adlib namespace 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /src/import/adlib/dbopl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * OPL implementation from DOSBox. Slightly modified for Lemmings for 3DS. 3 | */ 4 | 5 | /* 6 | * Copyright (C) 2002-2010 The DOSBox Team 7 | * 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21 | */ 22 | 23 | #include <3ds.h> 24 | #include "adlib.h" 25 | 26 | //Use 8 handlers based on a small logarithmic wavetabe and an exponential table for volume 27 | #define WAVE_HANDLER 10 28 | //Use a logarithmic wavetable with an exponential table for volume 29 | #define WAVE_TABLELOG 11 30 | //Use a linear wavetable with a multiply table for volume 31 | #define WAVE_TABLEMUL 12 32 | 33 | //Select the type of wave generator routine 34 | #define DBOPL_WAVE WAVE_TABLEMUL 35 | 36 | namespace DBOPL { 37 | 38 | struct Chip; 39 | struct Operator; 40 | struct Channel; 41 | 42 | #if (DBOPL_WAVE == WAVE_HANDLER) 43 | typedef signed long ( DB_FASTCALL *WaveHandler) ( unsigned long i, unsigned long volume ); 44 | #endif 45 | 46 | typedef signed long ( DBOPL::Operator::*VolumeHandler) ( ); 47 | typedef Channel* ( DBOPL::Channel::*SynthHandler) ( Chip* chip, u32 samples, s32* output ); 48 | 49 | //Different synth modes that can generate blocks of data 50 | typedef enum { 51 | sm2AM, 52 | sm2FM, 53 | sm3AM, 54 | sm3FM, 55 | sm4Start, 56 | sm3FMFM, 57 | sm3AMFM, 58 | sm3FMAM, 59 | sm3AMAM, 60 | sm6Start, 61 | sm2Percussion, 62 | sm3Percussion, 63 | } SynthMode; 64 | 65 | //Shifts for the values contained in chandata variable 66 | enum { 67 | SHIFT_KSLBASE = 16, 68 | SHIFT_KEYCODE = 24, 69 | }; 70 | 71 | struct Operator { 72 | public: 73 | //Masks for operator 20 values 74 | enum { 75 | MASK_KSR = 0x10, 76 | MASK_SUSTAIN = 0x20, 77 | MASK_VIBRATO = 0x40, 78 | MASK_TREMOLO = 0x80, 79 | }; 80 | 81 | typedef enum { 82 | OFF, 83 | RELEASE, 84 | SUSTAIN, 85 | DECAY, 86 | ATTACK, 87 | } State; 88 | 89 | VolumeHandler volHandler; 90 | 91 | #if (DBOPL_WAVE == WAVE_HANDLER) 92 | WaveHandler waveHandler; //Routine that generate a wave 93 | #else 94 | s16* waveBase; 95 | u32 waveMask; 96 | u32 waveStart; 97 | #endif 98 | u32 waveIndex; //WAVE_signed long shifted counter of the frequency index 99 | u32 waveAdd; //The base frequency without vibrato 100 | u32 waveCurrent; //waveAdd + vibratao 101 | 102 | u32 chanData; //Frequency/octave and derived data coming from whatever channel controls this 103 | u32 freqMul; //Scale channel frequency with this, TODO maybe remove? 104 | u32 vibrato; //Scaled up vibrato strength 105 | s32 sustainLevel; //When stopping at sustain level stop here 106 | s32 totalLevel; //totalLevel is added to every generated volume 107 | u32 currentLevel; //totalLevel + tremolo 108 | s32 volume; //The currently active volume 109 | 110 | u32 attackAdd; //Timers for the different states of the envelope 111 | u32 decayAdd; 112 | u32 releaseAdd; 113 | u32 rateIndex; //Current position of the evenlope 114 | 115 | u8 rateZero; //signed long for the different states of the envelope having no changes 116 | u8 keyOn; //Bitmask of different values that can generate keyon 117 | //Registers, also used to check for changes 118 | u8 reg20, reg40, reg60, reg80, regE0; 119 | //Active part of the envelope we're in 120 | u8 state; 121 | //0xff when tremolo is enabled 122 | u8 tremoloMask; 123 | //Strength of the vibrato 124 | u8 vibStrength; 125 | //Keep track of the calculated KSR so we can check for changes 126 | u8 ksr; 127 | private: 128 | void SetState( u8 s ); 129 | void UpdateAttack( const Chip* chip ); 130 | void UpdateRelease( const Chip* chip ); 131 | void UpdateDecay( const Chip* chip ); 132 | public: 133 | void UpdateAttenuation(); 134 | void UpdateRates( const Chip* chip ); 135 | void UpdateFrequency( ); 136 | 137 | void Write20( const Chip* chip, u8 val ); 138 | void Write40( const Chip* chip, u8 val ); 139 | void Write60( const Chip* chip, u8 val ); 140 | void Write80( const Chip* chip, u8 val ); 141 | void WriteE0( const Chip* chip, u8 val ); 142 | 143 | bool Silent() const; 144 | void Prepare( const Chip* chip ); 145 | 146 | void KeyOn( u8 mask); 147 | void KeyOff( u8 mask); 148 | 149 | template< State state> 150 | signed long TemplateVolume( ); 151 | 152 | s32 RateForward( u32 add ); 153 | unsigned long ForwardWave(); 154 | unsigned long ForwardVolume(); 155 | 156 | signed long GetSample( signed long modulation ); 157 | signed long GetWave( unsigned long index, unsigned long vol ); 158 | public: 159 | Operator(); 160 | }; 161 | 162 | struct Channel { 163 | Operator op[2]; 164 | inline Operator* Op( unsigned long index ) { 165 | return &( ( this + (index >> 1) )->op[ index & 1 ]); 166 | } 167 | SynthHandler synthHandler; 168 | u32 chanData; //Frequency/octave and derived values 169 | s32 old[2]; //Old data for feedback 170 | 171 | u8 feedback; //Feedback shift 172 | u8 regB0; //Register values to check for changes 173 | u8 regC0; 174 | //This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel 175 | u8 fourMask; 176 | s8 maskLeft; //Sign extended values for both channel's panning 177 | s8 maskRight; 178 | 179 | //Forward the channel data to the operators of the channel 180 | void SetChanData( const Chip* chip, u32 data ); 181 | //Change in the chandata, check for new values and if we have to forward to operators 182 | void UpdateFrequency( const Chip* chip, u8 fourOp ); 183 | void WriteA0( const Chip* chip, u8 val ); 184 | void WriteB0( const Chip* chip, u8 val ); 185 | void WriteC0( const Chip* chip, u8 val ); 186 | void ResetC0( const Chip* chip ); 187 | 188 | //call this for the first channel 189 | template< bool opl3Mode > 190 | void GeneratePercussion( Chip* chip, s32* output ); 191 | 192 | //Generate blocks of data in specific modes 193 | template 194 | Channel* BlockTemplate( Chip* chip, u32 samples, s32* output ); 195 | Channel(); 196 | }; 197 | 198 | struct Chip { 199 | //This is used as the base counter for vibrato and tremolo 200 | u32 lfoCounter; 201 | u32 lfoAdd; 202 | 203 | 204 | u32 noiseCounter; 205 | u32 noiseAdd; 206 | u32 noiseValue; 207 | 208 | //Frequency scales for the different multiplications 209 | u32 freqMul[16]; 210 | //Rates for decay and release for rate of this chip 211 | u32 linearRates[76]; 212 | //Best match attack rates for the rate of this chip 213 | u32 attackRates[76]; 214 | 215 | //18 channels with 2 operators each 216 | Channel chan[18]; 217 | 218 | u8 reg104; 219 | u8 reg08; 220 | u8 reg04; 221 | u8 regBD; 222 | u8 vibratoIndex; 223 | u8 tremoloIndex; 224 | s8 vibratoSign; 225 | u8 vibratoShift; 226 | u8 tremoloValue; 227 | u8 vibratoStrength; 228 | u8 tremoloStrength; 229 | //Mask for allowed wave forms 230 | u8 waveFormMask; 231 | //0 or -1 when enabled 232 | s8 opl3Active; 233 | 234 | //Return the maximum amount of samples before and LFO change 235 | u32 ForwardLFO( u32 samples ); 236 | u32 ForwardNoise(); 237 | 238 | void WriteBD( u8 val ); 239 | void WriteReg(u32 reg, u8 val ); 240 | 241 | u32 WriteAddr( u32 port, u8 val ); 242 | 243 | void GenerateBlock2( unsigned long samples, s32* output ); 244 | void GenerateBlock3( unsigned long samples, s32* output ); 245 | 246 | void Generate( u32 samples, unsigned long param ); 247 | void Setup( u32 r ); 248 | 249 | Chip(); 250 | }; 251 | 252 | struct Handler : public Adlib::Handler { 253 | DBOPL::Chip chip; 254 | virtual u32 WriteAddr( u32 port, u8 val ); 255 | virtual void WriteReg( u32 addr, u8 val ); 256 | virtual void Generate( unsigned long samples, unsigned long param ); 257 | virtual void Init( unsigned long rate ); 258 | }; 259 | 260 | 261 | }; //Namespace 262 | -------------------------------------------------------------------------------- /src/import/decode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include <3ds.h> 4 | #include "decode.h" 5 | 6 | static inline s8 get_bit(s8** in, s8* cur_bit_mask, s8* start_bits, s8* in_data) { 7 | // no check for nullptr to increase speed 8 | s8 ret; 9 | if (*start_bits == 0 || *cur_bit_mask == 0) { 10 | if (*in < in_data) { 11 | return 0; // ERROR: end of stream 12 | } 13 | *cur_bit_mask = 1; 14 | --*in; 15 | *start_bits = -1; 16 | }else if (*start_bits > 0) { 17 | --*start_bits; 18 | } 19 | ret = (((**in) & (*cur_bit_mask))?1:0); 20 | *cur_bit_mask <<= 1; 21 | return ret; 22 | } 23 | 24 | static inline s16 get_bits(s8** in, s8* cur_bit_mask, s8* start_bits, s8* in_data, s8 number) { 25 | s8 i; 26 | s16 ret = 0; 27 | for (i=number-1;i>=0;i--) { 28 | ret |= get_bit(in, cur_bit_mask, start_bits, in_data)< 0) { 39 | u16 size = 0; 40 | if (fseek(input,8,SEEK_CUR)) { 41 | return 0; // unexpected EOF or error reading file 42 | } 43 | if (2 != fread(&size,1,2,input)) { 44 | return 0; // unexpected EOF or error reading file 45 | } 46 | size = (size>>8) | (size<<8); 47 | if (size < 10) { 48 | return 0; // invalid file format 49 | } 50 | size-=10; 51 | if (fseek(input,size,SEEK_CUR)) { 52 | return 0; // unexpected EOF or error reading file 53 | } 54 | section--; 55 | } 56 | return 1; 57 | } 58 | 59 | 60 | struct Data* decompress_cur_section(FILE* input) { 61 | struct CompressionHeader header; 62 | memset(&header,0,sizeof(struct CompressionHeader)); 63 | if (1 != fread(&(header.start_bits),1,1,input)) { 64 | return 0; // unexpected EOF or error reading file 65 | } 66 | if (1 != fread(&(header.checksum),1,1,input)) { 67 | return 0; // unexpected EOF or error reading file 68 | } 69 | if (fseek(input,2,SEEK_CUR)) { 70 | return 0; // unexpected EOF or error reading file 71 | } 72 | if (2 != fread(&(header.size_dec),1,2,input)) { 73 | return 0; // unexpected EOF or error reading file 74 | } 75 | if (fseek(input,2,SEEK_CUR)) { 76 | return 0; // unexpected EOF or error reading file 77 | } 78 | if (2 != fread(&(header.size_enc),1,2,input)) { 79 | return 0; // unexpected EOF or error reading file 80 | } 81 | header.size_dec = (header.size_dec>>8) | (header.size_dec<<8); 82 | header.size_enc = (header.size_enc>>8) | (header.size_enc<<8); 83 | if (header.size_enc < 10) { 84 | return 0; // invalid file format 85 | } 86 | header.size_enc-=10; 87 | if (header.start_bits < 0 || header.start_bits > 8) { 88 | return 0; // invalid file format 89 | } 90 | struct Data* in = (struct Data*)malloc(sizeof(struct Data) + header.size_enc); 91 | if (in == 0) { 92 | return 0; // out of memory 93 | } 94 | in->size = header.size_enc; 95 | if (header.size_enc != fread(in->data,1,header.size_enc,input)) { 96 | free(in); 97 | return 0; // unexpected EOF or error reading file 98 | } 99 | s8* in_ptr = in->data + header.size_enc - 1; 100 | s8 in_mask = 1; 101 | 102 | s8 checksum = 0; 103 | u16 i; 104 | for (i=0;isize;i++) { 105 | checksum ^= in->data[i]; 106 | } 107 | if (checksum != header.checksum) { 108 | // ignore... TODO: maybe don't ignore...? 109 | } 110 | 111 | struct Data* data = (struct Data*)malloc(sizeof(struct Data) + header.size_dec); 112 | if (data == 0) { 113 | free(in); 114 | return 0; // out of memory 115 | } 116 | data->size = header.size_dec; 117 | 118 | s8* data_ptr = data->data + header.size_dec; 119 | while (data_ptr > data->data) { 120 | s16 type; 121 | if (get_bit(&in_ptr, &in_mask, &(header.start_bits),in->data)!=0) { 122 | type = get_bits(&in_ptr, &in_mask, &(header.start_bits),in->data,2)+2; 123 | }else{ 124 | type = get_bit(&in_ptr, &in_mask, &(header.start_bits),in->data); 125 | } 126 | switch (type) { 127 | case 0: 128 | { 129 | s16 n; 130 | s16 i; 131 | n = get_bits(&in_ptr, &in_mask, &(header.start_bits),in->data,3)+1; 132 | if (data_ptr-n < data->data) { 133 | free(data); 134 | free(in); 135 | return 0; // ERROR while decoding 136 | } 137 | for (i=0;idata,8); 140 | } 141 | } 142 | break; 143 | case 1: 144 | { 145 | s16 offset; 146 | s16 j; 147 | offset = get_bits(&in_ptr, &in_mask, &(header.start_bits),in->data,8)+1; 148 | if (data_ptr-2 < data->data) { 149 | free(data); 150 | free(in); 151 | return 0; // ERROR while decoding 152 | } 153 | for (j=0;j<2;j++) { 154 | data_ptr--; 155 | *data_ptr = data_ptr[offset]; 156 | } 157 | } 158 | break; 159 | case 2: 160 | { 161 | s16 offset; 162 | s16 j; 163 | offset = get_bits(&in_ptr, &in_mask, &(header.start_bits),in->data,9)+1; 164 | if (data_ptr-3 < data->data) { 165 | free(data); 166 | free(in); 167 | return 0; // ERROR while decoding 168 | } 169 | for (j=0;j<3;j++) { 170 | data_ptr--; 171 | *data_ptr = data_ptr[offset]; 172 | } 173 | } 174 | break; 175 | case 3: 176 | { 177 | s16 offset; 178 | s16 j; 179 | offset = get_bits(&in_ptr, &in_mask, &(header.start_bits),in->data,10)+1; 180 | if (data_ptr-4 < data->data) { 181 | free(data); 182 | free(in); 183 | return 0; // ERROR while decoding 184 | } 185 | for (j=0;j<4;j++) { 186 | data_ptr--; 187 | *data_ptr = data_ptr[offset]; 188 | } 189 | } 190 | break; 191 | case 4: 192 | { 193 | s16 offset; 194 | s16 j; 195 | s16 n = get_bits(&in_ptr, &in_mask, &(header.start_bits),in->data,8)+1; 196 | offset = get_bits(&in_ptr, &in_mask, &(header.start_bits),in->data,12)+1; 197 | if (data_ptr-n < data->data) { 198 | free(data); 199 | free(in); 200 | return 0; // ERROR while decoding 201 | } 202 | for (j=0;jdata,8)+9; 213 | if (data_ptr-n < data->data) { 214 | free(data); 215 | free(in); 216 | return 0; // ERROR while decoding 217 | } 218 | for (i=0;idata,8); 221 | } 222 | } 223 | break; 224 | default: 225 | free(data); 226 | free(in); 227 | return 0; // ERROR while decoding 228 | } 229 | } 230 | 231 | free(in); 232 | return data; 233 | } 234 | 235 | int planar_image_to_pixmap( 236 | u8* pixmap_image, 237 | void* planar_image, 238 | u16 num_of_pixels, 239 | u16 mask_offset) { 240 | u8 plane; 241 | u16 pos; 242 | u8 in_mask = 0x80; 243 | u8 out_mask = 0x01; 244 | if (!planar_image || !pixmap_image || !num_of_pixels) { 245 | return 0; // error 246 | } 247 | memset(pixmap_image,0,num_of_pixels); 248 | u16 stream_pos = 0; 249 | for (plane=0;plane<4;plane++) { 250 | for (pos=0;pos>= 1; 253 | if (in_mask == 0) { 254 | in_mask = 0x80; 255 | stream_pos++; 256 | } 257 | } 258 | out_mask <<= 1; 259 | } 260 | for (pos=0;pos>= 1; 272 | if (in_mask == 0) { 273 | in_mask = 0x80; 274 | stream_pos++; 275 | } 276 | } 277 | } 278 | return 1; // success 279 | } 280 | -------------------------------------------------------------------------------- /src/import/gamespecific_2p.c: -------------------------------------------------------------------------------- 1 | #include "gamespecific_2p.h" 2 | 3 | extern const u32 ingame_palette[]; 4 | 5 | const u8 orig_swap_exit[] = 6 | {0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0}; 7 | 8 | const u8 ohno_swap_exit[] = 9 | {1, 0, 0, 0, 0, 0, 1, 1, 1, 1}; 10 | 11 | const struct GameSpecific2P import_2p[2] = { 12 | // original Lemmings 13 | { 14 | "2p/orig", PATH_DATA_ORIGINAL, 15 | ingame_palette, 16 | 20, orig_swap_exit 17 | }, 18 | // Oh No! More Lemmings 19 | { 20 | "2p/ohno", PATH_DATA_OHNOMORE, 21 | ingame_palette, 22 | 10, ohno_swap_exit 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/import/import_adlib.cpp: -------------------------------------------------------------------------------- 1 | #include "import_adlib.h" 2 | #include "adlib/8086.h" 3 | 4 | static Emu8086* emu8086[2] = {0, 0}; 5 | 6 | void OPL_Init(unsigned long sample_rate) { 7 | if (emu8086[0]) { 8 | return; 9 | } 10 | emu8086[0] = new Emu8086(sample_rate); 11 | emu8086[1] = new Emu8086(sample_rate); 12 | } 13 | 14 | int OPL_LoadAdlibData(struct Data* decoded_adlib_dat) { 15 | if (emu8086[0]) { 16 | if (!emu8086[0]->load_adlib_data(decoded_adlib_dat)) { 17 | return 0; 18 | } 19 | } 20 | if (emu8086[1]) { 21 | return emu8086[1]->load_adlib_data(decoded_adlib_dat); 22 | } 23 | return 1; 24 | } 25 | 26 | int OPL_CallAdlib(u16 ax, int module) { 27 | if (module >= 0 && module < 2) { 28 | if (emu8086[module]) { 29 | return emu8086[module]->call_adlib(ax); 30 | } 31 | } 32 | return 0; 33 | } 34 | 35 | void OPL_FreeAdlibData() { 36 | if (emu8086[0]) { 37 | emu8086[0]->free_adlib_data(); 38 | } 39 | if (emu8086[1]) { 40 | emu8086[1]->free_adlib_data(); 41 | } 42 | } 43 | 44 | void OPL_QuerySamples(unsigned long samples, int module) { 45 | if (module >= 0 && module < 2) { 46 | if (emu8086[module]) { 47 | emu8086[module]->query_opl_samples(samples, module); 48 | } 49 | } 50 | } 51 | 52 | void OPL_ShutDown(){ 53 | if (emu8086[0]) { 54 | delete emu8086[0]; 55 | } 56 | if (emu8086[1]) { 57 | delete emu8086[1]; 58 | } 59 | emu8086[0] = 0; 60 | emu8086[1] = 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/import/import_ground.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "settings.h" 4 | #include "import_ground.h" 5 | 6 | inline u8 read_u8(void** in) { 7 | u16 ret = *(u8*)*in; 8 | *(u8**)in += 1; 9 | return ret; 10 | } 11 | 12 | inline u16 read_u16(void** in) { 13 | u16 ret = *(u16*)*in; 14 | *(u16**)in += 1; 15 | return ret; 16 | } 17 | 18 | inline void read_object_info(struct ObjectInfo* ret, void** in) { 19 | ret->animation_flags = read_u16(in); 20 | ret->start_animation_frame_index = read_u8(in); 21 | ret->end_animation_frame_index = read_u8(in); 22 | ret->width = read_u8(in); 23 | ret->height = read_u8(in); 24 | ret->animation_frame_data_size = read_u16(in); 25 | ret->mask_offset_from_image = read_u16(in); 26 | ret->unknown1 = read_u16(in); 27 | ret->unknown2 = read_u16(in); 28 | ret->trigger_left = read_u16(in); 29 | ret->trigger_top = read_u16(in); 30 | ret->trigger_width = read_u8(in); 31 | ret->trigger_height = read_u8(in); 32 | ret->trigger_effect_id = read_u8(in); 33 | ret->animation_frames_base_loc = read_u16(in); 34 | ret->preview_image_index = read_u16(in); 35 | ret->unknown3 = read_u16(in); 36 | ret->trap_sound_effect_id = read_u8(in); 37 | } 38 | 39 | inline void read_terrain_info(struct TerrainInfo* ret, void** in) { 40 | ret->width = read_u8(in); 41 | ret->height = read_u8(in); 42 | ret->image_loc = read_u16(in); 43 | ret->mask_loc = read_u16(in); 44 | ret->unknown1 = read_u16(in); 45 | } 46 | 47 | inline void read_palette(struct LevelPalette* ret, void** in) { 48 | int i; 49 | for (i=0;i<8;i++) { 50 | ret->ega_custom[i] = read_u8(in); 51 | } 52 | for (i=0;i<8;i++) { 53 | ret->ega_standard[i] = read_u8(in); 54 | } 55 | for (i=0;i<8;i++) { 56 | ret->ega_preview[i] = read_u8(in); 57 | } 58 | for (i=0;i<8;i++) { 59 | ret->vga_custom[i] = 0; 60 | memcpy(&ret->vga_custom[i],*in,3); 61 | *(u8**)in += 3; 62 | ret->vga_custom[i] = 63 | (((ret->vga_custom[i] >> 16) * 255 / 63) << 8) 64 | | ((((ret->vga_custom[i] & 0xFF00) >> 8) * 255 / 63) << 16) 65 | | (((ret->vga_custom[i] & 0xFF) * 255 / 63) << 24) | 0x000000FF; 66 | } 67 | for (i=0;i<8;i++) { 68 | ret->vga_standard[i] = 0; 69 | memcpy(&ret->vga_standard[i],*in,3); 70 | *(u8**)in += 3; 71 | ret->vga_standard[i] = 72 | (((ret->vga_standard[i] >> 16) * 255 / 63) << 8) 73 | | ((((ret->vga_standard[i] & 0xFF00) >> 8) * 255 / 63) << 16) 74 | | (((ret->vga_standard[i] & 0xFF) * 255 / 63) << 24) | 0x000000FF; 75 | } 76 | for (i=0;i<8;i++) { 77 | ret->vga_preview[i] = 0; 78 | memcpy(&ret->vga_preview[i],*in,3); 79 | *(u8**)in += 3; 80 | ret->vga_preview[i] = 81 | (((ret->vga_preview[i] >> 16) * 255 / 63) << 8) 82 | | ((((ret->vga_preview[i] & 0xFF00) >> 8) * 255 / 63) << 16) 83 | | (((ret->vga_preview[i] & 0xFF) * 255 / 63) << 24) | 0x000000FF; 84 | } 85 | } 86 | 87 | // read all 88 | int read_ground_data(struct GroundInfo* ret, void* ground_data) { 89 | int i; 90 | if (ret == 0 || ground_data == 0) { 91 | return 0; 92 | } 93 | void* in = ground_data; 94 | for (i=0;i<16;i++) { 95 | read_object_info(&ret->object_info[i],&in); 96 | } 97 | for (i=0;i<64;i++) { 98 | read_terrain_info(&ret->terrain_info[i],&in); 99 | } 100 | read_palette(&ret->palette,&in); 101 | return 1; 102 | } 103 | -------------------------------------------------------------------------------- /src/import/import_wave.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "import_wave.h" 3 | 4 | int wave_open_file(struct WaveFile* file, const char* filename) { 5 | if (!file) { 6 | return 0; 7 | } 8 | memset(file,0,sizeof(struct WaveFile)); 9 | if (!filename) { 10 | return 0; 11 | } 12 | // open file 13 | FILE* f = fopen(filename,"rb"); 14 | if (!f) { 15 | return 0; 16 | } 17 | // read RIFF chunk 18 | char buf[8]; 19 | if (fread(buf,1,8,f) != 8) { 20 | fclose(f); 21 | return 0; 22 | } 23 | if (strncmp(buf,"RIFF",4)) { 24 | fclose(f); 25 | return 0; 26 | } 27 | u32 size_left = *((u32*)(buf+4)); 28 | if (size_left < 4) { 29 | fclose(f); 30 | return 0; 31 | } 32 | if (fread(buf,1,4,f) != 4) { 33 | fclose(f); 34 | return 0; 35 | } 36 | size_left -= 4; 37 | if (strncmp(buf,"WAVE",4)) { 38 | fclose(f); 39 | return 0; 40 | } 41 | // now go through chunks, 42 | // ignore all chunks except of "data" and "fmt " chunk 43 | int fmt_chunk_read = 0; 44 | int data_chunk_read = 0; 45 | while (size_left && (!fmt_chunk_read || !data_chunk_read)) { 46 | // read chunk 47 | if (size_left < 8) { 48 | fclose(f); 49 | return 0; 50 | } 51 | if (fread(buf,1,8,f) != 8) { 52 | fclose(f); 53 | return 0; 54 | } 55 | size_left -= 8; 56 | u32 chunk_size = *((u32*)(buf+4)); 57 | if (size_left < chunk_size) { 58 | fclose(f); 59 | return 0; 60 | } 61 | if (!strncmp(buf,"fmt ",4)) { 62 | // read "fmt " chunk 63 | if (chunk_size != 16) { 64 | fclose(f); 65 | return 0; 66 | } 67 | // format 68 | u16 tmp; 69 | if (fread(&tmp,1,2,f) != 2) { 70 | fclose(f); 71 | return 0; 72 | } 73 | if (tmp != 0x0001) { 74 | // not in PCM format -> not supported 75 | fclose(f); 76 | return 0; 77 | } 78 | // channels 79 | if (fread(&(file->channels),1,2,f) != 2) { 80 | fclose(f); 81 | return 0; 82 | } 83 | if (file->channels != 1 && file->channels != 2) { 84 | // neither mono nor stereo -> not supported 85 | fclose(f); 86 | return 0; 87 | } 88 | // sample rate 89 | if (fread(&(file->frequency),1,4,f) != 4) { 90 | fclose(f); 91 | return 0; 92 | } 93 | // bytes/sec 94 | u32 tmp2; 95 | if (fread(&tmp2,1,4,f) != 4) { 96 | fclose(f); 97 | return 0; 98 | } 99 | // frame size 100 | if (fread(&tmp,1,2,f) != 2) { 101 | fclose(f); 102 | return 0; 103 | } 104 | // bit depth 105 | if (fread(&file->bitdepth,1,2,f) != 2) { 106 | fclose(f); 107 | return 0; 108 | } 109 | if (file->bitdepth != 8 && file->bitdepth != 16) { 110 | // neither 8 bit nor 16 bit -> not supported 111 | fclose(f); 112 | return 0; 113 | } 114 | // check whether frame size is correct 115 | if (tmp != file->channels*((file->bitdepth+7)/8)) { 116 | fclose(f); 117 | return 0; 118 | } 119 | // check whether bytes per second are correct 120 | if (tmp2 != (u32)tmp * file->frequency) { 121 | fclose(f); 122 | return 0; 123 | } 124 | fmt_chunk_read = 1; 125 | size_left -= chunk_size; 126 | continue; 127 | } 128 | if (!strncmp(buf,"data",4)) { 129 | // read "data" chunk 130 | file->data_offset = ftell(f); 131 | file->data_size = chunk_size; 132 | // don't read data now, just skip it 133 | data_chunk_read = 1; 134 | u32 old_pos = ftell(f); 135 | fseek(f,chunk_size,SEEK_CUR); 136 | if (ftell(f)-old_pos != chunk_size) { 137 | fclose(f); 138 | return 0; 139 | } 140 | size_left -= chunk_size; 141 | continue; 142 | } 143 | // skip unsupported chunk 144 | u32 old_pos = ftell(f); 145 | fseek(f,chunk_size,SEEK_CUR); 146 | if (ftell(f)-old_pos != chunk_size) { 147 | fclose(f); 148 | return 0; 149 | } 150 | size_left -= chunk_size; 151 | } 152 | if (!fmt_chunk_read || !data_chunk_read) { 153 | // wave file does not contain all supported chunks 154 | fclose(f); 155 | return 0; 156 | } 157 | // success 158 | file->file = f; 159 | wave_rewind(file); // move to begin of data section 160 | return (file->file?1:0); 161 | } 162 | 163 | u32 wave_get_next_samples(void* buffer, u32 num_samples, struct WaveFile* file) { 164 | if (!file) { 165 | return 0; 166 | } 167 | if (!file->file) { 168 | return 0; 169 | } 170 | char* buf = (char*)buffer; 171 | u8 sample_size = file->channels*((file->bitdepth+7)/8); 172 | u32 samples_read; 173 | for (samples_read=0;samples_readcurrent_data_position+sample_size > file->data_size) { 175 | return samples_read; 176 | } 177 | if (sample_size != fread(buf,1,sample_size,file->file)) { 178 | // error occured 179 | fclose(file->file); 180 | file->file = 0; 181 | return 0; 182 | } 183 | if (file->bitdepth == 8) { 184 | // convert unsigned to signed 185 | u8 j; 186 | for (j=0;jcurrent_data_position += sample_size; 192 | } 193 | return samples_read; 194 | } 195 | 196 | void wave_rewind(struct WaveFile* file) { 197 | if (!file) { 198 | return; 199 | } 200 | if (!file->file) { 201 | return; 202 | } 203 | file->current_data_position = 0; 204 | fseek(file->file,file->data_offset,SEEK_SET); 205 | if (ftell(file->file) != file->data_offset) { 206 | fclose(file->file); 207 | file->file = 0; 208 | return; 209 | } 210 | } 211 | 212 | void wave_close_file(struct WaveFile* file) { 213 | if (!file) { 214 | return; 215 | } 216 | if (file->file) { 217 | fclose(file->file); 218 | } 219 | memset(file,0,sizeof(struct WaveFile)); 220 | return; 221 | } 222 | 223 | int import_wave_sound(struct WaveSound* dest, const char* filename) { 224 | if (!dest) { 225 | return 0; 226 | } 227 | memset(dest,0,sizeof(struct WaveSound)); 228 | if (!filename) { 229 | return 0; 230 | } 231 | struct WaveFile file; 232 | if (!wave_open_file(&file, filename)) { 233 | return 0; 234 | } 235 | dest->channels = file.channels; 236 | dest->bitdepth = file.bitdepth; 237 | dest->frequency = file.frequency; 238 | dest->data = linearAlloc(file.data_size); 239 | if (!dest->data) { 240 | return 0; 241 | } 242 | dest->size = file.data_size; 243 | u32 samples = file.data_size / (file.channels * ((file.bitdepth+7)/8)); 244 | dest->samples = wave_get_next_samples( 245 | dest->data, 246 | samples, // get all samples 247 | &file); 248 | wave_close_file(&file); 249 | if (dest->samples != samples) { 250 | linearFree(dest->data); 251 | dest->data = 0; 252 | return 0; 253 | } 254 | return 1; 255 | } 256 | 257 | -------------------------------------------------------------------------------- /src/savegame.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "savegame.h" 4 | #include "settings.h" 5 | #include "gamespecific.h" 6 | 7 | void read_savegame(struct SaveGame* savegame) { 8 | char savefile_fn[64]; 9 | int i; 10 | int offset = 0; 11 | if (!savegame) { 12 | return; 13 | } 14 | if (!savegame->progress) { 15 | return; 16 | } 17 | 18 | // fill progress with zeros 19 | for (i=0;iprogress+offset,0,import[i].num_of_difficulties); 21 | offset += import[i].num_of_difficulties; 22 | } 23 | savegame->multiplayer_progress[0] = 0; 24 | savegame->multiplayer_progress[1] = 0; 25 | savegame->last_multiplayer_level = 0; 26 | savegame->last_multiplayer_game = 0; 27 | 28 | sprintf(savefile_fn,"%s/SAVEGAME.DAT", PATH_ROOT); 29 | FILE* savefile = fopen(savefile_fn,"rb"); 30 | if (!savefile) { 31 | return; 32 | } 33 | u8 version = 0; 34 | if (!fread(&version,1,1,savefile)) { 35 | fclose(savefile); 36 | return; 37 | } 38 | if (version < 0x20) { 39 | // old file; directly starts with progress of FUN rating 40 | version = 0; 41 | fseek(savefile,0,SEEK_SET); 42 | }else{ 43 | version -= 0x1F; 44 | } 45 | 46 | if (version > SAVEGAME_VERSION) { 47 | // file is too new and therefore unsupported 48 | fclose(savefile); 49 | return; 50 | } 51 | 52 | offset = 0; 53 | for (i=0;iprogress[offset], 64 | 1, 65 | import[i].num_of_difficulties, 66 | savefile) 67 | != import[i].num_of_difficulties) { 68 | memset(savegame->progress+offset,0,import[i].num_of_difficulties); 69 | break; 70 | } 71 | offset += import[i].num_of_difficulties; 72 | } 73 | if (version > 3) { 74 | fread(savegame->multiplayer_progress,1,2,savefile); 75 | } 76 | settings.sfx_volume = 100; 77 | settings.music_volume = 100; 78 | savegame->last_game = 0; 79 | savegame->last_level = 0; 80 | if (version == 2) { 81 | u8 tmp; 82 | fread(&tmp,1,1,savefile); 83 | switch (tmp) { 84 | case 1: 85 | settings.sfx_volume = 0; 86 | case 2: 87 | settings.music_volume = 0; 88 | default: 89 | break; 90 | } 91 | } 92 | if (version > 2) { 93 | fread(&settings.sfx_volume,1,1,savefile); 94 | fread(&settings.music_volume,1,1,savefile); 95 | fread(&savegame->last_game,1,1,savefile); 96 | fread(&savegame->last_level,1,1,savefile); 97 | if (version > 5) { 98 | fread(&savegame->last_multiplayer_game,1,1,savefile); 99 | fread(&savegame->last_multiplayer_level,1,1,savefile); 100 | } 101 | fread(&settings.glitch_nuke,1,1,savefile); 102 | fread(&settings.glitch_entrance_pausing,1,1,savefile); 103 | fread(&settings.glitch_mining_right_oneway,1,1,savefile); 104 | fread(&settings.glitch_shrugger,1,1,savefile); 105 | fread(&settings.glitch_mayhem12,1,1,savefile); 106 | fread(&settings.glitch_direct_drop,1,1,savefile); 107 | fread(&settings.speedup_millis_per_frame,1,1,savefile); 108 | fread(&settings.audio_order,1,1,savefile); 109 | fread(&settings.dlbclick_nuke,1,1,savefile); 110 | fread(&settings.dblclick_exit,1,1,savefile); 111 | fread(&settings.skip_unavailable_skills,1,1,savefile); 112 | fread(&settings.zoom_mode_active,1,1,savefile); 113 | if (version > 3) { 114 | fread(&settings.amiga_background,1,1,savefile); 115 | } 116 | if (version > 4) { 117 | u8 two_player_add_saved_lemmings; 118 | fread(&two_player_add_saved_lemmings,1,1,savefile); 119 | settings.two_player_always_equal = !two_player_add_saved_lemmings; 120 | } 121 | if (version > 6) { 122 | fread(&settings.two_player_timeout,1,1,savefile); 123 | fread(&settings.two_player_inspect_level,1,1,savefile); 124 | fread(&settings.reserved_1,1,1,savefile); 125 | fread(&settings.reserved_2,1,1,savefile); 126 | } 127 | for (i=0;i<2;i++) { 128 | fread(&settings.key_bindings[i].modifier,1,4,savefile); 129 | fread(&settings.key_bindings[i].click,1,4,savefile); 130 | fread(&settings.key_bindings[i].inc_rate,1,4,savefile); 131 | fread(&settings.key_bindings[i].dec_rate,1,4,savefile); 132 | fread(&settings.key_bindings[i].next_skill,1,4,savefile); 133 | fread(&settings.key_bindings[i].prev_skill,1,4,savefile); 134 | fread(&settings.key_bindings[i].pause,1,4,savefile); 135 | fread(&settings.key_bindings[i].nuke,1,4,savefile); 136 | fread(&settings.key_bindings[i].exit,1,4,savefile); 137 | fread(&settings.key_bindings[i].speed_up,1,4,savefile); 138 | fread(&settings.key_bindings[i].non_prio,1,4,savefile); 139 | fread(&settings.key_bindings[i].step_one_frame,1,4,savefile); 140 | fread(&settings.key_bindings[i].step_backwards,1,4,savefile); 141 | fread(&settings.key_bindings[i].play_backwards,1,4,savefile); 142 | fread(&settings.key_bindings[i].toggle_zoom_mode,1,4,savefile); 143 | fread(&settings.key_bindings[i].cursor_up,1,4,savefile); 144 | fread(&settings.key_bindings[i].cursor_down,1,4,savefile); 145 | fread(&settings.key_bindings[i].cursor_left,1,4,savefile); 146 | fread(&settings.key_bindings[i].cursor_right,1,4,savefile); 147 | fread(&settings.key_bindings[i].scroll_up,1,4,savefile); 148 | fread(&settings.key_bindings[i].scroll_down,1,4,savefile); 149 | fread(&settings.key_bindings[i].scroll_left,1,4,savefile); 150 | fread(&settings.key_bindings[i].scroll_right,1,4,savefile); 151 | } 152 | } 153 | fclose(savefile); 154 | } 155 | 156 | void write_savegame(struct SaveGame* savegame) { 157 | if (!savegame) { 158 | return; 159 | } 160 | if (!savegame->progress) { 161 | return; 162 | } 163 | char savefile_fn[64]; 164 | sprintf(savefile_fn,"%s/SAVEGAME.DAT", PATH_ROOT); 165 | FILE* savefile = fopen(savefile_fn,"wb"); 166 | if (!savefile) { 167 | return; 168 | } 169 | u8 version = SAVEGAME_VERSION + 0x1F; 170 | fwrite(&version,1,1,savefile); 171 | int i; 172 | int offset = 0; 173 | for (i=0;iprogress+offset, 176 | 1, 177 | import[i].num_of_difficulties, 178 | savefile); 179 | offset += import[i].num_of_difficulties; 180 | } 181 | fwrite(savegame->multiplayer_progress,1,2,savefile); 182 | fwrite(&settings.sfx_volume,1,1,savefile); 183 | fwrite(&settings.music_volume,1,1,savefile); 184 | fwrite(&savegame->last_game,1,1,savefile); 185 | fwrite(&savegame->last_level,1,1,savefile); 186 | fwrite(&savegame->last_multiplayer_game,1,1,savefile); 187 | fwrite(&savegame->last_multiplayer_level,1,1,savefile); 188 | fwrite(&settings.glitch_nuke,1,1,savefile); 189 | fwrite(&settings.glitch_entrance_pausing,1,1,savefile); 190 | fwrite(&settings.glitch_mining_right_oneway,1,1,savefile); 191 | fwrite(&settings.glitch_shrugger,1,1,savefile); 192 | fwrite(&settings.glitch_mayhem12,1,1,savefile); 193 | fwrite(&settings.glitch_direct_drop,1,1,savefile); 194 | fwrite(&settings.speedup_millis_per_frame,1,1,savefile); 195 | fwrite(&settings.audio_order,1,1,savefile); 196 | fwrite(&settings.dlbclick_nuke,1,1,savefile); 197 | fwrite(&settings.dblclick_exit,1,1,savefile); 198 | fwrite(&settings.skip_unavailable_skills,1,1,savefile); 199 | fwrite(&settings.zoom_mode_active,1,1,savefile); 200 | fwrite(&settings.amiga_background,1,1,savefile); 201 | u8 two_player_add_saved_lemmings = !settings.two_player_always_equal; 202 | fwrite(&two_player_add_saved_lemmings,1,1,savefile); 203 | fwrite(&settings.two_player_timeout,1,1,savefile); 204 | fwrite(&settings.two_player_inspect_level,1,1,savefile); 205 | fwrite(&settings.reserved_1,1,1,savefile); 206 | fwrite(&settings.reserved_2,1,1,savefile); 207 | for (i=0;i<2;i++) { 208 | fwrite(&settings.key_bindings[i].modifier,1,4,savefile); 209 | fwrite(&settings.key_bindings[i].click,1,4,savefile); 210 | fwrite(&settings.key_bindings[i].inc_rate,1,4,savefile); 211 | fwrite(&settings.key_bindings[i].dec_rate,1,4,savefile); 212 | fwrite(&settings.key_bindings[i].next_skill,1,4,savefile); 213 | fwrite(&settings.key_bindings[i].prev_skill,1,4,savefile); 214 | fwrite(&settings.key_bindings[i].pause,1,4,savefile); 215 | fwrite(&settings.key_bindings[i].nuke,1,4,savefile); 216 | fwrite(&settings.key_bindings[i].exit,1,4,savefile); 217 | fwrite(&settings.key_bindings[i].speed_up,1,4,savefile); 218 | fwrite(&settings.key_bindings[i].non_prio,1,4,savefile); 219 | fwrite(&settings.key_bindings[i].step_one_frame,1,4,savefile); 220 | fwrite(&settings.key_bindings[i].step_backwards,1,4,savefile); 221 | fwrite(&settings.key_bindings[i].play_backwards,1,4,savefile); 222 | fwrite(&settings.key_bindings[i].toggle_zoom_mode,1,4,savefile); 223 | fwrite(&settings.key_bindings[i].cursor_up,1,4,savefile); 224 | fwrite(&settings.key_bindings[i].cursor_down,1,4,savefile); 225 | fwrite(&settings.key_bindings[i].cursor_left,1,4,savefile); 226 | fwrite(&settings.key_bindings[i].cursor_right,1,4,savefile); 227 | fwrite(&settings.key_bindings[i].scroll_up,1,4,savefile); 228 | fwrite(&settings.key_bindings[i].scroll_down,1,4,savefile); 229 | fwrite(&settings.key_bindings[i].scroll_left,1,4,savefile); 230 | fwrite(&settings.key_bindings[i].scroll_right,1,4,savefile); 231 | } 232 | fclose(savefile); 233 | } 234 | --------------------------------------------------------------------------------