├── experiment ├── run.bat ├── compile.bat ├── convert.bat ├── res │ ├── gfx.res │ └── sgdk.png ├── .gitignore ├── cleanup.bat ├── HD SEGA logo 4x3.mp4 ├── Genesis does what Nintendon't - v2 HD by RVGM.mp4 └── src │ ├── movie_player.h │ ├── main.c │ ├── boot │ ├── rom_head.c │ └── sega.s │ └── movie_player.c ├── .gitignore ├── env.bat ├── lib ├── movie_player.h └── movie_player.c ├── package.json ├── LICENSE ├── js ├── require-conversion.js ├── file.js ├── convert.js ├── execute.js └── generate.js ├── index.js └── README.md /experiment/run.bat: -------------------------------------------------------------------------------- 1 | out\rom.bin 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmpmv_* 2 | /node_modules 3 | -------------------------------------------------------------------------------- /experiment/compile.bat: -------------------------------------------------------------------------------- 1 | make -f D:\Pessoal\Projetos\sgdk170\makefile.gen 2 | -------------------------------------------------------------------------------- /experiment/convert.bat: -------------------------------------------------------------------------------- 1 | node ../ convert "HD SEGA logo 4x3.mp4" res --only-if-changed 2 | -------------------------------------------------------------------------------- /experiment/res/gfx.res: -------------------------------------------------------------------------------- 1 | IMAGE logo_aplib "sgdk.png" APLIB 2 | IMAGE logo_ucomp "sgdk.png" NONE 3 | -------------------------------------------------------------------------------- /env.bat: -------------------------------------------------------------------------------- 1 | path %PATH%;D:\Pessoal\Projetos\ImageMagick-7.0.10-53-portable-Q16-x86;D:\Pessoal\Projetos\sgdk170\bin -------------------------------------------------------------------------------- /experiment/res/sgdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldo-ok/sgdk-video-player/HEAD/experiment/res/sgdk.png -------------------------------------------------------------------------------- /experiment/.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | /res/*.h 3 | /res/movie_frames.res 4 | /src/generated 5 | /res/movie_HD_SEGA_logo_4x3_mp4* -------------------------------------------------------------------------------- /experiment/cleanup.bat: -------------------------------------------------------------------------------- 1 | rmdir /s /q out\res 2 | del res\movie_* 3 | rmdir /s /q res\tmpmv_movie_HD_SEGA_logo_4x3_mp4 4 | -------------------------------------------------------------------------------- /experiment/HD SEGA logo 4x3.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldo-ok/sgdk-video-player/HEAD/experiment/HD SEGA logo 4x3.mp4 -------------------------------------------------------------------------------- /experiment/Genesis does what Nintendon't - v2 HD by RVGM.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldo-ok/sgdk-video-player/HEAD/experiment/Genesis does what Nintendon't - v2 HD by RVGM.mp4 -------------------------------------------------------------------------------- /lib/movie_player.h: -------------------------------------------------------------------------------- 1 | #ifndef _MOVIE_RES_H 2 | #define _MOVIE_RES_H 3 | 4 | #include 5 | 6 | typedef struct MovieData { 7 | u16 format; 8 | u16 w, h; 9 | u16 frameCount; 10 | const u8 *sound; 11 | const u32 soundLen; 12 | const Image *frames[]; 13 | } MovieData; 14 | 15 | extern void MVP_playMovie(const MovieData *movie); 16 | 17 | #endif // _MOVIE_RES_H 18 | -------------------------------------------------------------------------------- /experiment/src/movie_player.h: -------------------------------------------------------------------------------- 1 | #ifndef _MOVIE_RES_H 2 | #define _MOVIE_RES_H 3 | 4 | #include 5 | 6 | typedef struct MovieData { 7 | u16 format; 8 | u16 w, h; 9 | u16 frameCount; 10 | const u8 *sound; 11 | const u32 soundLen; 12 | const Image *frames[]; 13 | } MovieData; 14 | 15 | extern void MVP_playMovie(const MovieData *movie); 16 | 17 | #endif // _MOVIE_RES_H 18 | -------------------------------------------------------------------------------- /experiment/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gfx.h" 4 | #include "sound.h" 5 | 6 | #include "dma.h" 7 | 8 | #include "movie_player.h" 9 | #include "movie_HD_SEGA_logo_4x3_mp4.h" 10 | 11 | extern const MovieData movie_HD_SEGA_logo_4x3_mp4; 12 | 13 | int main(u16 hard) { 14 | while(TRUE) { 15 | MVP_playMovie(&movie_HD_SEGA_logo_4x3_mp4); 16 | } 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sgdk-video-player", 3 | "version": "0.3.0", 4 | "description": "A video converter/video player for SGDK ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/haroldo-ok/sgdk-video-player.git" 12 | }, 13 | "author": "Haroldo O. Pinheiro", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/haroldo-ok/sgdk-video-player/issues" 17 | }, 18 | "homepage": "https://github.com/haroldo-ok/sgdk-video-player#readme", 19 | "dependencies": { 20 | "branchy": "^2.0.0", 21 | "lodash": "^4.17.21", 22 | "rgbquant-sms": "^0.2.2", 23 | "yargs": "^17.7.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /experiment/src/boot/rom_head.c: -------------------------------------------------------------------------------- 1 | #include "genesis.h" 2 | 3 | __attribute__((externally_visible)) 4 | const ROMHeader rom_header = { 5 | #if (ENABLE_BANK_SWITCH != 0) 6 | "SEGA SSF ", 7 | #elif (ENABLE_MEGAWIFI != 0) 8 | "SEGA MEGAWIFI ", 9 | #else 10 | "SEGA MEGA DRIVE ", 11 | #endif 12 | "(C)Haroldo 2023 ", 13 | "SGDK VIDEO PLAYER TEST v0.2.0 ", 14 | "SGDK VIDEO PLAYER TEST v0.2.0 ", 15 | "GM 00000000-00", 16 | 0x000, 17 | "JD ", 18 | 0x00000000, 19 | #if (ENABLE_BANK_SWITCH != 0) 20 | 0x003FFFFF, 21 | #else 22 | 0x000FFFFF, 23 | #endif 24 | 0xE0FF0000, 25 | 0xE0FFFFFF, 26 | "RA", 27 | 0xF820, 28 | 0x00200000, 29 | 0x0020FFFF, 30 | " ", 31 | "SGDK VIDEO PLAYER TEST v0.2.0 ", 32 | "JUE " 33 | }; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Haroldo de Oliveira Pinheiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /js/require-conversion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | const { listCodeToGenerate, listImagesToConvert } = require('./generate'); 6 | const { checkLastModified } = require('./file'); 7 | 8 | const isFileAbsentOrChanged = async (baseDate, fileName) => { 9 | const genDate = await checkLastModified(fileName); 10 | return !genDate || genDate < baseDate; 11 | } 12 | 13 | const isConversionRequired = async (srcVideo, resDir, alias) => { 14 | const videoDate = await checkLastModified(srcVideo); 15 | if (!videoDate) { 16 | throw new Error(`Could not find source video: ${srcVideo}`); 17 | } 18 | 19 | // Check generated source files 20 | 21 | for (const { fileName } of listCodeToGenerate(resDir, alias)) { 22 | if (await isFileAbsentOrChanged(videoDate, fileName)) { 23 | // Generated file is missing, or is older than the video 24 | return true; 25 | } 26 | } 27 | 28 | // Check generated frames 29 | const imagesToConvert = await listImagesToConvert(resDir, alias); 30 | if (!imagesToConvert.length) return true; 31 | for (const { dest } of imagesToConvert) { 32 | if (await isFileAbsentOrChanged(videoDate, dest)) { 33 | // Generated file is missing, or is older than the video 34 | return true; 35 | } 36 | } 37 | 38 | return false; 39 | }; 40 | 41 | module.exports = { isConversionRequired }; -------------------------------------------------------------------------------- /js/file.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const checkFileExists = async fileName => fs.promises.access(fileName, fs.constants.F_OK) 5 | .then(() => true) 6 | .catch(() => false); 7 | 8 | const checkLastModified = async fileName => { 9 | try { 10 | // Use `stat()` fn to get last modified date 11 | const stat = await fs.promises.stat(fileName); 12 | return stat.mtime; 13 | } catch (e) { 14 | if (e.code === 'ENOENT') { 15 | // The error specifically says the file is missing 16 | return null; 17 | } 18 | 19 | // Fallback for other errors. 20 | return e; 21 | } 22 | }; 23 | 24 | const changeFileExtension = (file, extension) => { 25 | const baseName = path.basename(file, path.extname(file)) 26 | return path.join(path.dirname(file), baseName + extension); 27 | }; 28 | 29 | const removeFileExtension = file => changeFileExtension(file, ''); 30 | 31 | const clearDir = async dir => { 32 | for (const fileName of await fs.promises.readdir(dir)) { 33 | await fs.promises.unlink(path.join(dir, fileName)); 34 | } 35 | }; 36 | 37 | const listFilesRegex = async (dir, fileRegex) => { 38 | const allFiles = await fs.promises.readdir(dir); 39 | const fileNames = allFiles.filter(s => fileRegex.test(s)); 40 | const sortedFileNames = fileNames 41 | .map(name => ({ idx: parseInt(fileRegex.exec(name)[1]), name })) 42 | .sort((a, b) => a.idx - b.idx) 43 | .map(o => o.name); 44 | 45 | return sortedFileNames; 46 | }; 47 | 48 | module.exports = { checkFileExists, checkLastModified, changeFileExtension, removeFileExtension, clearDir, listFilesRegex }; -------------------------------------------------------------------------------- /js/convert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const { spawnWorkers, extractVideoFrames, reduceTileCount, convertImagesToIndexed } = require('./execute'); 7 | const { generateCode, getDestDir, listSourceFrames, listImagesToConvert } = require('./generate'); 8 | const { checkFileExists, changeFileExtension, clearDir, listFilesRegex } = require('./file'); 9 | const { isConversionRequired } = require('./require-conversion'); 10 | 11 | const convertVideo = async (srcVideo, resDir, { imagemagickDir, cpuCores, alias, onlyIfChanged }) => { 12 | 13 | if (!await checkFileExists(srcVideo)) { 14 | throw new Error(`Input video not found: ${srcVideo}`); 15 | } 16 | 17 | const destDir = getDestDir(resDir, alias); 18 | if (!await checkFileExists(destDir)) { 19 | await fs.promises.mkdir(destDir, { recursive: true }); 20 | } 21 | 22 | if (onlyIfChanged && !await isConversionRequired(srcVideo, resDir, alias)) { 23 | console.log(`Video already converted: ${srcVideo}`); 24 | return; 25 | } 26 | 27 | await clearDir(destDir); 28 | 29 | await extractVideoFrames(srcVideo, destDir, { imagemagickDir }); 30 | 31 | const imagesToConvert = await listImagesToConvert(resDir, alias); 32 | 33 | await spawnWorkers(async ({ src, dest }) => { 34 | console.log(`Converting ${src} to ${dest}...`); 35 | await reduceTileCount(src, dest); 36 | console.log(`Finished ${dest}.`); 37 | }, imagesToConvert, { 38 | cpuCores, 39 | onProgress: ({ percent }) => console.log(`${percent.toFixed(2)}% done: ${srcVideo}`) 40 | }); 41 | 42 | await convertImagesToIndexed(destDir, { imagemagickDir }); 43 | 44 | const sourceFrames = await listSourceFrames(resDir, alias); 45 | const targetImages = sourceFrames.map(frameSrc => changeFileExtension(frameSrc, '.png')); 46 | await generateCode(targetImages, resDir, alias); 47 | } 48 | 49 | module.exports = { convertVideo }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | const os = require('os'); 7 | const _ = require('underscore'); 8 | 9 | const { convertVideo } = require('./js/convert'); 10 | 11 | /* if called directly from command line or from a shell script */ 12 | if (require.main === module) { 13 | const yargs = require('yargs'); 14 | 15 | const commandLine = yargs.scriptName('sgdk-video-player') 16 | .usage('$0 [args]') 17 | .command('convert ', 'Converts a video file and outputs the result in the resource directory', (yargs) => { 18 | yargs 19 | .positional('src', { 20 | type: 'string', 21 | describe: 'The source video, the one that will be converted' 22 | }) 23 | .positional('resDir', { 24 | type: 'string', 25 | describe: 'The resource directory, where the generated sources will be placed.' 26 | }) 27 | .options({ 28 | 'cpu-cores': { 29 | describe: 'Number of CPU cores to use. If ommited, will use all of them.', 30 | type: 'int' 31 | }, 32 | 'alias': { 33 | describe: 'Alias to use when generating the C constants. If ommited, it will be generated from .', 34 | type: 'string' 35 | }, 36 | 'only-if-changed': { 37 | describe: 'Only converts if file has been changed.', 38 | default: false, 39 | type: 'boolean' 40 | } 41 | }) 42 | .check((argv, options) => { 43 | if (!fs.existsSync(argv.src)) { 44 | return `The provided source video file does not exist: ${argv.src}`; 45 | } 46 | 47 | if (!argv.cpuCores) { 48 | argv.cpuCores = os.cpus().length; 49 | } 50 | 51 | if (!argv.alias) { 52 | argv.alias = 'movie_' + argv.src.replace(/[^A-Za-z0-9]/g, '_').replace(/__+/g, '_'); 53 | } 54 | 55 | return true; 56 | }); 57 | }) 58 | .options({ 59 | 'imagemagick-dir': { 60 | alias: 'kd', 61 | describe: 'Directory where ImageMagick is located', 62 | type: 'string' 63 | } 64 | }) 65 | .demandCommand(1, 'You need to inform at least one command before moving on') 66 | .strict() 67 | .help() 68 | .argv; 69 | 70 | if (commandLine._.includes('convert')) { 71 | const options = _.pick(commandLine, 'imagemagickDir', 'cpuCores', 'alias', 'onlyIfChanged'); 72 | convertVideo(commandLine.src, commandLine.resDir, options); 73 | } 74 | } 75 | 76 | module.exports = { convertVideo }; -------------------------------------------------------------------------------- /js/execute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require('os'); 4 | const { spawn } = require('child_process'); 5 | 6 | const getCommand = (imagemagickDir, command) => imagemagickDir ? imagemagickDir + '/' + command : command; 7 | 8 | const executeCommand = async (command, args, {} = {}) => new Promise((resolve, reject) => { 9 | const process = spawn(command, args, { shell: true }); 10 | process.stdout.on('data', (data) => { 11 | console.log(data.toString()); 12 | }); 13 | process.stderr.on('data', (data) => { 14 | console.error(data.toString()); 15 | }); 16 | process.on('exit', (code) => { 17 | if (code) { 18 | reject(new Error(`Child returned error code ${code}`)); 19 | } else { 20 | resolve(); 21 | } 22 | }); 23 | process.on('error', (e) => { 24 | reject(e); 25 | }); 26 | }); 27 | 28 | const spawnWorkers = async (executor, jobs, { cpuCores, onProgress } = {}) => { 29 | cpuCores = cpuCores || os.cpus().length; 30 | 31 | let queuePosition = 0; 32 | let finishedJobs = 0; 33 | const spawnWorker = async () => { 34 | while (queuePosition < jobs.length) { 35 | const job = jobs[queuePosition]; 36 | queuePosition++; 37 | 38 | await executor(job); 39 | finishedJobs++; 40 | 41 | onProgress && onProgress({ job, finishedJobs, percent: finishedJobs * 100.0 / jobs.length }); 42 | } 43 | } 44 | 45 | await Promise.all(Array(cpuCores).fill(0).map(() => spawnWorker())); 46 | } 47 | 48 | const extractVideoFrames = async (srcVideo, destDir, { imagemagickDir }) => executeCommand( 49 | getCommand(imagemagickDir, 'ffmpeg'), 50 | ['-i', `"${srcVideo}"`, 51 | '-r', 12, 52 | '-s', '320x224', 53 | `"${destDir}/frame_%d.jpg"`, 54 | '-ar', 22050, 55 | '-ac', 1, 56 | '-acodec', 'pcm_u8', 57 | `"${destDir}/sound.wav"`]); 58 | 59 | const reduceTileCount = async (srcImage, destImage, { imagemagickDir } = {}) => executeCommand( 60 | 'npx', 61 | ['--max-old-space-size=4096', 62 | 'rgbquant-sms', 63 | 'convert', 64 | `"${srcImage}"`, 65 | `"${destImage}"`, 66 | '--colors', 16, 67 | '--maxTiles', 512, 68 | '--dithKern', 'Ordered2x1']); 69 | 70 | const convertImagesToIndexed = async (destDir, { imagemagickDir }) => executeCommand( 71 | getCommand(imagemagickDir, 'magick'), 72 | ['mogrify', 73 | `-path`, destDir, 74 | '-colors', 16, 75 | '-format', 'png', 76 | `${destDir}/*.png`]) 77 | 78 | module.exports = { executeCommand, spawnWorkers, extractVideoFrames, reduceTileCount, convertImagesToIndexed }; -------------------------------------------------------------------------------- /lib/movie_player.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "movie_player.h" 4 | 5 | /// Counter to be incremented by background task 6 | static volatile uint32_t movieFrameCount = 0; 7 | 8 | void MVP_VIntHandler() { 9 | movieFrameCount++; 10 | } 11 | 12 | void MVP_playMovie(const MovieData *movie) { 13 | // disable interrupt when accessing VDP 14 | SYS_disableInts(); 15 | 16 | // workaround to tiles leaking into PLAN A 17 | u16 originalBPlanAddress = VDP_getBPlanAddress(); 18 | VDP_setBPlanAddress(VDP_getAPlanAddress()); 19 | 20 | // Set up frame counter 21 | SYS_setVIntCallback(MVP_VIntHandler); 22 | 23 | // VDP process done, we can re enable interrupts 24 | SYS_enableInts(); 25 | 26 | bool activeBuffer = FALSE; 27 | u16 idx1 = TILE_USERINDEX; 28 | u16 idx2 = idx1 + movie->w * movie->h; 29 | 30 | u16 videoFrameRate = 12; 31 | u16 systemFrameRate = IS_PALSYSTEM ? 50 : 60; 32 | 33 | u16 videoFrame = 0; 34 | 35 | SYS_disableInts(); 36 | movieFrameCount = 0; 37 | SYS_enableInts(); 38 | 39 | SND_startPlay_2ADPCM(movie->sound, movie->soundLen, SOUND_PCM_CH1, FALSE); 40 | 41 | while (videoFrame < movie->frameCount) { 42 | const Image *frame = movie->frames[videoFrame]; 43 | u16 idx = activeBuffer ? idx1 : idx2; 44 | u16 palNum = activeBuffer ? PAL0 : PAL1; 45 | u16 palIdx = activeBuffer ? 0 : 16; 46 | 47 | VDP_loadTileSet(frame->tileset, idx, DMA_QUEUE); 48 | 49 | TileMap *ctmap = unpackTileMap(frame->tilemap, NULL); 50 | VDP_setTileMapEx(BG_A, ctmap, TILE_ATTR_FULL(palNum, FALSE, FALSE, FALSE, idx), 0, 0, 0, 0, frame->tilemap->w, frame->tilemap->h, DMA_QUEUE); 51 | 52 | VDP_waitVInt(); 53 | 54 | DMA_flushQueue(); 55 | VDP_setPaletteColors(palIdx, (u16*)frame->palette->data, palIdx + 16); 56 | MEM_free(ctmap); 57 | 58 | activeBuffer = !activeBuffer; 59 | 60 | // Waits for next video frame 61 | 62 | u16 previousVideoFrame = videoFrame; 63 | while (videoFrame == previousVideoFrame) { 64 | SYS_disableInts(); 65 | uint32_t frameCount = movieFrameCount; 66 | SYS_enableInts(); 67 | 68 | videoFrame = frameCount * videoFrameRate / systemFrameRate; 69 | if (videoFrame == previousVideoFrame) { 70 | VDP_waitVInt(); 71 | } 72 | } 73 | } 74 | 75 | VDP_waitVInt(); 76 | 77 | // Remove frame counter 78 | SYS_disableInts(); 79 | SYS_setVIntCallback(NULL); 80 | SYS_enableInts(); 81 | 82 | // Restore BPlan 83 | VDP_getBPlanAddress(originalBPlanAddress); 84 | 85 | // Stop sound. 86 | SND_stopPlay_2ADPCM (SOUND_PCM_CH1); 87 | } 88 | -------------------------------------------------------------------------------- /experiment/src/movie_player.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "movie_player.h" 4 | 5 | /// Counter to be incremented by background task 6 | static volatile uint32_t movieFrameCount = 0; 7 | 8 | void MVP_VIntHandler() { 9 | movieFrameCount++; 10 | } 11 | 12 | void MVP_playMovie(const MovieData *movie) { 13 | // disable interrupt when accessing VDP 14 | SYS_disableInts(); 15 | 16 | // workaround to tiles leaking into PLAN A 17 | u16 originalBPlanAddress = VDP_getBPlanAddress(); 18 | VDP_setBPlanAddress(VDP_getAPlanAddress()); 19 | 20 | // Set up frame counter 21 | SYS_setVIntCallback(MVP_VIntHandler); 22 | 23 | // VDP process done, we can re enable interrupts 24 | SYS_enableInts(); 25 | 26 | bool activeBuffer = FALSE; 27 | u16 idx1 = TILE_USERINDEX; 28 | u16 idx2 = idx1 + movie->w * movie->h; 29 | 30 | u16 videoFrameRate = 12; 31 | u16 systemFrameRate = IS_PALSYSTEM ? 50 : 60; 32 | 33 | u16 videoFrame = 0; 34 | 35 | SYS_disableInts(); 36 | movieFrameCount = 0; 37 | SYS_enableInts(); 38 | 39 | SND_startPlay_2ADPCM(movie->sound, movie->soundLen, SOUND_PCM_CH1, FALSE); 40 | 41 | while (videoFrame < movie->frameCount) { 42 | const Image *frame = movie->frames[videoFrame]; 43 | u16 idx = activeBuffer ? idx1 : idx2; 44 | u16 palNum = activeBuffer ? PAL0 : PAL1; 45 | u16 palIdx = activeBuffer ? 0 : 16; 46 | 47 | VDP_loadTileSet(frame->tileset, idx, DMA_QUEUE); 48 | 49 | TileMap *ctmap = unpackTileMap(frame->tilemap, NULL); 50 | VDP_setTileMapEx(BG_A, ctmap, TILE_ATTR_FULL(palNum, FALSE, FALSE, FALSE, idx), 0, 0, 0, 0, frame->tilemap->w, frame->tilemap->h, DMA_QUEUE); 51 | 52 | VDP_waitVInt(); 53 | 54 | DMA_flushQueue(); 55 | VDP_setPaletteColors(palIdx, (u16*)frame->palette->data, palIdx + 16); 56 | MEM_free(ctmap); 57 | 58 | activeBuffer = !activeBuffer; 59 | 60 | // Waits for next video frame 61 | 62 | u16 previousVideoFrame = videoFrame; 63 | while (videoFrame == previousVideoFrame) { 64 | SYS_disableInts(); 65 | uint32_t frameCount = movieFrameCount; 66 | SYS_enableInts(); 67 | 68 | videoFrame = frameCount * videoFrameRate / systemFrameRate; 69 | if (videoFrame == previousVideoFrame) { 70 | VDP_waitVInt(); 71 | } 72 | } 73 | } 74 | 75 | VDP_waitVInt(); 76 | 77 | // Remove frame counter 78 | SYS_disableInts(); 79 | SYS_setVIntCallback(NULL); 80 | SYS_enableInts(); 81 | 82 | // Restore BPlan 83 | VDP_getBPlanAddress(originalBPlanAddress); 84 | 85 | // Stop sound. 86 | SND_stopPlay_2ADPCM (SOUND_PCM_CH1); 87 | } 88 | -------------------------------------------------------------------------------- /js/generate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const { removeFileExtension, changeFileExtension, listFilesRegex } = require('./file'); 7 | 8 | const movieDataHeaderTemplate = (images, alias) => ` 9 | #ifndef _HEADER_${alias} 10 | #define _HEADER_${alias} 11 | 12 | #include "movie_player.h" 13 | 14 | extern const MovieData ${alias}; 15 | 16 | #endif // _HEADER_${alias} 17 | 18 | `; 19 | 20 | const movieDataTemplate = (images, alias) => ` 21 | #include "movie_player.h" 22 | #include "${alias}__frames.h" 23 | 24 | const MovieData ${alias} = { 25 | 0x01, 26 | 40, 28, 27 | ${images.length}, 28 | 29 | ${alias}__sound, 30 | sizeof(${alias}__sound), 31 | 32 | { 33 | ${images.map(s => ` &${alias}__${removeFileExtension(s)}`).join(',\n')} 34 | } 35 | 36 | }; 37 | 38 | `; 39 | 40 | const movieResourceTemplate = (images, alias) => images 41 | .map(s => `IMAGE ${alias}__${removeFileExtension(s)} "tmpmv_${alias}/${s}" FAST`) 42 | .join('\n') + '\n' + 43 | `WAV ${alias}__sound "tmpmv_${alias}/sound.wav" 2ADPCM` + '\n'; 44 | 45 | 46 | const getDestDir = (resDir, alias) => path.join(resDir, `tmpmv_${alias}`); 47 | 48 | const listSourceFrames = async (resDir, alias) => { 49 | const destDir = getDestDir(resDir, alias); 50 | return listFilesRegex(destDir, /^frame_(\d+)\.jpg$/); 51 | } 52 | 53 | const listImagesToConvert = async (resDir, alias) => { 54 | const destDir = getDestDir(resDir, alias); 55 | const sourceFrames = await listSourceFrames(resDir, alias); 56 | 57 | return sourceFrames.map(frameSrc => { 58 | const fullSrc = path.join(destDir, frameSrc); 59 | const dest = changeFileExtension(fullSrc, '.png'); 60 | 61 | return { src: fullSrc, dest }; 62 | }); 63 | }; 64 | 65 | 66 | const listCodeToGenerate = (resDir, alias) => { 67 | const createEntry = (name, sourceTemplate) => ({ 68 | fileName: path.join(resDir, name), 69 | sourceTemplate 70 | }); 71 | 72 | return [ 73 | createEntry(`${alias}.h`, movieDataHeaderTemplate), 74 | createEntry(`${alias}.c`, movieDataTemplate), 75 | createEntry(`${alias}__frames.res`, movieResourceTemplate) 76 | ]; 77 | }; 78 | 79 | 80 | const generateCode = async (images, resDir, alias) => { 81 | const codeToGenerate = listCodeToGenerate(resDir, alias); 82 | const codeGenerationPromises = codeToGenerate.map(({ fileName, sourceTemplate }) => 83 | fs.promises.writeFile(fileName, sourceTemplate(images, alias))); 84 | return Promise.all(codeGenerationPromises); 85 | } 86 | 87 | module.exports = { generateCode, listCodeToGenerate, getDestDir, listSourceFrames, listImagesToConvert }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to sgdk-video-player 👋

2 |

3 | Version 4 | 5 | Documentation 6 | 7 | 8 | Maintenance 9 | 10 | 11 | License: MIT 12 | 13 | 14 | Twitter: Haroldo0k 15 | 16 |

17 | 18 | > A video converter/video player for SGDK 19 | 20 | 21 | ### 🏠 [Homepage](https://github.com/haroldo-ok/sgdk-video-player#readme) 22 | 23 | ## Install 24 | 25 | ```sh 26 | npm install -g sgdk-video-player 27 | ``` 28 | 29 | ## Using the converter 30 | 31 | ```sh 32 | sgdk-video-player convert 33 | 34 | Positionals: 35 | src The source video, the one that will be converted [string] [required] 36 | resDir The resource directory, where the generated sources will be placed. 37 | [string] [required] 38 | 39 | Options: 40 | --version Show version number [boolean] 41 | --imagemagick-dir, --kd Directory where ImageMagick is located [string] 42 | --help Show help [boolean] 43 | --cpu-cores Number of CPU cores to use. If ommited, will use all 44 | of them. 45 | --alias Alias to use when generating the C constants. If 46 | ommited, it will be generated from . [string] 47 | --only-if-changed Only converts if file has been changed. 48 | [boolean] [default: false] 49 | ``` 50 | 51 | ## Using the video player 52 | 53 | The video player is on the `lib/` folder. You can copy it to your SGDK project. 54 | 55 | ## Author 56 | 57 | 👤 **Haroldo O. Pinheiro** 58 | 59 | * Website: https://haroldo-ok.itch.io/ 60 | * Twitter: [@Haroldo0k](https://twitter.com/Haroldo0k) 61 | * Github: [@haroldo-ok](https://github.com/haroldo-ok) 62 | * LinkedIn: [@haroldo-ok](https://linkedin.com/in/haroldo-ok) 63 | 64 | ## 🤝 Contributing 65 | 66 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/haroldo-ok/sgdk-video-player/issues). You can also take a look at the [contributing guide](https://github.com/haroldo-ok/sgdk-video-player/blob/master/CONTRIBUTING.md). 67 | 68 | ## Show your support 69 | 70 | Give a ⭐️ if this project helped you! 71 | 72 | ## 📝 License 73 | 74 | Copyright © 2023 [Haroldo O. Pinheiro](https://github.com/haroldo-ok).
75 | This project is [MIT](https://github.com/haroldo-ok/sgdk-video-player/blob/master/LICENSE) licensed. 76 | 77 | *** 78 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ -------------------------------------------------------------------------------- /experiment/src/boot/sega.s: -------------------------------------------------------------------------------- 1 | #include "task_cst.h" 2 | 3 | .section .text.keepboot 4 | 5 | *------------------------------------------------------- 6 | * 7 | * Sega startup code for the GNU Assembler 8 | * Translated from: 9 | * Sega startup code for the Sozobon C compiler 10 | * Written by Paul W. Lee 11 | * Modified by Charles Coty 12 | * Modified by Stephane Dallongeville 13 | * 14 | *------------------------------------------------------- 15 | 16 | .globl rom_header 17 | 18 | .org 0x00000000 19 | 20 | _Start_Of_Rom: 21 | _Vecteurs_68K: 22 | dc.l __stack /* Stack address */ 23 | dc.l _Entry_Point /* Program start address */ 24 | dc.l _Bus_Error 25 | dc.l _Address_Error 26 | dc.l _Illegal_Instruction 27 | dc.l _Zero_Divide 28 | dc.l _Chk_Instruction 29 | dc.l _Trapv_Instruction 30 | dc.l _Privilege_Violation 31 | dc.l _Trace 32 | dc.l _Line_1010_Emulation 33 | dc.l _Line_1111_Emulation 34 | dc.l _Error_Exception, _Error_Exception, _Error_Exception, _Error_Exception 35 | dc.l _Error_Exception, _Error_Exception, _Error_Exception, _Error_Exception 36 | dc.l _Error_Exception, _Error_Exception, _Error_Exception, _Error_Exception 37 | dc.l _Error_Exception, _INT, _EXTINT, _INT 38 | dc.l _HINT 39 | dc.l _INT 40 | dc.l _VINT 41 | dc.l _INT 42 | dc.l _trap_0 /* Resume supervisor task */ 43 | dc.l _INT,_INT,_INT,_INT,_INT,_INT,_INT 44 | dc.l _INT,_INT,_INT,_INT,_INT,_INT,_INT,_INT 45 | dc.l _INT,_INT,_INT,_INT,_INT,_INT,_INT,_INT 46 | dc.l _INT,_INT,_INT,_INT,_INT,_INT,_INT,_INT 47 | 48 | rom_header: 49 | .incbin "out/rom_head.bin", 0, 0x100 50 | 51 | _Entry_Point: 52 | move #0x2700,%sr 53 | tst.l 0xa10008 54 | bne.s SkipJoyDetect 55 | 56 | tst.w 0xa1000c 57 | 58 | SkipJoyDetect: 59 | bne.s SkipSetup 60 | 61 | lea Table,%a5 62 | movem.w (%a5)+,%d5-%d7 63 | movem.l (%a5)+,%a0-%a4 64 | * Check Version Number 65 | move.b -0x10ff(%a1),%d0 66 | andi.b #0x0f,%d0 67 | beq.s WrongVersion 68 | 69 | * Sega Security Code (SEGA) 70 | move.l #0x53454741,0x2f00(%a1) 71 | WrongVersion: 72 | * Read from the control port to cancel any pending read/write command 73 | move.w (%a4),%d0 74 | 75 | * Configure a USER_STACK_LENGTH bytes user stack at bottom, and system stack on top of it 76 | move %sp, %usp 77 | sub #USER_STACK_LENGTH, %sp 78 | 79 | move.w %d7,(%a1) 80 | move.w %d7,(%a2) 81 | 82 | * Jump to initialisation process now... 83 | 84 | jmp _start_entry 85 | 86 | SkipSetup: 87 | jmp _reset_entry 88 | 89 | 90 | Table: 91 | dc.w 0x8000,0x3fff,0x0100 92 | dc.l 0xA00000,0xA11100,0xA11200,0xC00000,0xC00004 93 | 94 | 95 | *------------------------------------------------ 96 | * 97 | * interrupt functions 98 | * 99 | *------------------------------------------------ 100 | 101 | registersDump: 102 | move.l %d0,registerState+0 103 | move.l %d1,registerState+4 104 | move.l %d2,registerState+8 105 | move.l %d3,registerState+12 106 | move.l %d4,registerState+16 107 | move.l %d5,registerState+20 108 | move.l %d6,registerState+24 109 | move.l %d7,registerState+28 110 | move.l %a0,registerState+32 111 | move.l %a1,registerState+36 112 | move.l %a2,registerState+40 113 | move.l %a3,registerState+44 114 | move.l %a4,registerState+48 115 | move.l %a5,registerState+52 116 | move.l %a6,registerState+56 117 | move.l %a7,registerState+60 118 | rts 119 | 120 | busAddressErrorDump: 121 | move.w 4(%sp),ext1State 122 | move.l 6(%sp),addrState 123 | move.w 10(%sp),ext2State 124 | move.w 12(%sp),srState 125 | move.l 14(%sp),pcState 126 | jmp registersDump 127 | 128 | exception4WDump: 129 | move.w 4(%sp),srState 130 | move.l 6(%sp),pcState 131 | move.w 10(%sp),ext1State 132 | jmp registersDump 133 | 134 | exceptionDump: 135 | move.w 4(%sp),srState 136 | move.l 6(%sp),pcState 137 | jmp registersDump 138 | 139 | 140 | _Bus_Error: 141 | jsr busAddressErrorDump 142 | movem.l %d0-%d1/%a0-%a1,-(%sp) 143 | move.l busErrorCB, %a0 144 | jsr (%a0) 145 | movem.l (%sp)+,%d0-%d1/%a0-%a1 146 | rte 147 | 148 | _Address_Error: 149 | jsr busAddressErrorDump 150 | movem.l %d0-%d1/%a0-%a1,-(%sp) 151 | move.l addressErrorCB, %a0 152 | jsr (%a0) 153 | movem.l (%sp)+,%d0-%d1/%a0-%a1 154 | rte 155 | 156 | _Illegal_Instruction: 157 | jsr exception4WDump 158 | movem.l %d0-%d1/%a0-%a1,-(%sp) 159 | move.l illegalInstCB, %a0 160 | jsr (%a0) 161 | movem.l (%sp)+,%d0-%d1/%a0-%a1 162 | rte 163 | 164 | _Zero_Divide: 165 | jsr exceptionDump 166 | movem.l %d0-%d1/%a0-%a1,-(%sp) 167 | move.l zeroDivideCB, %a0 168 | jsr (%a0) 169 | movem.l (%sp)+,%d0-%d1/%a0-%a1 170 | rte 171 | 172 | _Chk_Instruction: 173 | jsr exception4WDump 174 | movem.l %d0-%d1/%a0-%a1,-(%sp) 175 | move.l chkInstCB, %a0 176 | jsr (%a0) 177 | movem.l (%sp)+,%d0-%d1/%a0-%a1 178 | rte 179 | 180 | _Trapv_Instruction: 181 | jsr exception4WDump 182 | movem.l %d0-%d1/%a0-%a1,-(%sp) 183 | move.l trapvInstCB, %a0 184 | jsr (%a0) 185 | movem.l (%sp)+,%d0-%d1/%a0-%a1 186 | rte 187 | 188 | _Privilege_Violation: 189 | jsr exceptionDump 190 | movem.l %d0-%d1/%a0-%a1,-(%sp) 191 | move.l privilegeViolationCB, %a0 192 | jsr (%a0) 193 | movem.l (%sp)+,%d0-%d1/%a0-%a1 194 | rte 195 | 196 | _Trace: 197 | jsr exceptionDump 198 | movem.l %d0-%d1/%a0-%a1,-(%sp) 199 | move.l traceCB, %a0 200 | jsr (%a0) 201 | movem.l (%sp)+,%d0-%d1/%a0-%a1 202 | rte 203 | 204 | _Line_1010_Emulation: 205 | _Line_1111_Emulation: 206 | jsr exceptionDump 207 | movem.l %d0-%d1/%a0-%a1,-(%sp) 208 | move.l line1x1xCB, %a0 209 | jsr (%a0) 210 | movem.l (%sp)+,%d0-%d1/%a0-%a1 211 | rte 212 | 213 | _Error_Exception: 214 | jsr exceptionDump 215 | movem.l %d0-%d1/%a0-%a1,-(%sp) 216 | move.l errorExceptionCB, %a0 217 | jsr (%a0) 218 | movem.l (%sp)+,%d0-%d1/%a0-%a1 219 | rte 220 | 221 | _INT: 222 | movem.l %d0-%d1/%a0-%a1,-(%sp) 223 | move.l intCB, %a0 224 | jsr (%a0) 225 | movem.l (%sp)+,%d0-%d1/%a0-%a1 226 | rte 227 | 228 | _EXTINT: 229 | movem.l %d0-%d1/%a0-%a1,-(%sp) 230 | move.l eintCB, %a0 231 | jsr (%a0) 232 | movem.l (%sp)+,%d0-%d1/%a0-%a1 233 | rte 234 | 235 | _HINT: 236 | movem.l %d0-%d1/%a0-%a1,-(%sp) 237 | move.l hintCB, %a0 238 | jsr (%a0) 239 | movem.l (%sp)+,%d0-%d1/%a0-%a1 240 | rte 241 | 242 | _VINT: 243 | btst #5, (%sp) /* Skip context switch if not in user task */ 244 | bne.s no_user_task 245 | 246 | tst.w task_lock 247 | bne.s 1f 248 | move.w #0, -(%sp) /* TSK_superPend() will return 0 */ 249 | bra.s unlock /* If lock == 0, supervisor task is not locked */ 250 | 251 | 1: 252 | bcs.s no_user_task /* If lock < 0, super is locked with infinite wait */ 253 | subq.w #1, task_lock /* Locked with wait, subtract 1 to the frame count */ 254 | bne.s no_user_task /* And do not unlock if we did not reach 0 */ 255 | move.w #1, -(%sp) /* TSK_superPend() will return 1 */ 256 | 257 | unlock: 258 | /* Save bg task registers (excepting a7, that is stored in usp) */ 259 | move.l %a0, task_regs 260 | lea (task_regs + UTSK_REGS_LEN), %a0 261 | movem.l %d0-%d7/%a1-%a6, -(%a0) 262 | 263 | move.w (%sp)+, %d0 /* Load return value previously pushed to stack */ 264 | 265 | move.w (%sp)+, task_sr /* Pop user task sr and pc, and save them, */ 266 | move.l (%sp)+, task_pc /* so they can be restored later. */ 267 | movem.l (%sp)+, %d2-%d7/%a2-%a6 /* Restore non clobberable registers */ 268 | 269 | no_user_task: 270 | /* At this point, we always have in the stack the SR and PC of the task */ 271 | /* we want to jump after processing the interrupt, that might be the */ 272 | /* point where we came from (if there is no context switch) or the */ 273 | /* supervisor task (if we unlocked it). */ 274 | 275 | movem.l %d0-%d1/%a0-%a1,-(%sp) 276 | ori.w #0x0001, intTrace /* in V-Int */ 277 | addq.l #1, vtimer /* increment frame counter (more a vint counter) */ 278 | btst #3, VBlankProcess+1 /* PROCESS_XGM_TASK ? (use VBlankProcess+1 as btst is a byte operation) */ 279 | beq.s no_xgm_task 280 | 281 | jsr XGM_doVBlankProcess /* do XGM vblank task */ 282 | 283 | no_xgm_task: 284 | btst #1, VBlankProcess+1 /* PROCESS_BITMAP_TASK ? (use VBlankProcess+1 as btst is a byte operation) */ 285 | beq.s no_bmp_task 286 | 287 | jsr BMP_doVBlankProcess /* do BMP vblank task */ 288 | 289 | no_bmp_task: 290 | move.l vintCB, %a0 /* load user callback */ 291 | jsr (%a0) /* call user callback */ 292 | andi.w #0xFFFE, intTrace /* out V-Int */ 293 | movem.l (%sp)+,%d0-%d1/%a0-%a1 294 | rte 295 | 296 | *------------------------------------------------ 297 | * 298 | * Copyright (c) 1988 by Sozobon, Limited. Author: Johann Ruegg 299 | * 300 | * Permission is granted to anyone to use this software for any purpose 301 | * on any computer system, and to redistribute it freely, with the 302 | * following restrictions: 303 | * 1) No charge may be made other than reasonable charges for reproduction. 304 | * 2) Modified versions must be clearly marked as such. 305 | * 3) The authors are not responsible for any harmful consequences 306 | * of using this software, even if they result from defects in it. 307 | * 308 | *------------------------------------------------ 309 | 310 | ldiv: 311 | move.l 4(%a7),%d0 312 | bpl ld1 313 | neg.l %d0 314 | ld1: 315 | move.l 8(%a7),%d1 316 | bpl ld2 317 | neg.l %d1 318 | eor.b #0x80,4(%a7) 319 | ld2: 320 | bsr i_ldiv /* d0 = d0/d1 */ 321 | tst.b 4(%a7) 322 | bpl ld3 323 | neg.l %d0 324 | ld3: 325 | rts 326 | 327 | lmul: 328 | move.l 4(%a7),%d0 329 | bpl lm1 330 | neg.l %d0 331 | lm1: 332 | move.l 8(%a7),%d1 333 | bpl lm2 334 | neg.l %d1 335 | eor.b #0x80,4(%a7) 336 | lm2: 337 | bsr i_lmul /* d0 = d0*d1 */ 338 | tst.b 4(%a7) 339 | bpl lm3 340 | neg.l %d0 341 | lm3: 342 | rts 343 | 344 | lrem: 345 | move.l 4(%a7),%d0 346 | bpl lr1 347 | neg.l %d0 348 | lr1: 349 | move.l 8(%a7),%d1 350 | bpl lr2 351 | neg.l %d1 352 | lr2: 353 | bsr i_ldiv /* d1 = d0%d1 */ 354 | move.l %d1,%d0 355 | tst.b 4(%a7) 356 | bpl lr3 357 | neg.l %d0 358 | lr3: 359 | rts 360 | 361 | ldivu: 362 | move.l 4(%a7),%d0 363 | move.l 8(%a7),%d1 364 | bsr i_ldiv 365 | rts 366 | 367 | lmulu: 368 | move.l 4(%a7),%d0 369 | move.l 8(%a7),%d1 370 | bsr i_lmul 371 | rts 372 | 373 | lremu: 374 | move.l 4(%a7),%d0 375 | move.l 8(%a7),%d1 376 | bsr i_ldiv 377 | move.l %d1,%d0 378 | rts 379 | * 380 | * A in d0, B in d1, return A*B in d0 381 | * 382 | i_lmul: 383 | move.l %d3,%a2 /* save d3 */ 384 | move.w %d1,%d2 385 | mulu %d0,%d2 /* d2 = Al * Bl */ 386 | 387 | move.l %d1,%d3 388 | swap %d3 389 | mulu %d0,%d3 /* d3 = Al * Bh */ 390 | 391 | swap %d0 392 | mulu %d1,%d0 /* d0 = Ah * Bl */ 393 | 394 | add.l %d3,%d0 /* d0 = (Ah*Bl + Al*Bh) */ 395 | swap %d0 396 | clr.w %d0 /* d0 = (Ah*Bl + Al*Bh) << 16 */ 397 | 398 | add.l %d2,%d0 /* d0 = A*B */ 399 | move.l %a2,%d3 /* restore d3 */ 400 | rts 401 | * 402 | *A in d0, B in d1, return A/B in d0, A%B in d1 403 | * 404 | i_ldiv: 405 | tst.l %d1 406 | bne nz1 407 | 408 | * divide by zero 409 | * divu #0,%d0 /* cause trap */ 410 | move.l #0x80000000,%d0 411 | move.l %d0,%d1 412 | rts 413 | nz1: 414 | move.l %d3,%a2 /* save d3 */ 415 | cmp.l %d1,%d0 416 | bhi norm 417 | beq is1 418 | * AB and B is not 0 430 | norm: 431 | cmp.l #1,%d1 432 | bne not1 433 | * B==1, so ret A, rem 0 434 | clr.l %d1 435 | move.l %a2,%d3 /* restore d3 */ 436 | rts 437 | * check for A short (implies B short also) 438 | not1: 439 | cmp.l #0xffff,%d0 440 | bhi slow 441 | * A short and B short -- use 'divu' 442 | divu %d1,%d0 /* d0 = REM:ANS */ 443 | swap %d0 /* d0 = ANS:REM */ 444 | clr.l %d1 445 | move.w %d0,%d1 /* d1 = REM */ 446 | clr.w %d0 447 | swap %d0 448 | move.l %a2,%d3 /* restore d3 */ 449 | rts 450 | * check for B short 451 | slow: 452 | cmp.l #0xffff,%d1 453 | bhi slower 454 | * A long and B short -- use special stuff from gnu 455 | move.l %d0,%d2 456 | clr.w %d2 457 | swap %d2 458 | divu %d1,%d2 /* d2 = REM:ANS of Ahi/B */ 459 | clr.l %d3 460 | move.w %d2,%d3 /* d3 = Ahi/B */ 461 | swap %d3 462 | 463 | move.w %d0,%d2 /* d2 = REM << 16 + Alo */ 464 | divu %d1,%d2 /* d2 = REM:ANS of stuff/B */ 465 | 466 | move.l %d2,%d1 467 | clr.w %d1 468 | swap %d1 /* d1 = REM */ 469 | 470 | clr.l %d0 471 | move.w %d2,%d0 472 | add.l %d3,%d0 /* d0 = ANS */ 473 | move.l %a2,%d3 /* restore d3 */ 474 | rts 475 | * A>B, B > 1 476 | slower: 477 | move.l #1,%d2 478 | clr.l %d3 479 | moreadj: 480 | cmp.l %d0,%d1 481 | bhs adj 482 | add.l %d2,%d2 483 | add.l %d1,%d1 484 | bpl moreadj 485 | * we shifted B until its >A or sign bit set 486 | * we shifted #1 (d2) along with it 487 | adj: 488 | cmp.l %d0,%d1 489 | bhi ltuns 490 | or.l %d2,%d3 491 | sub.l %d1,%d0 492 | ltuns: 493 | lsr.l #1,%d1 494 | lsr.l #1,%d2 495 | bne adj 496 | * d3=answer, d0=rem 497 | move.l %d0,%d1 498 | move.l %d3,%d0 499 | move.l %a2,%d3 /* restore d3 */ 500 | rts 501 | --------------------------------------------------------------------------------